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:
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.
CASE WHEN— это «if/else внутри SQL-запроса». Способ поставить условие прямо вSELECT,UPDATEилиWHEREи получить одно значение из нескольких возможных в зависимости от состояния строки.В обычном языке программирования условную логику пишут как
if score >= 90: 'A' elif score >= 70: 'B' else: 'C'. В SQL то же самое выражается черезCASE. Это базовый инструмент для отчётов, бакетирования, расчёта тиров.Зачем нужен CASE WHEN
Без
CASEпришлось бы вытаскивать сырые данные и обрабатывать их в коде приложения. Это медленнее (передаём больше данных по сети) и грязнее (логика размазана между SQL и кодом).Типичные сценарии:
'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:Запрос с
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;Результат:
Каждой строке присвоен тир по правилам.
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.CASE WHEN условие THEN ...) и simple (CASE column WHEN значение THEN ...).ELSEи при никаком истинном условии — результатNULL.COUNT(*) FILTER (WHERE ...)илиSUM(CASE WHEN ... THEN 1 ELSE 0 END).IS NULL, не= NULL.