CASE WHEN в SQL: шпаргалка
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Синтаксис
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
ELSE default_result
ENDВторой вариант (simple CASE):
CASE column
WHEN value1 THEN result1
WHEN value2 THEN result2
ELSE default
END1. Категоризация числовой переменной
SELECT
user_id,
age,
CASE
WHEN age < 18 THEN 'teen'
WHEN age < 35 THEN 'young'
WHEN age < 55 THEN 'middle'
ELSE 'senior'
END AS age_group
FROM users;2. Бинарный флаг
SELECT
order_id,
total,
CASE
WHEN total > 10000 THEN 1
ELSE 0
END AS is_big_order
FROM orders;3. CASE в агрегате (очень частый паттерн)
Посчитать процент пользователей с большими заказами:
SELECT
AVG(CASE WHEN total > 10000 THEN 1.0 ELSE 0 END) AS big_order_share,
SUM(CASE WHEN total > 10000 THEN total ELSE 0 END) AS big_order_revenue
FROM orders;4. Pivot через CASE (когда нет PIVOT функции)
SELECT
DATE_TRUNC('day', created_at) AS day,
SUM(CASE WHEN status = 'paid' THEN total ELSE 0 END) AS paid_revenue,
SUM(CASE WHEN status = 'refunded' THEN total ELSE 0 END) AS refunded_revenue,
COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid_orders
FROM orders
GROUP BY 1;5. CASE в WHERE / HAVING
Можно, но обычно лучше через AND / OR:
-- так работает, но не оптимально
SELECT * FROM users
WHERE CASE WHEN country = 'RU' THEN age > 18 ELSE age > 21 END;
-- лучше:
SELECT * FROM users
WHERE (country = 'RU' AND age > 18)
OR (country != 'RU' AND age > 21);6. CASE в ORDER BY
Сортировка с приоритетом категорий:
SELECT * FROM orders
ORDER BY
CASE status
WHEN 'pending' THEN 1
WHEN 'paid' THEN 2
WHEN 'refunded' THEN 3
ELSE 4
END,
created_at DESC;7. CASE с несколькими условиями
SELECT
user_id,
CASE
WHEN age < 18 AND country = 'RU' THEN 'RU_teen'
WHEN age < 18 THEN 'other_teen'
WHEN age >= 18 AND is_premium THEN 'adult_premium'
ELSE 'adult_free'
END AS segment
FROM users;8. CASE с подзапросом
SELECT
user_id,
total,
CASE
WHEN total > (SELECT AVG(total) FROM orders) THEN 'above_avg'
ELSE 'below_avg'
END AS vs_average
FROM orders;9. NULL в CASE
Важно: NULL = NULL → NULL (не TRUE). Используйте IS NULL:
-- неправильно
CASE WHEN column = NULL THEN 'empty' ELSE 'has_value' END -- всегда вернёт 'has_value'
-- правильно
CASE WHEN column IS NULL THEN 'empty' ELSE 'has_value' END10. CASE vs COALESCE vs NULLIF
| Задача | Оптимально |
|---|---|
| Заменить NULL на значение | COALESCE(col, default) |
| Вернуть первое ненулевое из списка | COALESCE(a, b, c) |
| Вернуть NULL при совпадении | NULLIF(col, value) |
| Сложная ветвистая логика | CASE WHEN |
Частые ошибки
Забытый ELSE
Без ELSE вернётся NULL:
-- вернёт NULL для всех orders, где total < 100
CASE WHEN total > 100 THEN 'big' ENDЭто часто нормально, но иногда ломает логику — особенно в агрегатах.
Порядок WHEN
Проверяются сверху вниз, срабатывает первое совпадение:
-- все пользователи 10 лет попадут в 'teen', в 'child' не заглянет
CASE
WHEN age < 18 THEN 'teen'
WHEN age < 13 THEN 'child' -- никогда не сработает
ENDНесогласованные типы в THEN
Все ветки должны возвращать совместимые типы:
-- ошибка
CASE WHEN condition THEN 'text' ELSE 123 ENDНужно либо всё строка, либо всё число, либо CAST:
CASE WHEN condition THEN 'text' ELSE CAST(123 AS TEXT) ENDCASE в оконных функциях
Можно использовать CASE внутри окна:
SELECT
order_id,
user_id,
SUM(CASE WHEN status = 'paid' THEN total ELSE 0 END)
OVER (PARTITION BY user_id) AS user_paid_total
FROM orders;CASE в JOIN
Условное соединение:
SELECT *
FROM orders o
JOIN pricing p ON
CASE
WHEN o.country = 'RU' THEN p.country = 'RU'
ELSE p.country = 'default'
END;Производительность
- CASE быстрый, не является узким местом
- В агрегатах
SUM(CASE WHEN ...)часто эффективнее, чемFILTER, но в Postgres — сравнимо - В WHERE лучше избегать, если есть эквивалент через
AND/OR— планировщик лучше оптимизирует простые условия
Связанные темы
FAQ
Можно ли вложить CASE в CASE?
Можно, но читаемость страдает. Обычно лучше вынести во внешний SELECT или CTE.
Чем CASE отличается от IIF?
IIF(cond, a, b) — краткая форма для простого if-else. Работает не во всех СУБД. CASE — универсальный.
CASE или PIVOT?
Если СУБД поддерживает PIVOT (MSSQL, Oracle) — для чистого поворота таблицы удобнее. В Postgres, MySQL, ClickHouse — обычно CASE.
Можно ли CASE в GROUP BY?
Да, группировать по CASE-выражению — обычный приём:
SELECT
CASE WHEN age < 18 THEN 'teen' ELSE 'adult' END AS grp,
COUNT(*)
FROM users
GROUP BY 1;Тренируйте SQL на реальных задачах — откройте тренажёр с 1500+ вопросами для собеседований аналитиков.