CASE expressions
What you'll learn
- label the output with
CASE WHEN ... THEN ... ELSE ... ENDbranches right insideSELECT - tell the searched form of
CASEfrom the simple one and explain whyWHEN NULLin the simple form never fires - sort rows by business order by putting
CASEinsideORDER BY
Если — то
Настоящий архивист не только читает данные — он размечает их.
В старом хранилище «Котомаркета» лежат сырые факты:
- цена товара;
- статус заказа;
- количество на складе;
- город пользователя;
- дата события.
Но человеку часто нужен не просто факт, а понятная метка.
Например, в таблице products есть цена:
| name | price |
|---|---|
| Игрушка «Мышь-перехватчик» | 900 |
| Книга «SQL для котонавтов» | 2500 |
| Домик-портал | 7200 |
Сама цена полезна, но в отчёте иногда хочется сразу видеть сегмент:
| name | price | segment |
|---|---|---|
| Игрушка «Мышь-перехватчик» | 900 | дешёвый |
| Книга «SQL для котонавтов» | 2500 | средний |
| Домик-портал | 7200 | дорогой |
Такую разметку делает CASE.
CASE — это выражение с ветвлением:
CASE
WHEN условие1 THEN значение1
WHEN условие2 THEN значение2
ELSE значение_по_умолчанию
END
Читается почти как обычная инструкция:
если условие 1 верно — верни значение 1;
иначе если условие 2 верно — верни значение 2;
иначе верни значение по умолчанию.
Пример:
SELECT
name,
price,
CASE
WHEN price < 1500 THEN 'дешёвый'
WHEN price < 4000 THEN 'средний'
ELSE 'дорогой'
END AS segment
FROM products;
Здесь CASE создаёт новый вычисляемый столбец segment.
Он не меняет данные в таблице.
Он только добавляет разметку в результат запроса.
КВЕРИ: Разметка — это мнение архивиста, записанное поверх фактов. Вешай ярлыки так, чтобы за них не было стыдно через сто лет.

