Вітаю на новій лекції по SQL! Сьогодні познайомимось з найнепомітнішими, але дико потужними операторами — EXISTS і NOT EXISTS. Уяви собі розвідника, який не залишає слідів, але миттєво каже: "Так, об'єкт існує" або "Ні, тут порожньо". Ці оператори напряму не повертають дані, але дають можливість робити логічно точні перевірки у запитах.
Почнемо з бази. EXISTS — це оператор, який перевіряє існування записів у результаті виконання підзапиту. Якщо підзапит повертає хоча б один рядок, умова EXISTS поверне TRUE, інакше — FALSE.
SELECT 1
WHERE EXISTS (
SELECT *
FROM students
WHERE grade > 3.5
);
Як бачиш, нас не цікавлять самі дані підзапиту, а лише факт, що такі рядки існують. Якщо хоча б один запис підходить під умову, запит повертає 1.
Синтаксис EXISTS
Синтаксис EXISTS виглядає просто:
SELECT колонки
FROM таблиця
WHERE EXISTS (
SELECT 1
FROM інша_таблиця
WHERE умова
);
Пояснення:
- Вкладений підзапит всередині
EXISTSможе бути будь-яким запитом. - Саме результат підзапиту визначає, повернеться
TRUEчиFALSE.
Приклад: Чи є студенти з оцінкою вище 4?
Уявімо таблицю students:
| id | name | grade |
|---|---|---|
| 1 | Otto | 3.2 |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | 2.9 |
Припустимо, ми хочемо перевірити, чи існують студенти з оцінкою вище 4. Використаємо такий запит:
SELECT 'Є студенти з високим балом!'
WHERE EXISTS (
SELECT 1
FROM students
WHERE grade > 4
);
Результат:
Є студенти з високим балом!
Чому EXISTS працює швидше, ніж IN?
Головна фішка EXISTS у тому, що він зупиняє виконання підзапиту, як тільки знаходить перший збіг. Тобто, якщо у нас є умова, яка перевіряє існування даних, EXISTS може бути дуже ефективним.
Наприклад, уяви, що в таблиці students мільйони записів, але ми шукаємо лише одне існуюче співпадіння (grade > 4). Як тільки SQL знайде перший рядок, що підходить під умову, запит завершиться.
Використання NOT EXISTS
Тепер давай поговоримо про NOT EXISTS. Цей оператор працює як антипод EXISTS. Він повертає TRUE, якщо підзапит не повертає жодного запису.
Приклад: знайти студентів без оцінок (NULL)
Припустимо, у нашій таблиці є студенти, у яких ще немає оцінок:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | NULL |
Ми хочемо вибрати всіх студентів з відсутніми оцінками. Використаємо NOT EXISTS:
SELECT *
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM students
WHERE grade IS NOT NULL
AND id = s.id
);
Результат:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 4 | Lina | NULL |
Порівняння EXISTS і IN
Іноді здається, що EXISTS і IN роблять одне й те саме. На перший погляд — так, але є нюанси. Особливо якщо десь поруч затесався NULL. Тоді поведінка IN може бути неочікуваною, а EXISTS — рятівником.
Давай подивимось на прикладі.
Таблиця courses (курси, які можна пройти):
| course_id | name |
|---|---|
| 1 | Математика |
| 2 | Історія |
А ось і студенти:
| student_id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
Таблиця enrollments (записи про те, хто на який курс записався):
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
Ми хочемо вибрати назви курсів, на які хтось записаний. Здається, просто.
З використанням IN:
SELECT name
FROM courses
WHERE course_id IN (
SELECT course_id
FROM enrollments
);
На перший погляд все має спрацювати. Але якщо в enrollments є NULL у courseid, як у Maria Chi, IN може повернути... нічого! Бо NULL робить підзапит "невизначеним", і SQL губиться: раптом NULL — це саме той courseid, який ми шукаємо?
З використанням EXISTS:
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
);
А от EXISTS перевіряє: "Є хоча б один рядок, де course_id співпадає?" — і все. Його не бентежить, якщо десь поруч NULL, бо він шукає конкретні співпадіння, а не список значень.
Висновок: якщо в підзапиті може бути NULL, краще юзати EXISTS, щоб не отримати сюрпризів.
Приклади реальних задач
Таблиця students:
| id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
Таблиця enrollments:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
Приклад 1. Студенти, записані на курси
Знайдемо тих, хто вже десь засвітився — записався на курс (навіть якщо дивно, як Maria Chi):
SELECT name
FROM students s
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
Результат:
Alex Lin
Anna Song
Maria Chi
Якщо студент хоч якось фігурує в enrollments — він потрапляє у вибірку, навіть якщо його course_id незрозумілий.
Приклад 2. Студенти без курсів
А тепер знайдемо тих, хто поки що просто існує в системі — але нікуди не записаний:
SELECT name
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
Результат:
Dan Seth
Shadow Moon
Схоже, ці двоє поки не знайшли курс до душі. Або просто забули, що треба записуватись :)
Приклад 3. Вибірка курсів з більш ніж 5 зареєстрованими студентами
Таблиця courses:
| course_id | name |
|---|---|
| 1 | Математика |
| 2 | Історія |
| 3 | Біологія |
| 4 | Філософія |
Таблиця enrollments:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 7 | 2 |
| 8 | 2 |
| 9 | 2 |
| 10 | NULL |
Ми хочемо знайти такі курси, на які записалось більше п’яти студентів. Тут EXISTS ніби питає: "А є у цього курсу хоча б одна група записів, де студентів більше п’яти?"
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
GROUP BY e.course_id
HAVING COUNT(*) > 5
);
Результат:
Математика
Тільки на курс "Математика" (course_id = 1) записано шість студентів. Інші курси поки не такі популярні.
Часті помилки при використанні EXISTS і NOT EXISTS
- Неправильне розуміння синтаксису підзапиту. Завжди перевіряй, що підзапит коректно посилається на зовнішню таблицю.
- Забута перевірка
NULL. Навіть якщо юзаєшEXISTS, іноді треба явно вказати обробкуNULL. - Відсутність індексу на полях підзапиту. Це може сильно загальмувати виконання запиту.
На цьому все на сьогодні! Тепер ти знаєш, як використовувати EXISTS і NOT EXISTS для перевірки існування даних, а також різницю між цими операторами і IN. У наступній лекції продовжимо занурення у підзапити, розглянемо їх використання у SELECT для роботи з агрегованими даними.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