CASE в SQL работает как if/else внутри запроса: проверяет условия по порядку и возвращает значение первой подходящей ветки. Общая запись такая: CASE WHEN условие THEN результат … ELSE результат END. Если ни одно условие не выполнилось, а ELSE нет, вернётся NULL.
CASE пишут в SELECT, ORDER BY, GROUP BY, внутри SUM и COUNT, в UPDATE и WHERE. Ниже синтаксис обеих форм, примеры с реальным выводом и ошибки, на которых CASE молча отдаёт не тот результат. Если вы только начинаете, сначала прочитайте, что такое язык SQL и как устроен запрос.
Синтаксис CASE: две формы

У выражения две записи. Поисковая проверяет любые логические условия, простая сравнивает одно выражение со списком значений.
-- поисковый CASE
CASE
WHEN условие_1 THEN результат_1
WHEN условие_2 THEN результат_2
ELSE результат_по_умолчанию
END
-- простой CASE
CASE выражение
WHEN значение_1 THEN результат_1
WHEN значение_2 THEN результат_2
ELSE результат_по_умолчанию
END| Форма | Что проверяет | Когда брать |
|---|---|---|
| Поисковый CASE | любое условие: <, >, BETWEEN, IS NULL, LIKE | диапазоны, NULL, условия по нескольким столбцам |
| Простой CASE | равенство одного выражения значениям из WHEN | перевод кодов в названия: статусы, типы, флаги |
Простая форма, по описанию CASE в документации PostgreSQL, «больше похожа на оператор switch», знакомый по C. Если писали на нём, вот разбор switch case и других управляющих конструкций C.
Тестовая таблица
Все запросы ниже я прогнал в SQLite 3.51 на одной таблице заказов. Вывод скопирован из консоли, ничего не дописано руками. Синтаксис CASE одинаков в PostgreSQL, MySQL, SQL Server и Oracle, различия я отмечу там, где они есть.
CREATE TABLE orders (id INTEGER PRIMARY KEY, city TEXT, amount INTEGER, status TEXT);
INSERT INTO orders VALUES
(1, 'Москва', 1200, 'paid'),
(2, 'Казань', 450, 'paid'),
(3, 'Москва', 8900, 'new'),
(4, 'Пермь', NULL, 'cancelled'),
(5, 'Казань', 3100, 'paid'),
(6, 'Москва', 15000, 'paid');
Поисковый CASE: разбить заказы по сумме
SELECT id, amount,
CASE
WHEN amount < 1000 THEN 'мелкий'
WHEN amount < 5000 THEN 'средний'
WHEN amount >= 5000 THEN 'крупный'
ELSE 'без суммы'
END AS size
FROM orders;
id amount size
-- ------ ---------
1 1200 средний
2 450 мелкий
3 8900 крупный
4 без суммы
5 3100 средний
6 15000 крупный
У заказа 4 сумма NULL. Сравнение с NULL не даёт истины, поэтому ни одна ветка WHEN не сработала и запрос ушёл в ELSE.
Простой CASE: перевести статусы
SELECT id,
CASE status
WHEN 'paid' THEN 'оплачен'
WHEN 'new' THEN 'новый'
ELSE 'отменён'
END AS status_ru
FROM orders;
id status_ru
-- ---------
1 оплачен
2 оплачен
3 новый
4 отменён
5 оплачен
6 оплачен
Как CASE выбирает ветку
Правил три, и на каждом новички хотя бы раз получают странный результат.
- Побеждает первая истинная ветка. Microsoft пишет, что CASE «оценивает условия последовательно и останавливается с первым условием, условие которого удовлетворено» (справка по Transact-SQL). Остальные WHEN уже не проверяются.
- Без ELSE получится NULL. Если ни одно условие не выполнилось и ELSE нет, «результатом выражения будет NULL», так сказано в разделе об условных выражениях PostgreSQL.
- Все ветки возвращают один тип. Документация PostgreSQL требует, чтобы результаты приводились «к одному выходному типу», а SQL Server требует, чтобы они были «одинаковыми или неявно приводимыми друг к другу».
Вот что делает CASE без ELSE: статусы new и cancelled остались пустыми.
SELECT id, status,
CASE status WHEN 'paid' THEN 'оплачен' END AS status_ru
FROM orders;
id status status_ru
-- --------- ---------
1 paid оплачен
2 paid оплачен
3 new
4 cancelled
5 paid оплачен
6 paid оплачен
Пустая ячейка здесь означает NULL. Подробнее о том, чем NULL отличается от пустой строки и нуля, я писал в разборе NULL в SQL.
Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединитьсяГде писать CASE в запросе

