2013-07-18 8 views
5

Dies dauert 0,001 Sekunden auszuführen, und es nutzt IndexWarum Index nicht mit Unterabfrage verwendet

SELECT * FROM CUSTOMER WHERE ID IN (1008,1122) 

Jetzt habe ich eine gespeicherte Prozedur U_VIP suchen, die die gleiche ID wie Beispiel gibt ein (1008,1122) und es nur 0,001 Sekunden

SELECT ID FROM U_VIP //returns (1008,1122) 

Jetzt auszuführen, wenn ich sie kombinieren, dauert es etwa ein halbes Sekunde auszuführen und Index nicht

SELECT * FROM CUSTOMER WHERE ID IN (SELECT ID FROM U_VIP) 
verwendet wird

Ich habe das obige Beispiel vereinfacht, in der tatsächlichen Anwendung wird die Leistung von viel höherer Größenordnung beeinflusst. Wie erzwinge Firebird in diesem Fall den Index zu verwenden?

** Firebird 2.1

Mit ** EDIT **

Base auf Mark Antwort, JOIN die Ausführungszeit nicht verbessert, weil es jetzt Index suchen zu tun.

SELECT CUSTOMER.* 
FROM CUSTOMER 
INNER JOIN U_VIP ON U_VIP.ID = CUSTOMER.ID 

Dieser große ist, stellt es jedoch ein weiteres Problem für mich, die ich versuchen werde im folgenden Beispiel zu erklären.

Mit where-Klausel kann ich die Filterbasis abhängig davon verwenden, ob: AREAID vom Benutzer bereitgestellt wird. Wie erreiche ich dasselbe, wenn ich die WHERE-Klausel durch einen Join ersetze?

Etwas wie:

SELECT CUSTOMER.* 
FROM CUSTOMER 
{IF :AREAID > 0 THEN} 
INNER JOIN (SELECT ID FROM U_VIP(:AREAID)) VIP ON VIP.ID = CUSTOMER.ID 
{END IF} 

Was natürlich, Firebird den Teil mit Klammern =/

Antwort

5

Statt IN Abneigungen, müssen Sie EXISTS oder eine INNER JOIN verwenden. Ich bin nicht ganz sicher über die Details, aber ich glaube, dass in Ihrer Abfrage die CUSTOMER Tabelle vollständig gelesen wird und das Ergebnis der Unterabfrage für jede Zeile ausgewertet wird (vielleicht sogar die Unterabfrage für jede Zeile ausführt). Da der Optimierer die Anzahl der Ergebnisse der Unterabfrage nicht im Voraus kennt, kann er keine Optimierung erstellen, wie dies möglich ist, wenn Sie eine feste Anzahl literaler Werte wie in Ihrer ersten Abfrage verwenden.

Versuchen Sie bitte zum Ändern:

SELECT * 
FROM CUSTOMER 
WHERE EXISTS (SELECT 1 FROM U_VIP WHERE U_VIP.ID = CUSTOMER.ID) 

Oder:

SELECT CUSTOMER.* 
FROM CUSTOMER 
INNER JOIN U_VIP ON U_VIP.ID = CUSTOMER.ID 

Oder (Änderung der Reihenfolge kann zu einer besseren Leistung führen manchmal):

SELECT CUSTOMER.* 
FROM U_VIP 
INNER JOIN CUSTOMER ON CUSTOMER.ID = U_VIP.ID 

Im Allgemeinen würde ich erwarten, dass diese Abfragen besser abschneiden als die Abfrage mit IN.

bearbeitet als Antwort zu aktualisieren

Basierend auf Ihrer aktualisierten Frage, die ich von mehreren Lösungen denken, ich bin nicht ganz sicher, obwohl auf ihrer Leistung.

  • Verwenden separate Abfragen für :AREAID 0 und :AREAID nicht 0
  • eine gespeicherte Prozedur verwenden oder EXECUTE BLOCK mit einem EXECUTE STATEMENT mit einer dynamisch erstellte Aussage (Variante des vorherigen)
  • Sprechen Sie die gespeicherte Prozedur U_VIP Rückkehr Alle Kunden, wenn :AREAID ist 0
  • Verwenden Sie eine zusätzliche JOIN Bedingung OR :AREAID = 0; könnte dies nicht Ergebnissen führen, wenn U_VIP nichts für 0 zurück (und führen möglicherweise nicht *)
  • Verwenden Sie ein LEFT JOIN und fügen WHERE U_VIP.ID IS NOT NULL OR :AREAID = 0 (möglicherweise nicht durchführen *)
  • Verwenden Sie ein UNION der ‚normalen‘ Abfrage und eine zweite Abfrage auf CUSTOMER mit WHERE :AREAID = 0 (möglicherweise nicht durchführen *)

Für (*) sehen die 'Smart logic' anti-Muster

Für die Abfrage dynamisch erstellt Ihnen etwas ausdenken können wie:

EXECUTE BLOCK (INPUTCONDITION INTEGER = ?) 
    RETURNS (ID INTEGER) 
AS 
    DECLARE VARIABLE QUERY VARCHAR(6400); 
BEGIN 
    QUERY = 'SELECT a.ID FROM SORT_TEST a'; 
    IF (INPUTCONDITION <> 0) then 
     QUERY = QUERY || ' WHERE a.ID = ' || INPUTCONDITION; 
    FOR EXECUTE STATEMENT QUERY INTO :ID 
    DO 
     SUSPEND; 
END 

der Wert 0 für INPUTCONDITION In diesem Beispiel wird eine Abfrage ohne WHERE -Klausel erzeugen, und für die anderen Eingänge einer Abfrage mit einer WHERE -Klausel. Wenn Sie so vorgehen, besteht die Gefahr einer SQL-Injektion, wenn der Parameter (VAR)CHAR oder BLOB ist. Seien Sie also vorsichtig. Du könntest auch zwei Zweige betrachten, wo man EXECUTE STATEMENT mit Parametern verwendet und die andere ohne.

Anstelle von EXECUTE BLOCK können Sie auch ein auswählbares Verfahren verwenden, das Sie bereits für U_VIP verwenden; EXECUTE BLOCK ist im Wesentlichen eine gespeicherte Prozedur, die nicht in der Datenbank gespeichert ist.

Siehe 'Myth: dynamic SQL is slow'

+0

1 auch für sehr ordentlich Antwort dennoch – Kagawa

+0

@Kagawa Ich bin nicht sicher, ob ich verstehe dich, aber ich denke, eine 'UNION' (eine normale, nicht' UNION ALL'!) Mit der die Abfrage, die sich an "U_VIP" anschließt, und eine zusätzliche Auswahl an "KUNDE" und die Verwendung der Bedingung "KUNDE.ID =: EINGANGSKENNWORT" bei der zweiten Abfrage würden den Trick machen. Dadurch erhalten Sie die U_VIP und den spezifischen Kunden. –

+0

@Kagawa Wenn das das eigentliche Problem ist, möchten Sie vielleicht Ihre Frage aktualisieren, um diesen Teil der Info hinzuzufügen :) –

Verwandte Themen