Praca z podzapytaniami to trochę jak gra w szachy na rozbieranie: wszystko wydaje się proste na pierwszy rzut oka, dopóki nie zrobisz ruchu z błędem. A skąd się biorą błędy? Z niezrozumienia składni, ignorowania specyfiki logiki SQL albo po prostu z braku uwagi. W tym wykładzie pogadamy o najczęstszych błędach i o tym, jak ich unikać.
Błędy składniowe
Podzapytania wymagają uważnego podejścia do składni. Pominięte przecinki, nawiasy albo aliasy mogą całkowicie rozwalić twoje zapytanie. Zobaczmy kilka typowych problemów.
Pominięte nawiasy
Nawiasy mają kluczowe znaczenie przy używaniu podzapytań. Podzapytanie musi być w nawiasach okrągłych, a brak nawet jednej pary może wywołać błąd składni.
Przykład błędu:
SELECT student_name
FROM students
WHERE student_id IN SELECT student_id FROM enrollments);
Błąd:
ERROR: syntax error at or near "SELECT"
Poprawka:
SELECT student_name
FROM students
WHERE student_id IN (SELECT student_id FROM enrollments);
Komentarz: Zapytanie w IN zawsze musi być w nawiasach, żeby SQL wiedział, że chcesz wykonać podzapytanie.
Brak aliasów
Kiedy używasz podzapytań w sekcji FROM, nie zapomnij nadać im aliasu. Bez tego PostgreSQL po prostu się pogubi.
Przykład błędu:
SELECT student_name, avg_score
FROM (SELECT student_id, AVG(score) AS avg_score FROM grades GROUP BY student_id)
WHERE avg_score > 80;
Błąd:
ERROR: subquery in FROM must have an alias
Poprawka:
SELECT student_name, avg_score
FROM (SELECT student_id, AVG(score) AS avg_score FROM grades GROUP BY student_id) AS subquery
WHERE avg_score > 80;
Komentarz: PostgreSQL wymaga, żeby każda tymczasowa tabela (wynik podzapytania w FROM) miała nazwę.
Problemy z wydajnością
Podzapytania, szczególnie źle zoptymalizowane, mogą zamienić twoją bazę danych w smętnie działający mechanizm. Wydajność cierpi najczęściej przez zbędne obliczenia albo brak indeksów.
Zbędne obliczenia
Podzapytania w sekcji SELECT mogą być liczone dla każdego wiersza wyniku, co prowadzi do ogromnych strat czasu.
Przykład:
SELECT student_name,
(SELECT COUNT(*) FROM enrollments WHERE enrollments.student_id = students.student_id) AS course_count
FROM students;
Jeśli w tabeli students są dziesiątki tysięcy wierszy, to podzapytanie będzie wykonywane dla każdego wiersza od nowa.
Optymalizacja:
WITH course_counts AS (
SELECT student_id, COUNT(*) AS course_count
FROM enrollments
GROUP BY student_id
)
SELECT s.student_name, c.course_count
FROM students s
LEFT JOIN course_counts c ON s.student_id = c.student_id;
CTE (Common Table Expression) albo joiny pozwalają uniknąć przeliczania dla każdego wiersza. Do tego podejścia wrócimy za parę poziomów :P
Brak indeksów
Jeśli używasz złożonych podzapytań w sekcji WHERE, upewnij się, że potrzebne kolumny są zindeksowane.
Przykład:
SELECT student_name
FROM students
WHERE student_id IN (SELECT student_id FROM enrollments WHERE course_id = 10);
Jeśli na kolumnie student_id w tabeli enrollments nie ma indeksu, podzapytanie będzie wykonywane przez pełne skanowanie tabeli.
Optymalizacja: Stwórz indeks:
CREATE INDEX idx_enrollments_course_id ON enrollments (course_id);
Tak często słyszysz o indeksach, że już na pewno się zastanawiasz, co to takiego. Ale poczekaj jeszcze kilka poziomów. Indeksy to ważna rzecz, ale raczej służą do przyspieszania zapytań bez zmiany ich kodu. Nie zrobią złego zapytania dobrym, raczej pomogą ci przyspieszyć zapytania na produkcji. Zwłaszcza jeśli masz tam tabele na miliony wierszy.
Błędy logiczne
Błędy logiczne w podzapytaniach zdarzają się równie często jak składniowe. Problemy mogą wynikać z nieprawidłowego rozumienia działania NULL, filtrów albo funkcji agregujących.
Nieprawidłowe użycie NULL
NULL to pułapka, która czyha na każdego początkującego. Przy użyciu IN albo NOT IN w podzapytaniach obecność NULL może wpłynąć na wynik.
Przykład błędu:
SELECT student_name
FROM students
WHERE student_id NOT IN (SELECT student_id FROM enrollments);
Jeśli w tabeli enrollments są wiersze z student_id = NULL, zapytanie nic nie zwróci. To dlatego, że warunek NOT IN działa jak NULL IS NOT IN.
Poprawka:
SELECT student_name
FROM students
WHERE student_id NOT IN (SELECT student_id FROM enrollments WHERE student_id IS NOT NULL);
Zawsze filtruj NULL, jeśli używasz NOT IN.
Błędy w warunkach filtrowania
Często podzapytanie jest używane do złożonego filtrowania, ale błędy w warunkach mogą sprawić, że wynik będzie zupełnie inny niż się spodziewasz.
Przykład błędu:
SELECT student_name
FROM students
WHERE (SELECT AVG(score) FROM grades WHERE grades.student_id = students.student_id) > 80;
Jeśli choć jeden student nie ma ocen, podzapytanie zwróci NULL i ten student nie pojawi się w wyniku.
Poprawka:
SELECT student_name
FROM students
WHERE COALESCE((SELECT AVG(score) FROM grades WHERE grades.student_id = students.student_id), 0) > 80;
Używaj COALESCE, żeby zamienić NULL na wartości domyślne.
Wskazówki jak unikać błędów
Żeby uniknąć typowych błędów przy pracy z podzapytaniami, trzymaj się tych zasad:
Poprawne użycie nawiasów i aliasów. Jeśli coś nie działa, sprawdź czy wszystkie nawiasy są zamknięte i czy aliasy są podane w podzapytaniach.
Optymalizacja zapytań. Staraj się minimalizować użycie podzapytań tam, gdzie można użyć JOIN, WITH albo indeksów.
Uwaga na NULL. Zawsze miej na uwadze możliwość pojawienia się NULL w podzapytaniach i używaj IS NOT NULL, COALESCE albo podobnych konstrukcji.
Testowanie. Testuj każde podzapytanie osobno, żeby mieć pewność, że zwraca to, co trzeba.
Czytelność. Używaj wcięć i aliasów, żeby twój kod był czytelny. Pamiętaj: za miesiąc sam możesz nie pamiętać, co napisałeś.
GO TO FULL VERSION