CASE можно использовать везде, где допустимо выражение: в SELECT, UPDATE, DELETE, SET, WHERE, ORDER BY и HAVING. Это прямо перечислено в справке Microsoft. Если с порядком частей запроса пока путаница, загляните в разбор команды SELECT от WHERE до JOIN.
CASE внутри COUNT и SUM
Самый частый рабочий приём аналитика: посчитать по группам сразу несколько показателей. Сколько всего заказов в городе, сколько оплачено и на какую сумму:
SELECT city,
COUNT(*) AS orders_total,
COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid_cnt,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid_sum
FROM orders
GROUP BY city
ORDER BY paid_sum DESC;
city orders_total paid_cnt paid_sum
------ ------------ -------- --------
Москва 3 2 16200
Казань 2 2 3550
Пермь 1 0 0
В COUNT ветку ELSE специально не пишем: неподходящие строки дают NULL, а COUNT пропускает NULL. В SUM, наоборот, стоит ELSE 0, иначе у Перми в сумме будет NULL, а не ноль. Больше вариантов подсчёта я собрал в статье про COUNT с условием и по группам.
В PostgreSQL то же самое короче пишется через FILTER: COUNT(*) FILTER (WHERE status = 'paid'). По описанию агрегатных выражений, с FILTER «агрегатной функции подаются только те входные строки», для которых условие истинно. В MySQL и SQL Server FILTER нет, там остаётся CASE.
CASE в ORDER BY: свой порядок сортировки
Нужно показать сначала новые заказы, потом оплаченные, а отменённые в конце. По алфавиту так не отсортировать, а через CASE можно:
SELECT id, status
FROM orders
ORDER BY CASE status
WHEN 'new' THEN 1
WHEN 'paid' THEN 2
ELSE 3
END, id;
id status
-- ---------
3 new
1 paid
2 paid
5 paid
6 paid
4 cancelled
CASE в GROUP BY
SELECT CASE
WHEN amount < 5000 THEN 'до 5000'
ELSE '5000 и больше'
END AS bucket,
COUNT(*) AS cnt
FROM orders
WHERE amount IS NOT NULL
GROUP BY CASE
WHEN amount < 5000 THEN 'до 5000'
ELSE '5000 и больше'
END;
bucket cnt
------------- ---
5000 и больше 2
до 5000 3
Почему выражение написано дважды? В SQL Server выражение GROUP BY «не может содержать псевдоним столбца», заданный в SELECT (справка по GROUP BY). PostgreSQL разрешает ссылаться в GROUP BY на имя выходного столбца (документация SELECT), SQLite тоже принял короткое GROUP BY bucket с тем же выводом. Полная запись работает везде.
CASE в UPDATE
Одним запросом можно поменять разные строки по-разному. Например, дать скидку 10 % на оплаченные заказы из Казани, а остальные не трогать:
UPDATE orders
SET amount = CASE
WHEN city = 'Казань' THEN amount * 0.9
ELSE amount
END
WHERE status = 'paid';
SELECT id, city, amount FROM orders WHERE status = 'paid';
id city amount
-- ------ ------
1 Москва 1200
2 Казань 405
5 Казань 2790
6 Москва 15000
Ветка ELSE amount обязательна. Без неё московские заказы получили бы NULL вместо суммы.
Перед UPDATE на рабочей базе я сначала запускаю тот же CASE внутри SELECT и смотрю, какие значения получатся. Удобнее всего делать это в графическом клиенте, вот как подключить DBeaver к PostgreSQL.
CASE в WHERE
Технически работает: в Москве ищем заказы дороже 5000, в остальных городах дороже 1000.
SELECT id, city, amount
FROM orders
WHERE CASE
WHEN city = 'Москва' THEN amount > 5000
ELSE amount > 1000
END;
Но то же условие читается проще через AND и OR, и вывод у обоих запросов совпал:
SELECT id, city, amount
FROM orders
WHERE (city = 'Москва' AND amount > 5000)
OR (city <> 'Москва' AND amount > 1000);
id city amount
-- ------ ------
3 Москва 8900
5 Казань 3100
6 Москва 15000
Я бы оставлял CASE в WHERE только там, где через AND и OR условие становится нечитаемым: такой фильтр труднее проверить глазами и легко сломать при правке.
Вложенный CASE
Внутри ветки THEN или ELSE можно написать ещё один CASE. Так удобно делить крупные заказы по городу, а остальные не трогать:
SELECT id, amount,
CASE
WHEN amount IS NULL THEN 'нет данных'
WHEN amount >= 5000 THEN
CASE WHEN city = 'Москва' THEN 'крупный, Москва' ELSE 'крупный, регион' END
ELSE 'обычный'
END AS segment
FROM orders;
id amount segment
-- ------ ---------------
1 1200 обычный
2 450 обычный
3 8900 крупный, Москва
4 нет данных
5 3100 обычный
6 15000 крупный, Москва
В SQL Server глубина ограничена: справка Microsoft допускает «только 10 уровней вложения» в CASE. На практике три уровня уже читаются тяжело. Если вложенность растёт, вынесите классификацию в отдельный столбец или справочную таблицу и соедините через JOIN. Как пишется JOIN, показано в разборе SELECT с примерами.
CASE, IF, IIF и COALESCE: что выбрать
CASE есть во всех популярных СУБД: PostgreSQL, MySQL, SQL Server, Oracle, SQLite. Короткие функции удобнее, но часть из них привязывает запрос к одной базе.
| Задача | Стандартный вариант | Короткий вариант |
|---|---|---|
| Одно условие, два исхода | CASE WHEN … THEN … ELSE … END |
IF() в MySQL, IIF() в SQL Server |
| Подставить значение вместо NULL | CASE WHEN x IS NULL THEN 0 ELSE x END |
COALESCE(x, 0) |
| Превратить значение в NULL | CASE WHEN x = 0 THEN NULL ELSE x END |
NULLIF(x, 0) |
| Условный подсчёт | COUNT(CASE WHEN … THEN 1 END) |
COUNT(*) FILTER (WHERE …) в PostgreSQL |
По справке Microsoft, IIF «является быстрым способом написания выражения CASE» и переводится в CASE, поэтому тоже вкладывается «максимум до уровня 10». COALESCE и NULLIF описаны в той же главе документации PostgreSQL, что и CASE. Для MySQL функции IF, IFNULL и NULLIF собраны в разделе Flow Control Functions.
Частые ошибки с CASE и как их найти

