2013-07-18 11 views
5

Dzieje 0.001 sekund do wykonania i używa indeksu szukaćDlaczego Indeks nie jest używany z Podzapytanie

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

Teraz mam procedura przechowywana U_VIP która zwraca ten sam identyfikator jako przykład jednego (1008,1122), a trwa tylko 0,001 sekundy do wykonania

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

teraz, kiedy je połączyć, trwa około pół-sekundę do wykonania i indeks nie jest używany

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

Uprośniłem powyższy przykład, w rzeczywistym zastosowaniu na wydajność ma wpływ o wiele większa jasność. Jak zmusić Firebirda do korzystania z indeksu w tym przypadku?

** Korzystanie Firebird 2.1

** EDIT **

Bazę na odpowiedź Marka, wykorzystanie JOIN ma poprawić czas realizacji, ponieważ jest on teraz robi Indeks szukać.

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

To świetnie, ale wprowadza kolejny problem, który spróbuję wyjaśnić w poniższym przykładzie.

SELECT CUSTOMER.* 
FROM CUSTOMER 
WHERE (:AREAID = 0 OR ID IN (SELECT ID FROM U_VIP(:AREAID))) 

Używając klauzuli where, mogę warunkowo zastosować filtr na podstawie tego, czy: AREAID jest dostarczany przez użytkownika. Jak osiągnąć to samo, gdy zastępuję klauzulę where złączeniem?

Coś jak:

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

Które oczywiście Firebird lubi część z szelkami =/

Odpowiedz

5

Zamiast IN, trzeba użyć EXISTS lub INNER JOIN. Nie jestem do końca pewien co do szczegółów, ale wierzę w to, że tabela CUSTOMER jest w pełni odczytana, oceniając wynik podkwerendy dla każdego wiersza (może nawet wykonuje podzapytanie dla każdego wiersza). Ponieważ optymalizator nie zna wcześniej liczby wyników podkwerendy, nie może utworzyć optymalizacji takiej, jak może, jeśli używa się ustalonej liczby literalnych wartości, jak w pierwszym zapytaniu.

Spróbuj zmienić zapytanie do:

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

Lub:

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

lub (zmiana kolejności czasami może prowadzić do lepszej wydajności):

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

W ogóle bym oczekiwać, że te zapytania będą działać lepiej niż zapytanie z IN.

Edycja w odpowiedzi na aktualizację

podstawie zaktualizowanego pytanie mogę myśleć o wielu rozwiązaniach, nie jestem do końca pewien, choć na ich wydajność.

  • Używaj oddzielnych zapytań dla :AREAID jest 0 i :AREAID nie jest 0
  • pomocą procedury przechowywanej lub EXECUTE BLOCK z EXECUTE STATEMENT z dynamicznie zbudowany rachunku (wariant poprzedni)
  • Bądź procedura przechowywana U_VIP powrót wszyscy klienci, jeśli :AREAID jest 0
  • Użyj dodatkowego JOIN stanu OR :AREAID = 0; to może nie przynosić rezultaty jeśli U_VIP nic nie zwraca 0 (a może nie wykonać *)
  • Korzystając LEFT JOIN i dodać WHERE U_VIP.ID IS NOT NULL OR :AREAID = 0 (nie może wykonać *)
  • użyć UNION od „normalnego” zapytania i drugie zapytanie o CUSTOMER z WHERE :AREAID = 0 (nie może wykonać *)

Dla (*) zobaczyć 'Smart logic' anti-pattern

Dla dynamicznie zbudowany zapytania można myśleć o czymś takim:

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 

W tym przykładzie wartość 0 do INPUTCONDITION generuje zapytanie bez WHERE -clause, a inne wejścia kwerendy z WHERE -clause. Takie działanie jest podatne na iniekcję SQL, jeśli parametr jest (VAR)CHAR lub BLOB, więc należy zachować ostrożność. Można również rozważyć dwie gałęzie, w których jedna używa EXECUTE STATEMENT z parametrami, a druga bez.

Zamiast EXECUTE BLOCK można również użyć wybieranej procedury, takiej jak ta, której już używasz do U_VIP; EXECUTE BLOCK jest zasadniczo procedurą przechowywaną, która nie jest przechowywana w bazie danych.

Zobacz także 'Myth: dynamic SQL is slow'

+0

+1 za odpowiedź bardzo schludny mimo – Kagawa

+0

@Kagawa Nie jestem pewien, czy rozumiem cię, ale myślę, używając 'unia (jeden normalny, a nie' UNION ALL'!) Z zapytanie łączące się na 'U_VIP' i dodatkowe zaznaczenie na' CUSTOMER' i użycie warunku 'CUSTOMER.ID =: INPUTCUSTID' na drugim zapytaniu powinno wystarczyć. Dzięki temu uzyskasz U_VIP i konkretnego klienta. –

+0

@Kagawa Jeśli to jest aktualny problem, możesz zaktualizować swoje pytanie, aby dodać tę informację :) –

Powiązane problemy