Робота з підзапитами — це як грати в шахи на роздягання: все здається простим на перший погляд, поки не зробиш хід з помилкою. А звідки беруться помилки? Через нерозуміння синтаксису, ігнорування особливостей логіки SQL або просто неуважність. У цій лекції поговоримо про найпоширеніші помилки, а також про те, як їх уникати.
Синтаксичні помилки
Підзапити вимагають уважного ставлення до синтаксису. Пропущені коми, дужки чи alias-и можуть повністю завалити твій запит. Давай розглянемо кілька типових проблем.
Пропущені дужки
Дужки грають ключову роль при використанні підзапитів. Підзапит має бути в круглих дужках, і відсутність навіть однієї пари може викликати синтаксичну помилку.
Приклад помилки:
SELECT student_name
FROM students
WHERE student_id IN SELECT student_id FROM enrollments);
Помилка:
ERROR: syntax error at or near "SELECT"
Виправлення:
SELECT student_name
FROM students
WHERE student_id IN (SELECT student_id FROM enrollments);
Коментар: Запит у IN завжди має бути в дужках, щоб SQL зрозумів, що ти хочеш виконати підзапит.
Відсутність alias-ів
Коли ти використовуєш підзапити в секції FROM, не забудь дати їм alias. Без нього PostgreSQL просто заплутається.
Приклад помилки:
SELECT student_name, avg_score
FROM (SELECT student_id, AVG(score) AS avg_score FROM grades GROUP BY student_id)
WHERE avg_score > 80;
Помилка:
ERROR: subquery in FROM must have an alias
Виправлення:
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;
Коментар: PostgreSQL вимагає, щоб кожна тимчасова таблиця (результат підзапиту у FROM) мала ім'я.
Проблеми з продуктивністю
Підзапити, особливо погано оптимізовані, можуть перетворити твою базу даних на сумно працюючий механізм. Продуктивність страждає найчастіше через зайві обчислення або відсутність індексів.
Зайві обчислення
Підзапити в секції SELECT можуть обчислюватися для кожного рядка результату, що веде до величезних витрат часу.
Приклад:
SELECT student_name,
(SELECT COUNT(*) FROM enrollments WHERE enrollments.student_id = students.student_id) AS course_count
FROM students;
Якщо в таблиці students десятки тисяч рядків, цей підзапит буде виконуватись для кожного рядка заново.
Оптимізація:
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) або join-и дозволяють уникнути перерахунку для кожного рядка. До цього підходу ми ще повернемось через пару рівнів :P
Відсутність індексів
Якщо ти використовуєш складні підзапити в секції WHERE, переконайся, що потрібні стовпці проіндексовані.
Приклад:
SELECT student_name
FROM students
WHERE student_id IN (SELECT student_id FROM enrollments WHERE course_id = 10);
Якщо на стовпці student_id у таблиці enrollments немає індексу, підзапит буде виконуватись через повне сканування таблиці.
Оптимізація: Створи індекс:
CREATE INDEX idx_enrollments_course_id ON enrollments (course_id);
Ти так часто чуєш про індекси, що вже точно зацікавився, що це таке. Але зачекай ще кілька рівнів. Індекси — річ важлива, але вони скоріше стосуються способу прискорити роботу запитів, не змінюючи код запитів. Вони не зроблять поганий запит хорошим, скоріше допоможуть прискорити запити на production. Особливо якщо там таблиці на мільйони рядків.
Логічні помилки
Логічні помилки в підзапитах трапляються не рідше синтаксичних. Проблеми можуть виникати через неправильне розуміння роботи NULL, фільтрів або агрегатних функцій.
Неправильне використання NULL
NULL — це пастка, яка чекає на кожного новачка. При використанні IN або NOT IN у підзапитах наявність NULL може вплинути на результат.
Приклад помилки:
SELECT student_name
FROM students
WHERE student_id NOT IN (SELECT student_id FROM enrollments);
Якщо в таблиці enrollments є рядки з student_id = NULL, запит нічого не поверне. Це пов'язано з тим, що умова NOT IN спрацьовує як NULL IS NOT IN.
Виправлення:
SELECT student_name
FROM students
WHERE student_id NOT IN (SELECT student_id FROM enrollments WHERE student_id IS NOT NULL);
Завжди фільтруй NULL, якщо використовуєш NOT IN.
Помилки в умовах фільтрації
Часто підзапит використовується для складної фільтрації, але помилки в умовах можуть призвести до того, що результат буде зовсім не таким, як ти очікував.
Приклад помилки:
SELECT student_name
FROM students
WHERE (SELECT AVG(score) FROM grades WHERE grades.student_id = students.student_id) > 80;
Якщо хоча б у одного студента немає оцінок, підзапит поверне NULL, і студент не потрапить у результат.
Виправлення:
SELECT student_name
FROM students
WHERE COALESCE((SELECT AVG(score) FROM grades WHERE grades.student_id = students.student_id), 0) > 80;
Використовуй COALESCE, щоб замінити NULL значеннями за замовчуванням.
Рекомендації для уникнення помилок
Щоб уникати типових помилок при роботі з підзапитами, дотримуйся таких правил:
Правильне використання дужок і alias-ів. Якщо щось не працює, перевір, чи всі дужки закриті і чи вказані alias-и у підзапитах.
Оптимізація запитів. Намагайся мінімізувати використання підзапитів там, де можна застосувати JOIN, WITH або індекси.
Урахування NULL. Завжди враховуй можливість появи NULL у підзапитах і використовуй IS NOT NULL, COALESCE або схожі конструкції.
Тестування. Тестуй кожен підзапит окремо, щоб переконатися, що він повертає потрібний результат.
Читабельність. Використовуй відступи і alias-и, щоб твій код був читабельним. Пам'ятай: через місяць ти сам можеш не згадати, що написав.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