Простой CASE не видит NULL
Запрос выглядит логично, но строка с NULL в сумме не попадает в нужную ветку:
SELECT id, amount,
CASE amount WHEN NULL THEN 'нет суммы' ELSE 'есть' END AS wrong,
CASE WHEN amount IS NULL THEN 'нет суммы' ELSE 'есть' END AS right_way
FROM orders;
id amount wrong right_way
-- ------ ----- ---------
1 1200 есть есть
2 450 есть есть
3 8900 есть есть
4 есть нет суммы
5 3100 есть есть
6 15000 есть есть
Простой CASE сравнивает значения через знак равенства, а NULL ничему не равен, даже другому NULL. В документации SQLite это сказано прямо: если базовое выражение NULL, результатом всегда будет ELSE или NULL.
Решение: для NULL берите поисковую форму и IS NULL.
Широкое условие стоит выше узкого
SELECT id, amount,
CASE
WHEN amount > 1000 THEN 'средний'
WHEN amount > 5000 THEN 'крупный'
ELSE 'мелкий'
END AS size
FROM orders
WHERE amount IS NOT NULL;
id amount size
-- ------ -------
1 1200 средний
2 450 мелкий
3 8900 средний
5 3100 средний
6 15000 средний
Заказ на 15 000 попал в «средние»: условие amount > 1000 истинно, до второй ветки дело не дошло. Ошибки СУБД не покажет.
Решение: пишите условия от узкого к широкому или задавайте диапазоны с обеих сторон.
Другие ловушки
- Забыли END. Без него запрос не выполнится. Чаще всего так бывает, когда CASE копируют из середины другого запроса.
- Числа и текст в разных ветках. SQLite такое пропускает:
ELSE amountрядом сTHEN 'нет'выполнился без ошибки. А PostgreSQL и SQL Server требуют общего типа. Приведите ветки сами:CAST(amount AS TEXT)илиCAST(amount AS VARCHAR(20)). - CASE в хранимой процедуре MySQL. Там это отдельная инструкция, а не выражение: она заканчивается на
END CASE, и без ELSE при отсутствии совпадений выдаёт ошибку «Case not found for CASE statement» (справка MySQL). - Деление на ноль внутри ветки. CASE обычно не вычисляет лишние ветки, но PostgreSQL оговаривается, что «железной гарантии» нет. В SQL Server агрегаты из WHEN «вычисляются сначала», поэтому в примере из справки
MAX(1 / value)падает с делением на ноль, хотя ветка выше уже вернула бы 0. Надёжнее защищаться черезNULLIF(делитель, 0).
Вопросы про CASE, NULL и порядок условий регулярно попадают в тестовые задания. Примеры разобраны в статье про собеседование на аналитика данных.
Где освоить SQL системно
CASE закрывает одну задачу: условную логику внутри запроса. Чтобы уверенно собирать отчёты, нужны ещё JOIN, подзапросы, оконные функции и привычка проверять результат на краевых случаях. Если хотите идти в профессию, вот план, как стать SQL-разработчиком.
Сравнить курсы по SQL по цене и программе можно в каталоге курсов SQL. Ниже пять программ, с которых удобно начать сравнение.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Нейросети для рабочих задач Перейти на сайт курса | 31 290 ₽ | - | 1 месяц | Обзор курса | |
| SQL для анализа данных Перейти на сайт курса | 52 353 ₽ | 6203 ₽/мес. | 2 месяца | Обзор курса | |
| SQL-разработчик: тариф PRO Перейти на сайт курса | 89 900 ₽ | 7491 ₽/мес. | 2 месяца | Обзор курса | |
| SQL-разработчик: тариф Базовый Перейти на сайт курса | 79 900 ₽ | 3329 ₽/мес. | 3 месяца | Обзор курса | |
| SQL с нуля | 134 640 ₽ | 5500 ₽/мес. | 12 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по SQL




