2013-02-18 17 views
5

Jeśli mam następujące zapytanie zabawkaCzy Postgresql plpgsql/sql obsługuje zwarcie w klauzuli where?

SELECT * 
FROM my_tables 
WHERE my_id in (
    SELECT my_other_id 
    FROM my_other_tables 
) AND some_slow_func(arg) BETWEEN 1 AND 2; 

Czy pierwszy warunek w klauzuli WHERE zwarcia drugi warunek, które mają złożoną czas pracy?

Pracuję nad pewnym sql, który jest faktycznie częścią FOR LOOP w plpgsql, i mógłbym zrobić iteracje nad wszystkimi rekordami istniejącymi w my_other_tables, a następnie przetestować w ramach FOR LOOP z some_slow_func (). Ale jestem ciekawy, czy obsługuje sql, lub plpgsql obsługuje zwarcie.

rozeznanie: Spojrzałem na listach dyskusyjnych PostgreSQL oraz SQL, które oceniły tę mówiąc w ogóle nie obsługuje zwarcia:

http://www.postgresql.org/message-id/[email protected]

Ale jedną z odpowiedzi mówi, że zamówienie może być egzekwowane przez subselects. Nie jestem do końca pewien, o czym on mówi. Wiem, co to jest selekcja podrzędna, ale nie jestem pewien, w jaki sposób będzie egzekwowane zamówienie? Czy ktoś może mi to wyjaśnić?

+0

Nie sądzę, że zwarcie jest istotne; SQL ma być zorientowany na ustawienie, a wynik nie powinien zależeć od kolejności oceny. Wyjątkiem od tego * może * być UNION z dwóch podzapytań, oba z LIMIT plus dodatkowy LIMIT na kwerendzie jako całość. Ale LIMIT jest tak czy inaczej ... Efekty uboczne oceny nie powinny być możliwe w prawdziwie relacyjnych RSBMS (może poza NIESTANDARDOWYM).W skrócie: kolejność oceny wpływa tylko na wydajność, a nie (poprawność) wyników, IMHO. Dlatego powinniśmy zostawić kolejność oceny planiście. – wildplasser

Odpowiedz

6

Zgodnie z dokumentacją, kolejność oceny w klauzuli WHERE ma być nieprzewidywalna.

Różni się w przypadku podkwerend. W aktualnych wersjach najprostszą i powszechną metodą sterowania kolejnością oceny jest zapisanie podkwerendy w CTE. Aby upewnić się, że IN(...) ocenia najpierw, kod może być zapisany jako:

WITH subquery AS 
(select * from my_tables 
    WHERE my_id in (SELECT my_other_id FROM my_other_tables) 
) 
SELECT * FROM subquery 
    WHERE some_slow_func(arg) BETWEEN 1 AND 2; 

coś innego, że można dostosować to koszt swojej funkcji, aby zasygnalizować, że optymalizator jest powolne. Koszt domyślną funkcją jest 100 i może być zmieniony z oświadczeniem, takich jak:

ALTER FUNCTION funcname(argument types) cost N; 

gdzie N jest szacunkowy koszt wezwanie, wyrażone w dowolnej jednostce, która powinna być w porównaniu do Planner Cost Constants.

+0

Tak, użycie WITH byłoby dobrym sposobem na ograniczenie zestawu. Nie jestem pewien, dlaczego nie pomyślałem o tym .... Uwielbiam używać WITH. Nie myślał nawet o zmianie kosztu planisty. Wiedziałem o tym, ale zawsze pozwalałem postgresowi dbać o optymalizację. Właściwie nigdy wcześniej go nie użyłem, ale przeczytam o tym więcej. Dziękuję za zwrócenie mi uwagi! – enigmasck

+0

Możesz także dodać 'COST N' przy definiowaniu funkcji. – jpmc26

0

Zgodnie z the Postgresql docs i this answer by Tom Lane kolejność wykonywania ograniczeń WHERE nie jest wiarygodna.

Myślę, że najlepiej będzie dodać drugą część WHERE do górnej części swojej funkcji i "szybko zawieść"; tj. uruchom my_id in ( SELECT my_other_id FROM my_other_tables) w swojej funkcji, a jeśli nie przejdzie, wróć tutaj, zanim wykonasz intensywne przetwarzanie. To powinno dać ci mniej więcej taki sam efekt.

+0

Dzięki za referencje. Przez cały dzień patrzę na dokumentację postgresql i nie natknąłem się na to wyjaśnienie. Doceniam twoją szybką odpowiedź. – enigmasck

+0

Nie ma problemu. Jedna wskazówka - gdy patrzysz na te fora, na dole zazwyczaj znajdują się sekcje "W odpowiedzi na" i "Odpowiedzi". Jeśli skorzystasz z linków pod odpowiedziami, prawdopodobnie uzyskasz więcej informacji. W tym przypadku wszystko, co zrobiłem, to śledzenie odpowiedzi Toma Lane'a na temat postu powiązanego w OP. –

2

Wiem, że to stare pytanie, ale ostatnio wpadł na podobny problem, a znalezione przy użyciu predykatu CASE w klauzuli WHERE działało lepiej dla mnie. W kontekście powyższej odpowiedzi:

SELECT * 
    FROM my_tables 
WHERE CASE WHEN my_id in (SELECT my_other_id 
          FROM my_other_tables) 
      AND some_slow_func(arg) BETWEEN 1 AND 2 
      THEN 1 
      ELSE 0 
     END = 1; 

To sprawia, że ​​SQL jest nieco bardziej agnostyczny. Oczywiście, może nie używać indeksów, jeśli masz trochę na my_id, ale w zależności od kontekstu, w którym się znajdujesz, może to być dobra opcja.

Powiązane problemy