CASE WHEN в SQL — полный гайд с примерами
Коротко
CASE WHEN -- условный оператор в SQL, аналог if/else. Позволяет создавать новые столбцы на основе условий: сегментировать пользователей, считать условные метрики, управлять сортировкой. На собеседовании по SQL CASE встречается почти в каждой задаче -- без него невозможно ни разбить пользователей на когорты, ни посчитать конверсию одним запросом.
Синтаксис: простой CASE и searched CASE
В SQL есть две формы CASE. Обе возвращают значение и могут использоваться везде, где допускается выражение: SELECT, WHERE, GROUP BY, ORDER BY, HAVING.
Простой CASE -- сравнивает одно выражение с набором значений:
SELECT
user_id,
CASE status
WHEN 'active' THEN 'Активен'
WHEN 'churned' THEN 'Отток'
WHEN 'paused' THEN 'Пауза'
ELSE 'Неизвестно'
END AS status_label
FROM usersSearched CASE -- проверяет произвольные условия. Это форма, которую вы будете использовать в 90% случаев:
SELECT
user_id,
CASE
WHEN lifetime_revenue >= 50000 THEN 'VIP'
WHEN lifetime_revenue >= 10000 THEN 'Средний'
ELSE 'Новичок'
END AS segment
FROM usersУсловия проверяются сверху вниз. Как только одно срабатывает -- остальные пропускаются. Порядок условий важен: если поставить >= 10000 первым, VIP-пользователи попадут в категорию "Средний".
CASE в SELECT: сегментация и бакеты
Самый частый сценарий -- разбить непрерывную метрику на категории.
Сегментация пользователей по активности:
SELECT
user_id,
CASE
WHEN last_active_at >= CURRENT_DATE - INTERVAL '7 days' THEN 'active'
WHEN last_active_at >= CURRENT_DATE - INTERVAL '30 days' THEN 'dormant'
ELSE 'churned'
END AS activity_segment,
COUNT(*) OVER () AS total
FROM usersБакеты по сумме заказа:
SELECT
order_id,
amount,
CASE
WHEN amount < 500 THEN 'до 500'
WHEN amount < 2000 THEN '500–2000'
WHEN amount < 10000 THEN '2000–10000'
ELSE '10000+'
END AS amount_bucket
FROM ordersCASE с агрегатными функциями
Это ключевой паттерн для аналитика. CASE внутри агрегатной функции позволяет считать условные метрики в одном запросе -- без подзапросов и джойнов.
Условный COUNT -- конверсия по шагам воронки:
SELECT
DATE_TRUNC('week', created_at) AS week,
COUNT(*) AS registrations,
COUNT(CASE WHEN completed_onboarding THEN 1 END) AS onboarded,
COUNT(CASE WHEN first_order_at IS NOT NULL THEN 1 END) AS buyers
FROM users
WHERE created_at >= '2025-01-01'
GROUP BY DATE_TRUNC('week', created_at)
ORDER BY weekCOUNT игнорирует NULL, поэтому CASE без ELSE возвращает NULL для несовпавших строк -- и они не считаются. ELSE здесь не нужен.
Условный SUM -- выручка по каналам в одной строке:
SELECT
DATE_TRUNC('month', order_date) AS month,
SUM(amount) AS total_revenue,
SUM(CASE WHEN channel = 'organic' THEN amount ELSE 0 END) AS organic_revenue,
SUM(CASE WHEN channel = 'paid' THEN amount ELSE 0 END) AS paid_revenue
FROM orders
GROUP BY DATE_TRUNC('month', order_date)ELSE 0 в SUM — хорошая практика для читаемости. Технически SUM игнорирует NULL, но явный ELSE 0 делает намерение понятным.
CASE в GROUP BY и ORDER BY
GROUP BY с CASE -- группировка по вычисляемому сегменту:
SELECT
CASE
WHEN age < 25 THEN '18–24'
WHEN age < 35 THEN '25–34'
WHEN age < 45 THEN '35–44'
ELSE '45+'
END AS age_group,
COUNT(*) AS user_count,
ROUND(AVG(lifetime_revenue), 2) AS avg_ltv
FROM users
GROUP BY
CASE
WHEN age < 25 THEN '18–24'
WHEN age < 35 THEN '25–34'
WHEN age < 45 THEN '35–44'
ELSE '45+'
END
ORDER BY user_count DESCВыражение CASE нужно продублировать в GROUP BY -- алиас из SELECT в стандартном SQL здесь недоступен (PostgreSQL допускает, MySQL -- тоже, но лучше не полагаться).
ORDER BY с CASE -- кастомный порядок сортировки:
SELECT topic, question_count
FROM topics
ORDER BY
CASE topic
WHEN 'SQL' THEN 1
WHEN 'Python' THEN 2
WHEN 'A/B-тесты' THEN 3
WHEN 'Продуктовая аналитика' THEN 4
ELSE 99
ENDВложенный CASE
CASE можно вкладывать друг в друга, но злоупотреблять этим не стоит -- читаемость страдает. Одного уровня вложенности обычно достаточно.
SELECT
user_id,
CASE
WHEN subscription_type = 'premium' THEN
CASE
WHEN lifetime_revenue > 50000 THEN 'Premium VIP'
ELSE 'Premium'
END
WHEN subscription_type = 'free' THEN 'Free'
ELSE 'Unknown'
END AS user_tier
FROM usersЕсли вложенность растёт дальше -- вынесите логику в CTE или создайте справочную таблицу.
CASE vs COALESCE vs IF
| Конструкция | Что делает | Когда использовать |
|---|---|---|
| CASE WHEN | Произвольные условия, несколько веток | Сегментация, бакеты, условные агрегаты |
| COALESCE | Возвращает первое не-NULL значение | Подставить значение по умолчанию вместо NULL |
| IF (MySQL) | Условие с двумя ветками | Простая бинарная логика (только MySQL) |
COALESCE -- частный случай. COALESCE(x, 0) эквивалентен CASE WHEN x IS NOT NULL THEN x ELSE 0 END, но короче. Если нужна проверка на NULL -- используйте COALESCE, а не CASE.
IF() существует в MySQL и ClickHouse. В PostgreSQL и стандартном SQL его нет -- используйте CASE.
Типичные ошибки
Забыли ELSE. Без ELSE несовпавшие строки получают NULL. Для COUNT(CASE...) это нормально и даже нужно. Для SELECT с выводом на дашборд -- скорее всего баг. Всегда задавайте себе вопрос: «Что будет, если ни одно условие не сработает?».
Неправильный порядок условий. Условия проверяются сверху вниз. WHEN revenue >= 10000 перед WHEN revenue >= 50000 поглотит VIP-пользователей. Ставьте более строгие условия первыми.
NULL в сравнении. CASE WHEN status = 'active' не поймает строки с status = NULL, потому что NULL = 'active' даёт NULL (не TRUE). Если NULL -- отдельная категория, добавьте WHEN status IS NULL THEN ... перед остальными условиями.
Дублирование CASE в GROUP BY. Длинное выражение CASE приходится копировать в GROUP BY. Это некрасиво, но в стандартном SQL неизбежно. Альтернатива -- вынести CASE в подзапрос или CTE, а группировать уже по алиасу.
Вопросы с собеседований
-- Чем отличается простой CASE от searched CASE?
-- Простой CASE сравнивает одно выражение с набором значений (CASE x WHEN 1 THEN ...). Searched CASE проверяет произвольные условия (CASE WHEN x > 10 AND y = 'a' THEN ...). Searched -- более гибкий, простой -- короче для проверки на равенство.
-- Как посчитать конверсию из регистрации в покупку одним запросом?
-- COUNT(CASE WHEN first_order_at IS NOT NULL THEN 1 END) * 100.0 / COUNT(*). CASE без ELSE возвращает NULL для тех, кто не купил, и COUNT их не считает.
-- Что вернёт CASE, если ни одно условие не сработало и ELSE не указан? -- NULL. Это стандартное поведение SQL. Для агрегатных функций это обычно не проблема (NULL игнорируется), но в SELECT может привести к неожиданным пустым значениям.
-- Можно ли использовать CASE в WHERE?
-- Да, но обычно не нужно. WHERE CASE WHEN ... THEN 1 ELSE 0 END = 1 работает, но проще переписать через обычные условия с AND/OR. CASE в WHERE полезен в редких случаях -- например, когда логика фильтрации зависит от параметра.
-- Как посчитать долю мобильных пользователей за каждый день?
-- SELECT date, ROUND(100.0 * COUNT(CASE WHEN platform = 'mobile' THEN 1 END) / COUNT(*), 1) AS mobile_pct FROM events GROUP BY date. Классический паттерн условного COUNT.
Потренируйтесь решать задачи — откройте тренажёр с 1500+ вопросами для подготовки к собеседованиям аналитиков.
FAQ
Можно ли использовать CASE внутри CASE?
Да, вложенные CASE допустимы. Но если уровней больше двух -- код становится нечитаемым. Лучше вынести промежуточные вычисления в CTE или использовать справочную таблицу с JOIN.
Влияет ли CASE на производительность запроса?
В абсолютном большинстве случаев -- нет. CASE вычисляется построчно и работает быстро. Проблемы могут возникнуть, если CASE стоит в WHERE и мешает использовать индекс: WHERE CASE WHEN ... END = 'x' не индексируется. Если фильтрация по вычисляемому значению нужна часто -- создайте вычисляемый столбец или используйте обычные условия.
Чем заменить CASE для работы с NULL?
Используйте COALESCE. COALESCE(city, 'Не указан') -- короче и понятнее, чем CASE WHEN city IS NULL THEN 'Не указан' ELSE city END. Для более сложной логики с NULL (несколько условий, разные значения по умолчанию) -- CASE остаётся лучшим вариантом.
Поддерживается ли CASE во всех СУБД?
Да. CASE WHEN -- часть стандарта SQL и работает одинаково в PostgreSQL, MySQL, ClickHouse, BigQuery, SQL Server, Oracle и других СУБД. Это одна из самых переносимых конструкций SQL.
Как тренироваться
CASE WHEN нужно довести до автоматизма -- на собеседовании его используют в каждой второй задаче по SQL. В тренажёре Карьерник есть вопросы на условные агрегаты, сегментацию и бакеты -- с разборами. Тренируйтесь по 15 минут в день в Telegram, и через неделю CASE перестанет вызывать затруднения.
Больше вопросов по SQL -- в примерах вопросов по всем темам.