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 users

Searched 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 '5002000'
        WHEN amount < 10000 THEN '200010000'
        ELSE '10000+'
    END AS amount_bucket
FROM orders

CASE с агрегатными функциями

Это ключевой паттерн для аналитика. 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 week

COUNT игнорирует 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 '1824'
        WHEN age < 35 THEN '2534'
        WHEN age < 45 THEN '3544'
        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 '1824'
        WHEN age < 35 THEN '2534'
        WHEN age < 45 THEN '3544'
        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 -- в примерах вопросов по всем темам.