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
END

1. Категоризация числовой переменной

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' END

10. 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) END

CASE в оконных функциях

Можно использовать 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+ вопросами для собеседований аналитиков.