CASE is the archivist’s tagging: each row gets a label from the first condition that fires.CASE checks its branches top to bottom: a row takes the value of the first WHEN that fires, and everything else falls to ELSE.price — and the catalogue reads like a finished report.| name | price | segment |
|---|---|---|
| Пазл 1000 деталей | 890.00 | дешёвый |
| Футболка базовая | 990.00 | дешёвый |
| Мягкая игрушка кот | 1290.00 | дешёвый |
| SQL для профи | 1290.00 | дешёвый |
| Чистый код | 1490.00 | дешёвый |
| Алгоритмы на практике | 1690.00 | средний |
| Плед флисовый | 1890.00 | средний |
| Конструктор кубики | 1990.00 | средний |
| Настольная лампа | 2190.00 | средний |
| Веб-камера HD | 2790.00 | средний |
| Худи оверсайз | 2990.00 | средний |
| Радиоуправляемая машина | 3290.00 | средний |
| Портативная колонка | 3490.00 | средний |
| Набор посуды | 3590.00 | средний |
| Джинсы слим | 3990.00 | средний |
| Кофеварка капельная | 4290.00 | дорогой |
| Механическая клавиатура | 4590.00 | дорогой |
| Кроссовки беговые | 5490.00 | дорогой |
| Беспроводные наушники | 5990.00 | дорогой |
| Смарт-часы | 8990.00 | дорогой |
CASE проверяет ветки сверху вниз
Самая важная деталь CASE:
срабатывает первый подходящий WHEN.
Как только SQL нашёл ветку, условие которой вернуло TRUE, он берёт значение после THEN и дальше ветки уже не проверяет.
Посмотрим на пример:
CASE
WHEN price < 4000 THEN 'не дорогой'
WHEN price < 1500 THEN 'дешёвый'
ELSE 'дорогой'
END
На первый взгляд кажется, что товар за 900 должен получить метку 'дешёвый'.
Но он её не получит.
Почему?
Для цены 900 первое условие уже верно:
price < 4000
Значит, CASE сразу вернёт:
не дорогой
и до ветки:
WHEN price < 1500 THEN 'дешёвый'
уже не дойдёт.
Поэтому порядок веток — это не оформление. Это часть логики.
Для диапазонов обычно идут от более узкого условия к более широкому:
CASE
WHEN price < 1500 THEN 'дешёвый'
WHEN price < 4000 THEN 'средний'
ELSE 'дорогой'
END
Так товар за 900 попадёт в первую ветку, а товар за 2500 — во вторую.
Что делает ELSE
ELSE — это запасной вариант.
Он срабатывает, если ни один WHEN не подошёл.
Например:
CASE
WHEN price < 1500 THEN 'дешёвый'
WHEN price < 4000 THEN 'средний'
ELSE 'дорогой'
END
Если цена 7200, первые два условия не подходят:
7200 < 1500 -- нет
7200 < 4000 -- нет
Значит, вернётся значение из ELSE:
дорогой
А что будет, если ELSE не написать?
CASE
WHEN price < 1500 THEN 'дешёвый'
WHEN price < 4000 THEN 'средний'
END
Если цена 7200, ни один WHEN не сработает. Запасного варианта нет.
В такой ситуации CASE вернёт NULL.
То есть:
если ни одна ветка не подошла и
ELSEотсутствует, результатом будетNULL.
Для отчётов часто лучше писать явный ELSE, чтобы результат был понятнее человеку:
ELSE 'без сегмента'
или:
ELSE 'дорогой'
Выбор зависит от бизнес-логики.
CASE — это выражение, а не отдельная команда
CASE возвращает значение.
Это значит, что его можно использовать там, где SQL ждёт значение:
- в
SELECT; - в
ORDER BY; - иногда в
WHERE; - внутри вычислений;
- внутри в следующих модулях.
В этом уроке главный сценарий — SELECT.
SELECT
name,
price,
CASE
WHEN price < 1500 THEN 'дешёвый'
WHEN price < 4000 THEN 'средний'
ELSE 'дорогой'
END AS segment
FROM products;
Здесь CASE работает как вычисляемый столбец.
Можно думать о нём так:
для каждой строки посчитай новое значение по условиям
У каждой строки получится свой результат.
У товара за 900 — 'дешёвый'.
У товара за 2500 — 'средний'.
У товара за 7200 — 'дорогой'.
CASE не фильтрует строки сам по себе. Он не похож на WHERE.
WHERE решает:
оставить строку или убрать?
CASE решает:
какое значение показать для этой строки?
CASE для бизнесовых меток
CASE часто используют, чтобы превратить технические значения в понятные человеку подписи.
Например, в таблице orders есть статусы:
| id | status |
|---|---|
| 1 | paid |
| 2 | pending |
| 3 | cancelled |
| 4 | refunded |
Для базы такие значения удобны: короткие, стабильные, на английском.
Но в отчёте можно показать русские метки:
SELECT
id,
status,
CASE
WHEN status = 'paid' THEN 'оплачен'
WHEN status = 'pending' THEN 'ожидает оплаты'
WHEN status = 'cancelled' THEN 'отменён'
WHEN status = 'refunded' THEN 'возврат'
ELSE 'неизвестный статус'
END AS status_label
FROM orders;
Результат:
| id | status | status_label |
|---|---|---|
| 1 | paid | оплачен |
| 2 | pending | ожидает оплаты |
| 3 | cancelled | отменён |
| 4 | refunded | возврат |
Такой запрос не меняет status в таблице.
Он просто добавляет рядом понятную расшифровку.
Это удобно для отчётов, выгрузок, и учебных задач, где результат должен говорить языком человека.
Поисковая форма CASE
Форма, которую мы использовали выше, называется поисковой:
CASE
WHEN условие1 THEN значение1
WHEN условие2 THEN значение2
ELSE значение_по_умолчанию
END
Её называют searched CASE.
В каждой ветке после WHEN можно писать полноценное условие:
WHEN price < 1500 THEN 'дешёвый'
WHEN status = 'paid' THEN 'оплачен'
WHEN stock = 0 THEN 'нет в наличии'
WHEN price < 1500 AND stock > 0 THEN 'дешёвый и есть в наличии'
Поисковая форма гибкая. В ней можно:
- сравнивать разные столбцы;
- использовать
ANDиOR; - проверять
NULLчерезIS NULL; - строить диапазоны;
- описывать сложные бизнес-правила.
Например:
SELECT
name,
price,
stock,
CASE
WHEN stock = 0 THEN 'нет в наличии'
WHEN price < 1500 AND stock > 0 THEN 'дешёвый товар в наличии'
WHEN price >= 1500 AND stock > 0 THEN 'товар в наличии'
ELSE 'проверь данные'
END AS product_note
FROM products;
Здесь каждая ветка — отдельное условие.
Именно поисковая форма чаще всего удобнее для новичков, потому что она прямо показывает логику.
Простая форма CASE
У CASE есть вторая форма — простая.
Она удобна, когда мы сравниваем один и тот же столбец с несколькими точными значениями.
Например, вместо такого запроса:
CASE
WHEN status = 'paid' THEN 'оплачен'
WHEN status = 'pending' THEN 'ожидает'
WHEN status = 'cancelled' THEN 'отменён'
WHEN status = 'refunded' THEN 'возврат'
ELSE 'другой статус'
END
можно написать короче:
CASE status
WHEN 'paid' THEN 'оплачен'
WHEN 'pending' THEN 'ожидает'
WHEN 'cancelled' THEN 'отменён'
WHEN 'refunded' THEN 'возврат'
ELSE 'другой статус'
END
Здесь выражение status написано один раз:
CASE status
А дальше перечислены значения, с которыми его нужно сравнить:
WHEN 'paid' THEN 'оплачен'
WHEN 'pending' THEN 'ожидает'
Простая форма читается как таблица соответствий:
| status | status_label |
|---|---|
| paid | оплачен |
| pending | ожидает |
| cancelled | отменён |
| refunded | возврат |
Она хорошо подходит для простого маппинга:
если значение равно этому — покажи такую подпись.
Простая и поисковая форма: в чём разница
Сравним две формы рядом.
Поисковая форма:
CASE
WHEN status = 'paid' THEN 'оплачен'
WHEN status = 'pending' THEN 'ожидает'
ELSE 'другой статус'
END
Простая форма:
CASE status
WHEN 'paid' THEN 'оплачен'
WHEN 'pending' THEN 'ожидает'
ELSE 'другой статус'
END
Результат может быть одинаковым.
Но логика записи разная.
В простой форме SQL сам сравнивает выражение после CASE со значениями после WHEN:
status = 'paid'
status = 'pending'
В поисковой форме ты сам пишешь условия полностью:
WHEN status = 'paid'
WHEN status = 'pending'
Простая форма короче, когда условия однотипные.
Поисковая форма гибче, когда условия разные.
Например, такую логику простой формой нормально не выразить:
CASE
WHEN status IS NULL THEN 'статус не указан'
WHEN status = 'paid' AND paid_at IS NOT NULL THEN 'оплачен'
WHEN status = 'pending' THEN 'ожидает'
ELSE 'проверь заказ'
END
Здесь есть проверка NULL, проверка другого столбца paid_at и составное условие через AND.
Для таких случаев выбирают поисковую форму.
Практическое правило:
Если нужно сопоставить один столбец с набором точных значений — можно использовать простой CASE.
Если нужны условия, диапазоны, NULL, несколько столбцов или AND/OR — используй поисковый CASE.
Почему WHEN NULL в простом CASE не работает
У простой формы есть важный подводный камень.
Допустим, в orders.status иногда бывает NULL.
Новичок может написать так:
CASE status
WHEN 'paid' THEN 'оплачен'
WHEN NULL THEN 'статус не указан'
ELSE 'другой статус'
END
Кажется, что ветка:
WHEN NULL THEN 'статус не указан'
должна сработать, если status равен NULL.
Но она не сработает никогда.
Почему?
Простая форма CASE status WHEN ... сравнивает status со значениями веток через обычное равенство =.
То есть ветка WHEN NULL по смыслу превращается в:
status = NULL
А из урока про NULL ты уже знаешь:
status = NULL
не даёт TRUE.
Оно даёт UNKNOWN.
А ветка WHEN срабатывает только когда условие даёт TRUE.
Поэтому для проверки NULL нужна поисковая форма:
CASE
WHEN status IS NULL THEN 'статус не указан'
WHEN status = 'paid' THEN 'оплачен'
WHEN status = 'pending' THEN 'ожидает'
ELSE 'другой статус'
END
Здесь проверка написана правильно:
status IS NULL
Главное правило:
NULLвCASEпроверяют черезIS NULL, а значит — поисковой формой.
CASE превращает технические статусы заказов в понятные подписи.| id | status | status_label |
|---|---|---|
| 1 | paid | оплачен |
| 2 | paid | оплачен |
| 3 | cancelled | отменён |
| 4 | paid | оплачен |
| 5 | paid | оплачен |
| 6 | paid | оплачен |
| 7 | pending | ожидает |
| 8 | paid | оплачен |
| 9 | paid | оплачен |
| 10 | paid | оплачен |
| 11 | paid | оплачен |
| 12 | paid | оплачен |
| 13 | pending | ожидает |
| 14 | paid | оплачен |
| 15 | paid | оплачен |
| 16 | paid | оплачен |
| 17 | pending | ожидает |
| 18 | paid | оплачен |
| 19 | paid | оплачен |
| 20 | paid | оплачен |
Значения THEN и ELSE должны быть совместимы
CASE возвращает одно значение.
Но разные ветки могут возвращать разные варианты этого значения.
Например:
CASE
WHEN price < 1500 THEN 'дешёвый'
WHEN price < 4000 THEN 'средний'
ELSE 'дорогой'
END
Все ветки возвращают текст. Это нормально.
'дешёвый'
'средний'
'дорогой'
А вот такой CASE выглядит подозрительно:
CASE
WHEN price < 1500 THEN 'дешёвый'
ELSE 0
END
В одной ветке возвращается текст:
'дешёвый'
В другой — число:
0
Базе нужно решить, какой тип будет у результата CASE: текстовый или числовой. В разных ситуациях это может привести к ошибке или неочевидному приведению типов.
Поэтому хорошая привычка:
ветки
THENиELSEдолжны возвращать значения одного понятного типа.
Если делаешь текстовую метку — возвращай текст во всех ветках:
CASE
WHEN price < 1500 THEN 'дешёвый'
ELSE 'не дешёвый'
END
Если делаешь числовой ранг — возвращай числа во всех ветках:
CASE
WHEN status = 'paid' THEN 1
WHEN status = 'pending' THEN 2
ELSE 3
END
Это особенно важно в ORDER BY, где CASE часто возвращает числовой приоритет.
CASE в ORDER BY: сортировка по бизнес-порядку
Иногда обычная сортировка не подходит.
Например, есть статусы заказов:
paid
pending
cancelled
refunded
Если отсортировать их по алфавиту:
SELECT id, status
FROM orders
ORDER BY status;
получится технический порядок по тексту.
Но бизнесу может быть нужен другой порядок:
- сначала оплаченные;
- потом ожидающие;
- потом отменённые;
- потом возвраты;
- потом всё остальное.
Алфавит не знает этого смысла. Зато его можно задать через CASE:
SELECT id, status
FROM orders
ORDER BY
CASE status
WHEN 'paid' THEN 1
WHEN 'pending' THEN 2
WHEN 'cancelled' THEN 3
WHEN 'refunded' THEN 4
ELSE 5
END,
id;
Здесь CASE возвращает числовой приоритет:
| status | priority |
|---|---|
| paid | 1 |
| pending | 2 |
| cancelled | 3 |
| refunded | 4 |
| другое | 5 |
ORDER BY сортирует по этому приоритету.
А id в конце нужен для стабильного порядка внутри одинаковых статусов:
ORDER BY CASE ... END, id
Так строки с одинаковым статусом будут отсортированы по id, а не в неопределённом порядке.
| id | status |
|---|---|
| 1 | paid |
| 2 | paid |
| 4 | paid |
| 5 | paid |
| 6 | paid |
| 8 | paid |
| 9 | paid |
| 10 | paid |
| 11 | paid |
| 12 | paid |
| 14 | paid |
| 15 | paid |
| 16 | paid |
| 18 | paid |
| 19 | paid |
| 20 | paid |
| 21 | paid |
| 22 | paid |
| 24 | paid |
| 27 | paid |
Можно показать метку и сортировать по той же логике
Иногда удобно одновременно:
- показать человеку понятную метку;
- отсортировать строки по бизнес-порядку.
Например:
SELECT
id,
status,
CASE status
WHEN 'paid' THEN 'оплачен'
WHEN 'pending' THEN 'ожидает'
WHEN 'cancelled' THEN 'отменён'
WHEN 'refunded' THEN 'возврат'
ELSE 'другой статус'
END AS status_label
FROM orders
ORDER BY
CASE status
WHEN 'paid' THEN 1
WHEN 'pending' THEN 2
WHEN 'cancelled' THEN 3
WHEN 'refunded' THEN 4
ELSE 5
END,
id;
В SELECT CASE создаёт текстовую метку:
status_label
В ORDER BY другой CASE создаёт числовой приоритет сортировки.
Почему не сортировать по самой русской метке?
Потому что алфавитный порядок меток снова может не совпасть с бизнес-логикой.
Например, по алфавиту 'возврат' может оказаться раньше 'оплачен', но бизнесу нужно наоборот.
Поэтому для отображения используют текст, а для порядка — числовой ранг.
Главное правило CASE
CASE проверяет ветки сверху вниз и возвращает результат первого WHEN, который дал TRUE.
CASE
WHEN price < 1500 THEN 'дешёвый'
WHEN price < 4000 THEN 'средний'
ELSE 'дорогой'
END
Порядок условий важен.
Если ни один WHEN не сработал и ELSE не написан, результатом будет NULL.
Для проверки NULL используй поисковую форму:
CASE
WHEN status IS NULL THEN 'статус не указан'
ELSE 'статус есть'
END
Не рассчитывай на WHEN NULL в простой форме.
Interview question
Interview question: how does a simple CASE differ from a searched one, and why does the WHEN NULL branch in a simple CASE never fire?
Strong answer: a simple CASE (CASE x WHEN a THEN …) compares one expression against the branch values with =; a searched one (CASE WHEN condition THEN …) takes an arbitrary condition in each branch. WHEN NULL in the simple form expands to x = NULL, which yields , not TRUE — the branch is dead. You check for with the searched form: WHEN x IS NULL THEN …. The searched form can do everything the simple one can, so in doubtful cases you choose it.
CASE, если ни один WHEN не сработал и ELSE не написан?WHEN сработает первым?
CASE
WHEN price < 4000 THEN 'не дорогой'
WHEN price < 1500 THEN 'дешёвый'
ELSE 'дорогой'
END
Если price = 900.NULL?CASE, чтобы отсортировать статусы в бизнес-порядке?КВЕРИ:
CASE— это не просто «если — то». Это способ объяснить архиву, как человек должен читать сырые факты.