SELECT: достаём нужные данные

CASE: логика прямо в SELECT

22 мин
Чему научишься
  • размечать строки прямо в SELECT через CASE WHEN ... THEN ... ELSE ... END
  • превращать сырые значения из таблицы в понятные метки для отчёта
  • понимать, что условия в CASE проверяются сверху вниз
  • объяснять, что вернёт CASE, если ни один WHEN не сработал
  • отличать поисковую форму CASE от простой формы CASE
  • понимать, почему WHEN NULL в простой форме не срабатывает никогда
  • использовать CASE в ORDER BY, чтобы сортировать строки по бизнес-порядку, а не по алфавиту

Если — то

Настоящий архивист не только читает данные — он размечает их.

В старом хранилище «Котомаркета» лежат сырые факты:

  • цена товара;
  • статус заказа;
  • количество на складе;
  • город пользователя;
  • дата события.

Но человеку часто нужен не просто факт, а понятная метка.

Например, в таблице products есть цена:

nameprice
Игрушка «Мышь-перехватчик»900
Книга «SQL для котонавтов»2500
Домик-портал7200

Сама цена полезна, но в отчёте иногда хочется сразу видеть сегмент:

namepricesegment
Игрушка «Мышь-перехватчик»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 — ярлыки архивиста: каждая строка получает метку по первому сработавшему условию.
price = 4990WHENprice < 1000да'дёшево'нетWHENprice < 5000да'средне'ELSE'дорого'сработало первое истинное WHEN
CASE проверяет ветки сверху вниз: строка получает значение первого сработавшего WHEN, остальное достаётся ELSE.
Разбиваем товары на ценовые сегменты по price — и каталог читается как готовый отчёт.

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 есть статусы:

idstatus
1paid
2pending
3cancelled
4refunded

Для базы такие значения удобны: короткие, стабильные, на английском.

Но в отчёте можно показать русские метки:

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;

Результат:

idstatusstatus_label
1paidоплачен
2pendingожидает оплаты
3cancelledотменён
4refundedвозврат

Такой запрос не меняет 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 'ожидает'

Простая форма читается как таблица соответствий:

statusstatus_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 превращает технические статусы заказов в понятные подписи.

Значения 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;

получится технический порядок по тексту.

Но бизнесу может быть нужен другой порядок:

  1. сначала оплаченные;
  2. потом ожидающие;
  3. потом отменённые;
  4. потом возвраты;
  5. потом всё остальное.

Алфавит не знает этого смысла. Зато его можно задать через 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 возвращает числовой приоритет:

statuspriority
paid1
pending2
cancelled3
refunded4
другое5

ORDER BY сортирует по этому приоритету.

А id в конце нужен для стабильного порядка внутри одинаковых статусов:

ORDER BY CASE ... END, id

Так строки с одинаковым статусом будут отсортированы по id, а не в неопределённом порядке.

Сортируем заказы двух клиентов не по алфавиту статуса, а по бизнес-приоритету: оплаченные выше ожидающих, потом отменённые и возвраты. Выборка маленькая нарочно — так в результате видно, где кончается один статус и начинается следующий.

Можно показать метку и сортировать по той же логике

Иногда удобно одновременно:

  • показать человеку понятную метку;
  • отсортировать строки по бизнес-порядку.

Например:

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 в простой форме.

Вопрос с собеседования

Вопрос с собеседования:
Что делает CASE в SQL?

Сильный ответ:
CASE — это выражение с ветвлением. Оно проверяет условия и возвращает значение для первой подходящей ветки. Его часто используют в SELECT, чтобы добавить в выдачу понятные метки, например сегмент цены или расшифровку статуса. CASE не меняет данные в таблице, а вычисляет значение в результате запроса.


Вопрос с собеседования:
В каком порядке проверяются ветки WHEN?

Сильный ответ:
Ветки проверяются сверху вниз. Срабатывает первый WHEN, условие которого вернуло TRUE. После этого остальные ветки уже не проверяются. Поэтому порядок условий в CASE — часть логики, а не просто оформление.


Вопрос с собеседования:
Что вернёт CASE, если ни один WHEN не сработал и ELSE не написан?

Сильный ответ:
Он вернёт NULL. Если для отчёта нужен понятный запасной вариант, лучше явно добавить ELSE, например ELSE 'другой статус' или ELSE 'без сегмента'.


Вопрос с собеседования:
Чем простой CASE отличается от поискового?

Сильный ответ:
Простой CASE записывается как CASE x WHEN a THEN ... WHEN b THEN ... END и сравнивает одно выражение x со значениями веток через =. Поисковый CASE записывается как CASE WHEN условие THEN ... END, и в каждой ветке можно писать полноценное условие: сравнения, диапазоны, AND/OR, IS NULL и проверки разных столбцов.


Вопрос с собеседования:
Почему ветка WHEN NULL в простом CASE никогда не срабатывает?

Сильный ответ:
Потому что простой CASE сравнивает выражение со значениями веток через =. Ветка WHEN NULL по смыслу превращается в x = NULL, а такое сравнение даёт UNKNOWN, а не TRUE. Ветка WHEN срабатывает только при TRUE. Поэтому NULL проверяют в поисковой форме: WHEN x IS NULL THEN ....


Вопрос с собеседования:
Зачем ставить CASE в ORDER BY?

Сильный ответ:
CASE в ORDER BY используют, когда нужен не алфавитный или числовой порядок, а бизнес-порядок. Например, статусы можно отсортировать так: сначала paid, потом pending, затем cancelled, затем refunded. Для этого каждому статусу через CASE назначают числовой приоритет и сортируют по нему.

Проверь себя
Что вернёт CASE, если ни один WHEN не сработал и ELSE не написан?
Проверь себя
Какая ветка определит результат CASE?
CASE
  WHEN price < 4000 THEN 'не дорогой'
  WHEN price < 1500 THEN 'дешёвый'
  ELSE 'дорогой'
END
Если price = 900.
Проверь себя
Какая форма нужна, чтобы правильно проверить NULL?
Проверь себя
Где можно использовать CASE, чтобы отсортировать статусы в бизнес-порядке?

КВЕРИ: CASE — это не просто «если — то». Это способ объяснить архиву, как человек должен читать сырые факты.

Закрепление: реши задачи
Решено 0 из 3 · для зачёта достаточно 2