Скорее всего, вам уже приходилось сталкиваться с условными выражениями в языках программирования: if-else, switch-case и подобные конструкции. В SQL есть собственный инструмент для работы с условиями: это выражение CASE. Оно позволяет принимать решения прямо в запросах: если условие выполнено, сделать одно, если не выполнено — другое.
Конструкция CASE особенно полезна, когда вам нужно работать с данными, которые могут содержать NULL значения. Синтаксис простой, как шпаргалка, давайте взглянем:
CASE
WHEN условие1 THEN результат1
WHEN условие2 THEN результат2
...
ELSE результат_по_умолчанию
END
Звучит логично, да? "Если дело обстоит вот так, сделай это, иначе сделай другое". Запомнить можно легко: "WHEN — это если, THEN — это что делать, ELSE — это что делать, если ничего не подошло".
Пример: классификация товаров
Представьте, что у нас есть таблица products, в которой есть колонка price, и мы хотим распределить товары по группам в зависимости от их цены.
| id | name | price |
|---|---|---|
| 1 | Magic Wand | 120 |
| 2 | Potion Set | 45 |
| 3 | Crystal Ball | 75 |
| 4 | Enchanted Map | NULL |
| 5 | Broomstick | 99 |
| 6 | Spell Book | 180 |
SELECT
name AS product_name,
price,
CASE
WHEN price IS NULL THEN 'Unknown'
WHEN price < 50 THEN 'Budget'
WHEN price BETWEEN 50 AND 100 THEN 'Standard'
ELSE 'Premium'
END AS price_category
FROM products;
Что здесь происходит?
- Если цена
priceу товара не указанаNULL, мы отображаем категорию как'Unknown'. - Если цена меньше 50, товар считается бюджетным
'Budget'. - Если цена находится в диапазоне от 50 до 100, он попадает в категорию
'Standard'. - Всё остальное — это товары премиум-класса
'Premium'.
Вот результат:
| product_name | price | price_category |
|---|---|---|
| Magic Wand | 120 | Premium |
| Potion Set | 45 | Budget |
| Crystal Ball | 75 | Standard |
| Enchanted Map | NULL | Unknown |
| Broomstick | 99 | Standard |
| Spell Book | 180 | Premium |
SQL, словно волшебник, читает строки таблицы products, применяется к каждой и хитро классифицирует.
Работа с NULL в CASE WHEN
Часто бывает, что в данных есть пропущенные значения (привет, NULL), и их нужно чем-то заменить. Например, у нас есть таблица users с колонкой email, и для пользователей без электронной почты мы хотим отобразить 'Не указано'.
| user_id | name | |
|---|---|---|
| 1 | Alex Lin | alex@example.com |
| 2 | Maria Chi | NULL |
| 3 | Anna Song | anna@magic.com |
| 4 | Otto Art | NULL |
| 5 | John Smith | john@wizard.org |
SELECT
user_id,
name,
CASE
WHEN email IS NULL THEN 'Не указано'
ELSE email
END AS email_address
FROM users;
| user_id | name | email_address |
|---|---|---|
| 1 | Alex Lin | alex@example.com |
| 2 | Maria Chi | Не указано |
| 3 | Anna Song | anna@magic.com |
| 4 | Otto Art | Не указано |
| 5 | John Smith | john@wizard.org |
SQL здесь работает как шаман, оживляя полупустые строки. Если email отсутствует, он заменяет его текстом 'Не указано'. Если email есть — просто оставляет его.
Условные выражения с числами
Иногда требуется не просто замена значений, но и построение новой логики. Например, допустим, у нас есть таблица students со столбцами score (оценка) и name.
| name | score |
|---|---|
| Alex Lin | 95 |
| Maria Chi | 82 |
| Anna Song | 48 |
| Otto Art | NULL |
| John Smith | 67 |
| Zoe Black | 30 |
Мы хотим оценивать студентов как "Успешный", "Нормальный" и "Неуспешный", в зависимости от их баллов.
SELECT
name AS student_name,
score,
CASE
WHEN score IS NULL THEN 'No Score'
WHEN score >= 90 THEN 'Excellent'
WHEN score >= 50 THEN 'Pass'
ELSE 'Fail'
END AS performance_category
FROM students;
| student_name | score | performance_category |
|---|---|---|
| Alex Lin | 95 | Excellent |
| Maria Chi | 82 | Pass |
| Anna Song | 48 | Fail |
| Otto Art | NULL | No Score |
| John Smith | 67 | Pass |
| Zoe Black | 30 | Fail |
SQL любезно распределяет студентов, как строгий экзаменатор: 90 баллов и выше — только "Отлично", от 50 — "Удовлетворительно". А вот если баллов не хватает... ну, вы поняли.
Группировка и обработка NULL
Работа с группами данных — еще одна область, где CASE WHEN блестяще себя показывает. Представьте, что у нас есть таблица orders, и мы хотим посчитать общее количество заказов по стату, включая заказы со статусом NULL.
| order_id | status |
|---|---|
| 1 | Completed |
| 2 | Pending |
| 3 | NULL |
| 4 | Shipped |
| 5 | Completed |
| 6 | NULL |
| 7 | Pending |
| 8 | Completed |
| 9 | Shipped |
| 10 | NULL |
SELECT
CASE
WHEN status IS NULL THEN 'No Status'
ELSE status
END AS order_status,
COUNT(*)
FROM orders
GROUP BY
CASE
WHEN status IS NULL THEN 'No Status'
ELSE status
END;
Этот запрос аккуратно обрабатывает пустые статусы NULL и подставляет 'No Status' вместо них, а также подсчитывает общее количество заказов в каждой группе. Вот результат:
| order_status | count |
|---|---|
| Completed | 3 |
| Pending | 2 |
| Shipped | 2 |
| No Status | 3 |
Практические кейсы: "Фокусы" с CASE WHEN
Пример 1: Сортировка с учетом NULL
Иногда NULL значения необходимо отображать либо первыми, либо последними в отсортированном списке. Это часто используется, например, в списках задач, где приоритетные задачи должны отображаться выше остальных, а задачи без приоритета (с NULL) должны быть в конце.
| task_id | task_name | priority |
|---|---|---|
| 1 | Fix bugs | 1 |
| 2 | Update documentation | 3 |
| 3 | Plan sprint | NULL |
| 4 | Code review | 2 |
| 5 | Organize meeting | NULL |
| 6 | Deploy release | 1 |
SELECT
task_name,
priority,
CASE
WHEN priority IS NULL THEN 1
ELSE 0
END AS priority_sort
FROM tasks
ORDER BY priority_sort ASC, priority ASC;
Здесь мы добавили "виртуальную" колонку priority_sort, которая расставляет NULL значения ниже всех, а остальные сортирует по возрастанию.
| task_name | priority | priority_sort |
|---|---|---|
| Deploy release | 1 | 0 |
| Fix bugs | 1 | 0 |
| Code review | 2 | 0 |
| Update documentation | 3 | 0 |
| Plan sprint | NULL | 1 |
| Organize meeting | NULL | 1 |
Пример 2: Вычисления с учетом NULL
А теперь представьте, что мы подсчитываем итоговую сумму заказа в таблице orders, где в колонке discount скидка может быть NULL, если её нет.
| order_id | total_price | discount |
|---|---|---|
| 101 | 100 | 10 |
| 102 | 200 | NULL |
| 103 | 150 | 15 |
| 104 | 120 | NULL |
| 105 | 80 | 5 |
Нам нужно заменить NULL на 0, чтобы расчет не ломался.
SELECT
order_id,
total_price,
discount,
total_price -
CASE
WHEN discount IS NULL THEN 0
ELSE discount
END AS final_price
FROM orders;
| order_id | total_price | discount | final_price |
|---|---|---|---|
| 101 | 100 | 10 | 90 |
| 102 | 200 | NULL | 200 |
| 103 | 150 | 15 | 135 |
| 104 | 120 | NULL | 120 |
| 105 | 80 | 5 | 75 |
Этот волшебный трюк с CASE позволяет нам быть уверенными, что NULL не нарушит математические расчеты.
Для orderid = 101: discount = 10. finalprice = 100 - 10 = 90. Для orderid = 102: discount = NULL. CASE возвращает 0. finalprice = 200 - 0 = 200. Для orderid = 103: discount = 15. finalprice = 150 - 15 = 135. Для orderid = 104: discount = NULL. CASE возвращает 0. finalprice = 120 - 0 = 120. Для orderid = 105: discount = 5. finalprice = 80 - 5 = 75.
Пример 3: Отображение пользовательских статусов
В повседневной работе встречается задача показать статус пользователя (например, "Активен" или "В ожидании") или указать на отсутствие данных. Например, в таблице users есть колонка last_login, где хранится дата последнего входа.
| user_id | name | last_login |
|---|---|---|
| 1 | Alex Lin | 2024-12-10 |
| 2 | Maria Chi | 2025-04-20 |
| 3 | Anna Song | NULL |
| 4 | Otto Art | 2025-05-01 |
| 5 | Liam Park | 2025-05-25 |
SELECT
user_id,
name,
CASE
WHEN last_login IS NULL THEN 'Never Logged In'
WHEN last_login < CURRENT_DATE - INTERVAL '30 days' THEN 'Inactive'
ELSE 'Active'
END AS user_status
FROM users;
С этим запросом система админа оживает: пользователей без входа называют "Never Logged In", а тех, кто давно не заходил, считают "Inactive". Остальные активны!
| user_id | name | user_status |
|---|---|---|
| 1 | Alex Lin | Inactive |
| 2 | Maria Chi | Inactive |
| 3 | Anna Song | Never Logged In |
| 4 | Otto Art | Inactive |
| 5 | Liam Park | Active |
Распространённые ошибки и как их избежать
Пропуск ELSE: Если вы не добавите ELSE, SQL просто вернёт NULL, если ни одно из условий не выполнено. Это не всегда то, что вы хотите. Поэтому лучше явно указывать ELSE, даже если вы уверены, что учли все случаи.
CASE
WHEN condition THEN result
-- ELSE 'Default value' -- не забудьте!
END
Сложные условия без скобок: Если у вас несколько сложных условий с AND, OR или NOT, всегда используйте скобки. Без них ваш запрос может начать "думать" неправильно.
CASE
WHEN (column1 IS NOT NULL AND column2 > 5) THEN 'Valid'
ELSE 'Invalid'
END
Работа с NULL: Помните, что NULL никогда не равно (=) чему-либо. Например:
CASE
WHEN column = NULL THEN 'Nope!' -- Ошибка!
WHEN column IS NULL THEN 'Correct!' -- Вот это правильно.
END
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