SQLCASEtutorial

Что такое CASE WHEN в SQL? Условная логика для начинающих

CASE WHEN — это «if/else внутри SQL». Простыми словами: как поставить условие прямо в SELECT, разница между searched CASE и simple CASE, бакетирование чисел в категории, pivot одной формулой и условные агрегаты. С таблицами и частыми ошибками.

4 мин чтенияСправочникSQL · CASE · tutorial

CASE WHEN — это «if/else внутри SQL-запроса». Способ поставить условие прямо в SELECT, UPDATE или WHERE и получить одно значение из нескольких возможных в зависимости от состояния строки.

В обычном языке программирования условную логику пишут как if score >= 90: 'A' elif score >= 70: 'B' else: 'C'. В SQL то же самое выражается через CASE. Это базовый инструмент для отчётов, бакетирования, расчёта тиров.

Зачем нужен CASE WHEN

Без CASE пришлось бы вытаскивать сырые данные и обрабатывать их в коде приложения. Это медленнее (передаём больше данных по сети) и грязнее (логика размазана между SQL и кодом).

Типичные сценарии:

  • Бакеты: «возраст: до 18 / 18-25 / 25-40 / 40+».
  • Тиры: «по сумме покупок: bronze / silver / gold / platinum».
  • Группировка статусов: «pending и processing — это 'open', paid и shipped — 'closed'».
  • Условный счёт: «сколько мужчин, сколько женщин в одной строке отчёта».

Базовый синтаксис

Есть две формы — searched CASE и simple CASE. Searched используется чаще.

Searched CASE

CASE
  WHEN условие1 THEN значение1
  WHEN условие2 THEN значение2
  ...
  ELSE значение_по_умолчанию
END

Каждый WHEN — отдельное условие. Postgres проверяет их по порядку, возвращает значение первого истинного. ELSE — если ни одно не подошло (опционально, без него — NULL).

SELECT
  name,
  score,
  CASE
    WHEN score >= 90 THEN 'A'
    WHEN score >= 70 THEN 'B'
    WHEN score >= 50 THEN 'C'
    ELSE 'F'
  END AS grade
FROM exams;

Simple CASE

CASE column
  WHEN значение1 THEN ...
  WHEN значение2 THEN ...
  ELSE ...
END

Короче, когда сравниваем одну колонку с разными значениями:

SELECT
  id,
  status,
  CASE status
    WHEN 'pending'   THEN 'ожидает'
    WHEN 'paid'      THEN 'оплачен'
    WHEN 'shipped'   THEN 'отправлен'
    WHEN 'cancelled' THEN 'отменён'
    ELSE status
  END AS status_ru
FROM orders;

В таких случаях simple-форма читается чище. Но как только нужны диапазоны или сложные условия — переходи на searched.

Пример с таблицей

Таблица users:

id name total_spent
1 Аня 250
2 Боб 50
3 Вера 12000
4 Гриша 1500

Запрос с CASE:

SELECT
  name,
  total_spent,
  CASE
    WHEN total_spent >= 10000 THEN 'platinum'
    WHEN total_spent >= 1000  THEN 'gold'
    WHEN total_spent >= 100   THEN 'silver'
    ELSE 'bronze'
  END AS tier
FROM users;

Результат:

name total_spent tier
Аня 250 silver
Боб 50 bronze
Вера 12000 platinum
Гриша 1500 gold

Каждой строке присвоен тир по правилам. CASE смотрит условия сверху вниз — первое истинное «выигрывает».

Условный счёт (pivot одной формулой)

Один из самых мощных трюков — SUM(CASE WHEN ... THEN 1 ELSE 0 END) или COUNT(*) FILTER (WHERE ...). Это «считай, если выполняется условие»:

SELECT
  country,
  COUNT(*) AS total_users,
  SUM(CASE WHEN tier = 'free' THEN 1 ELSE 0 END) AS free_users,
  SUM(CASE WHEN tier = 'gold' THEN 1 ELSE 0 END) AS gold_users
FROM users
GROUP BY country;

Получаешь «по странам, всего сколько и сколько в каждом тиере» в одной таблице. В Postgres есть короткая форма через FILTER:

SELECT
  country,
  COUNT(*) AS total_users,
  COUNT(*) FILTER (WHERE tier = 'free') AS free_users,
  COUNT(*) FILTER (WHERE tier = 'gold') AS gold_users
FROM users
GROUP BY country;

Та же логика, чище. FILTER есть в стандарте SQL, но не во всех БД (MySQL до 8.0 — нет).

CASE в WHERE и ORDER BY

CASE можно использовать где угодно — не только в SELECT:

-- Сложная сортировка: paid сначала, потом pending, потом всё остальное
SELECT *
FROM orders
ORDER BY
  CASE status
    WHEN 'paid'    THEN 1
    WHEN 'pending' THEN 2
    ELSE 3
  END,
  created_at DESC;

Или в WHERE:

-- Динамическая выборка в зависимости от роли
SELECT *
FROM orders
WHERE
  CASE
    WHEN $role = 'admin' THEN TRUE
    WHEN $role = 'user'  THEN customer_id = $user_id
  END;

CASE с агрегатами в одной строке

Когда нужно несколько условных сумм в одной выборке:

SELECT
  CASE
    WHEN tier = 'gold' OR tier = 'platinum' THEN 'paid'
    ELSE 'free'
  END AS plan,
  COUNT(*) AS users
FROM users
GROUP BY CASE WHEN tier = 'gold' OR tier = 'platinum' THEN 'paid' ELSE 'free' END;

Группировка по выражению CASE. Длинно, но иногда нужно.

Частые ошибки новичков

1. Забыл END. CASE WHEN ... THEN ... без END — синтаксическая ошибка. Всегда заканчивай END.

2. Порядок WHEN имеет значение. CASE идёт сверху вниз и берёт первое истинное условие. Если у тебя WHEN score > 50 THEN 'C' стоит выше чем WHEN score > 70 THEN 'B', то всем со score 80 присвоится 'C', потому что первое условие сработает раньше.

3. Несовместимые типы в THEN. CASE WHEN x > 10 THEN 'big' ELSE 0 END — Postgres попробует привести 'big' к числу или 0 к тексту. В Postgres получишь либо ошибку каста, либо неожиданный результат. Все ветки CASE должны возвращать совместимые типы.

4. Без ELSE — может оказаться NULL. Если ни одно условие не сработало и нет ELSE, CASE вернёт NULL. На отчётах это выглядит как «дырка». Привычка: всегда иметь ELSE, даже если кажется что все случаи покрыты.

5. CASE в WHERE без боли — но не везде нужен. Иногда новички пишут WHERE CASE WHEN x > 10 THEN TRUE ELSE FALSE END вместо просто WHERE x > 10. Это работает, но избыточно. CASE в WHERE имеет смысл, когда логика реально ветвится по нескольким переменным.

6. NULL в CASE. CASE WHEN col = NULL THEN ... END — никогда не сработает. NULL не сравнивается через =. Используй WHEN col IS NULL THEN ....

Мини-резюме

  • CASE WHEN ... THEN ... ELSE ... END — if/else внутри SQL.
  • Две формы: searched (CASE WHEN условие THEN ...) и simple (CASE column WHEN значение THEN ...).
  • Условия проверяются по порядку, побеждает первое истинное.
  • Без ELSE и при никаком истинном условии — результат NULL.
  • Все ветви должны возвращать совместимые типы.
  • Для подсчёта по условию — COUNT(*) FILTER (WHERE ...) или SUM(CASE WHEN ... THEN 1 ELSE 0 END).
  • Сравнение с NULL — только через IS NULL, не = NULL.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр