Podzapytanie to SELECT wewnątrz SELECT
Podzapytanie jest dokładnie tym, na co wygląda: instrukcją SELECT schowaną w innej instrukcji i ujętą w nawiasy. SQLite wykonuje zapytanie wewnętrzne, bierze jego wynik i przekazuje go do zewnętrznego.
Przygotujmy mały przykład, z którego będziemy korzystać dalej:
Pięć zamówień, czterech klientów, z których dwóch nic nie zamówiło. Będziemy używać tych danych przez całą stronę.
Podzapytanie w WHERE: filtrowanie według listy
Najczęstszy schemat: pobierz listę identyfikatorów w zapytaniu wewnętrznym, a potem przefiltruj według niej zapytanie zewnętrzne.
Zapytanie wewnętrzne zwraca każdy customer_id, który występuje w orders. Zapytanie zewnętrzne zostawia tylko klientów, których id jest na tej liście. Pojawiają się Cleo, Boris i Ada; Dmitri (bez zamówień) nie.
IN (SELECT ...) to podstawowy wzorzec dla pytania „wiersze z A, które mają dopasowanie w B”. Czytaj to w myślach jako „gdzie wartość tej kolumny jest jedną z wartości zwróconych przez zapytanie wewnętrzne”.
NOT IN: uważaj na NULL
Odwrotne pytanie, „którzy klienci nic nie zamówili?”, jest o jedną linijkę dalej:
Tutaj to działa. Ale NOT IN ma ostrą krawędź: jeśli podzapytanie kiedykolwiek zwróci NULL, całe NOT IN staje się NULL (a to nie jest TRUE) i dostajesz zero wierszy. Zaskakujące i ciche.
Bezpieczny nawyk przy użyciu NOT IN z kolumną, która może zawierać NULL:
Albo użyj NOT EXISTS, które w ogóle nie ma tego problemu. Zaraz do tego dojdziemy.
Podzapytania skalarne: jeden wiersz, jedna kolumna
Podzapytanie skalarne zwraca jedną wartość (jeden wiersz, jedną kolumnę) i możesz go użyć wszędzie tam, gdzie oczekiwana jest wartość.
Wewnętrzne SELECT MAX(total) FROM orders zwraca 200. Zapytanie zewnętrzne filtruje potem zamówienia o tej wartości. Przydaje się zawsze, gdy musisz porównać coś z agregatem.
Podzapytania skalarnego możesz też użyć na liście SELECT, żeby dołączyć obliczoną wartość do każdego wiersza:
Dla każdego wiersza customers zapytanie wewnętrzne wykonuje się raz, z podstawionym customers.id. To podzapytanie skorelowane, więcej o nim niżej. W przypadkach typu „jedna liczba na wiersz” LEFT JOIN z GROUP BY zwykle działa szybciej, ale forma skalarna czyta się świetnie.
EXISTS: sprawdź tylko, czy cokolwiek pasuje
EXISTS to spokojniejszy kuzyn IN. Nie interesują go wartości, sprawdza tylko, czy podzapytanie zwraca jakikolwiek wiersz. Zwykle wpisuje się w środku SELECT 1, bo kolumna nie ma znaczenia.
To zapytanie znajduje klientów, którzy złożyli co najmniej jedno zamówienie powyżej 100. Zapytanie wewnętrzne odwołuje się do c.id z zapytania zewnętrznego i właśnie to czyni je skorelowanym. SQLite przestaje skanować tabelę wewnętrzną, gdy tylko znajdzie dopasowanie, dlatego EXISTS często wypada lepiej niż IN przy pytaniach „czy ten wiersz ma powiązany wiersz?”.
Negacja, NOT EXISTS, to bezpieczny względem NULL sposób, żeby zapytać o „brak powiązanego wiersza”:
Podzapytanie w FROM: tabela pochodna
Podzapytanie może stać wszędzie tam, gdzie tabela, także w klauzuli FROM. Zapytanie wewnętrzne staje się tymczasową, nazwaną „tabelą pochodną”, którą możesz łączyć, filtrować albo agregować.
Zapytanie wewnętrzne liczy sumę dla każdego klienta. Zapytanie zewnętrzne uśrednia te sumy dla każdego kraju. Dwuetapowe agregacje tego typu to dokładnie to, do czego służą tabele pochodne: gdy nie da się zrobić wszystkiego jednym GROUP BY.
Alias AS per_customer jest wymagany: każda tabela pochodna musi mieć nazwę.
Podzapytania skorelowane: wykonanie dla każdego wiersza zewnętrznego
Podzapytanie jest skorelowane, gdy odwołuje się do kolumny z zapytania zewnętrznego. SQLite musi obliczać zapytanie wewnętrzne od nowa dla każdego wiersza zewnętrznego, co daje elastyczność, ale może sporo kosztować.
Dla każdego klienta znajdź jego największe zamówienie. Zapytanie wewnętrzne zależy od customers.id, więc wykonuje się raz na klienta. Klienci bez zamówień dostają NULL, i o to właśnie chodzi.
Podzapytania skorelowane naturalnie pasują do zadań typu „dla każdego wiersza z A oblicz coś z B”. Jeśli tabela jest mała albo wyszukiwanie korzysta z indeksu, wszystko jest w porządku. Na dużych tabelach bez odpowiednich indeksów zmierz wydajność przed wdrożeniem: JOIN z GROUP BY jest często szybszy.
Podzapytanie czy JOIN: co wybrać?
Te dwa zapytania odpowiadają na to samo pytanie:
Oba zwracają te same wiersze. Optymalizator SQLite często wewnętrznie przepisuje jedną formę na drugą. Wybieraj na podstawie czytelności:
- Użyj podzapytania, gdy chcesz tylko filtrować i nie chcesz, żeby kolumny z tabeli wewnętrznej zaśmiecały wynik.
- Użyj JOIN, gdy wynik potrzebuje kolumn z obu tabel.
- Użyj EXISTS, gdy pytasz „czy istnieje co najmniej jeden powiązany wiersz?”: to czytelniejsze i omija pułapki
NULLwIN/NOT IN.
W razie wątpliwości napisz wersję, która tłumaczy się sama, gdy przeczytasz ją na głos.
Częsta pułapka: podzapytania zwracające wiele wierszy
Podzapytanie użyte z = musi zwracać najwyżej jeden wiersz. Jeśli zwróci więcej, SQLite wybierze jeden (w praktyce przypadkowy) i dostaniesz po cichu błędne wyniki, bez żadnego błędu.
Użyj IN, gdy zapytanie wewnętrzne może zwrócić wiele wierszy:
Jeśli oczekujesz dokładnie jednego wiersza i chcesz to wymusić, dodaj LIMIT 1 i ORDER BY, żeby wybór był przynajmniej deterministyczny. Lepiej jednak napisać zapytanie tak, żeby pojedynczy wiersz gwarantowały same dane (filtruj po kolumnie unikalnej).
Dalej: Common Table Expressions
Podzapytania w FROM szybko stają się nieporęczne, zwłaszcza gdy potrzebujesz tej samej tabeli pochodnej dwa razy albo zagnieżdżenie sięga trzech poziomów. Common Table Expressions (WITH ... AS (...)) pozwalają nazwać podzapytanie na początku i odwoływać się do niego po nazwie w reszcie instrukcji. O tym jest następna strona.
Najczęściej zadawane pytania
Czym jest podzapytanie w SQLite?
Podzapytanie to instrukcja SELECT zagnieżdżona w innej instrukcji i ujęta w nawiasy. SQLite wykonuje zapytanie wewnętrzne i przekazuje jego wynik do zewnętrznego. Podzapytania mogą pojawiać się w WHERE, FROM, SELECT i kilku innych klauzulach.
Czym różni się IN od EXISTS w SQLite?
IN (SELECT ...) sprawdza, czy wartość pasuje do któregokolwiek wiersza zwróconego przez podzapytanie. EXISTS (SELECT ...) sprawdza tylko, czy podzapytanie zwraca jakikolwiek wiersz, a wartości go nie interesują. EXISTS to zwykle lepszy wybór, gdy zapytanie wewnętrzne odwołuje się do wiersza zewnętrznego (podzapytanie skorelowane).
Użyć podzapytania czy JOIN w SQLite?
Użyj JOIN, gdy w wyniku potrzebujesz kolumn z obu tabel. Użyj podzapytania, gdy chcesz tylko filtrować albo obliczyć jedną wartość. Optymalizator SQLite i tak często przepisuje jedną formę na drugą, więc wybierz tę, która czyta się czytelniej.
Czym jest podzapytanie skorelowane w SQLite?
Podzapytanie skorelowane odwołuje się do kolumny z zapytania zewnętrznego, więc musi być obliczane od nowa dla każdego wiersza zewnętrznego. Jest elastyczne, ale na dużych tabelach bywa wolne. Jeśli takie podzapytanie staje się wąskim gardłem, często pomaga przepisanie go na JOIN albo CTE.