
CASE в SQL: как проверить порядок условий при разметке заказов


При разметке заказов через CASE ошибка часто появляется не в формуле условия, а в порядке ветвей. Один и тот же заказ может одновременно быть дорогим, крупным по количеству позиций и уже возвращенным. SQL в таком случае не выбирает «самую подходящую» метку: он берет первую истинную ветвь. Поэтому проверка должна отвечать на один практический вопрос: не перехватывает ли более широкое условие заказ раньше, чем до него дойдет нужное правило.
Ниже разберем это на одном условном наборе заказов в среде PostgreSQL. Фокус будет не на обзоре синтаксиса, а на том, как заранее увидеть пересечения правил, правильно расставить ветви и убедиться, что граничные заказы размечаются так, как вы договорились с командой.
Что именно нужно проверить в CASE
В PostgreSQL форма CASE WHEN ... THEN ... проверяет условия последовательно. Как только находится первая истинная ветвь, результат уже определен, а оставшиеся ветви дальше не рассматриваются. Из этого следует важное практическое правило: порядок строк внутри CASE — это не оформление, а часть логики классификации.
Вторая проверка касается ELSE. Если его не написать, то все заказы, которые не подошли ни под одно условие, получат NULL. Иногда это полезно для поиска «забытых» случаев, но для рабочей разметки чаще безопаснее явно задать остаточную метку, например 'regular'. Тогда вы не теряете строки в последующей группировке и сразу видите, сколько заказов ушло в базовую категорию.
Третья проверка — тип результата. Все ветви THEN и ELSE должны приводиться к одному типу. В разметке заказов удобнее всего возвращать однородные текстовые метки, а не смешивать текст, числа и NULL без явного замысла.
Условия учебной классификации
Учебный пример. Допустим, команда согласовала такую приоритетную разметку заказов:
refunded, если у заказа есть дата возврата.priority_wholesale, если сумма не меньше 10000 и в заказе не меньше 3 позиций.high_value, если сумма не меньше 10000.bulk, если позиций не меньше 3.regularдля остальных заказов.
Здесь сразу видно, что правила пересекаются. Заказ на 12000 с четырьмя позициями подходит и под priority_wholesale, и под high_value, и под bulk. Заказ с возвратом подходит под категорию возврата независимо от суммы и количества позиций. Значит, нам нужна не просто формула, а именно очередь приоритетов.
WITH orders(order_id, amount, items_count, refunded_at) AS (
VALUES
(101, 9500, 1, NULL::date),
(102, 10000, 1, NULL::date),
(103, 7000, 3, NULL::date),
(104, 12000, 4, NULL::date),
(105, 15000, 5, DATE '2026-10-01'),
(106, 10000, 3, NULL::date),
(107, 8000, 2, DATE '2026-10-02'),
(108, 9999, 3, NULL::date),
(109, 10000, 3, DATE '2026-10-03')
)
SELECT *
FROM orders
ORDER BY order_id;
В этом наборе есть и граничные значения, и пересечения. Заказ 102 проверяет порог amount = 10000, заказ 103 — порог items_count = 3, заказ 106 — обе границы сразу, а заказ 109 добавляет к ним еще и возврат. Такой маленький набор удобнее для проверки, чем сразу вся рабочая таблица.
Где правила пересекаются и почему это ломает разметку
Самая частая ошибка — сначала написать широкие условия, а потом более узкие. Например, если поставить amount >= 10000 раньше, чем сочетание amount >= 10000 AND items_count >= 3, то комбинированная категория вообще не сработает: до нее выполнение просто не дойдет.
WITH orders(order_id, amount, items_count, refunded_at) AS (
VALUES
(101, 9500, 1, NULL::date),
(102, 10000, 1, NULL::date),
(103, 7000, 3, NULL::date),
(104, 12000, 4, NULL::date),
(105, 15000, 5, DATE '2026-10-01'),
(106, 10000, 3, NULL::date),
(107, 8000, 2, DATE '2026-10-02'),
(108, 9999, 3, NULL::date),
(109, 10000, 3, DATE '2026-10-03')
)
SELECT
order_id,
CASE
WHEN amount >= 10000 THEN 'high_value'
WHEN items_count >= 3 THEN 'bulk'
WHEN amount >= 10000 AND items_count >= 3 THEN 'priority_wholesale'
WHEN refunded_at IS NOT NULL THEN 'refunded'
END AS wrong_label
FROM orders
ORDER BY order_id;
У этого варианта сразу несколько проблем. Заказы 104 и 106 получат high_value, хотя по договоренности должны стать priority_wholesale. Заказы 105 и 109 тоже уйдут в high_value, хотя возврат должен иметь больший приоритет. Заказ 101 не подходит ни под одно условие и останется с NULL, потому что ELSE пропущен.
| Заказ | Почему пересекается | Неверная метка | Ожидаемая метка |
|---|---|---|---|
| 104 | Сумма ≥ 10000 и позиций ≥ 3 | high_value | priority_wholesale |
| 105 | Возврат и высокая сумма | high_value | refunded |
| 106 | Обе границы одновременно | high_value | priority_wholesale |
| 109 | Возврат, высокая сумма, много позиций | high_value | refunded |
| 101 | Не подошел ни под одно правило | NULL | regular |
Эта таблица полезнее длинного списка правил, потому что показывает конфликтные строки по именам. Если вы не можете привести 3–5 конкретных пересекающихся заказов, проверка порядка почти наверняка еще не закончена.
Как расставить ветви и зачем нужен ELSE
Рабочий порядок обычно выглядит так: сначала исключения, затем самые узкие составные правила, затем более общие признаки, и в конце остаточная категория. Для нашего случая это означает: сначала возвраты, потом сочетание высокой суммы и большого количества позиций, потом отдельные одиночные признаки.
WITH orders(order_id, amount, items_count, refunded_at) AS (
VALUES
(101, 9500, 1, NULL::date),
(102, 10000, 1, NULL::date),
(103, 7000, 3, NULL::date),
(104, 12000, 4, NULL::date),
(105, 15000, 5, DATE '2026-10-01'),
(106, 10000, 3, NULL::date),
(107, 8000, 2, DATE '2026-10-02'),
(108, 9999, 3, NULL::date),
(109, 10000, 3, DATE '2026-10-03')
)
SELECT
order_id,
CASE
WHEN refunded_at IS NOT NULL THEN 'refunded'
WHEN amount >= 10000 AND items_count >= 3 THEN 'priority_wholesale'
WHEN amount >= 10000 THEN 'high_value'
WHEN items_count >= 3 THEN 'bulk'
ELSE 'regular'
END AS label
FROM orders
ORDER BY order_id;
Теперь каждая пересекающаяся строка останавливается на правильной ветви. Заказ 109 не дойдет до денежных и количественных правил, потому что метка refunded определяется раньше. Заказ 106 не потеряется в high_value, потому что более узкая комбинация стоит выше. А заказ 101 получает явную базовую метку regular, а не пустое значение.
Проверяемый итог по нашему примеру такой: regular = 1, high_value = 1, bulk = 2, priority_wholesale = 2, refunded = 3. Сумма категорий равна 9 заказам, то есть ни одна строка не выпала из разметки.
Как проверить граничные заказы до запуска на всей таблице
Самая надежная техника — заранее собрать маленькую таблицу ожидаемых результатов и сравнить ее с фактической разметкой. Тогда вы проверяете не «кажется, логика правильная», а вполне конкретное соответствие строк договоренности. Для граничных заказов это особенно важно: ровно 10000 и ровно 3 позиции часто ведут себя не так, как предполагалось на словах.
WITH orders(order_id, amount, items_count, refunded_at) AS (
VALUES
(101, 9500, 1, NULL::date),
(102, 10000, 1, NULL::date),
(103, 7000, 3, NULL::date),
(104, 12000, 4, NULL::date),
(105, 15000, 5, DATE '2026-10-01'),
(106, 10000, 3, NULL::date),
(107, 8000, 2, DATE '2026-10-02'),
(108, 9999, 3, NULL::date),
(109, 10000, 3, DATE '2026-10-03')
), expected(order_id, expected_label) AS (
VALUES
(101, 'regular'),
(102, 'high_value'),
(103, 'bulk'),
(104, 'priority_wholesale'),
(105, 'refunded'),
(106, 'priority_wholesale'),
(107, 'refunded'),
(108, 'bulk'),
(109, 'refunded')
), actual AS (
SELECT
order_id,
CASE
WHEN refunded_at IS NOT NULL THEN 'refunded'
WHEN amount >= 10000 AND items_count >= 3 THEN 'priority_wholesale'
WHEN amount >= 10000 THEN 'high_value'
WHEN items_count >= 3 THEN 'bulk'
ELSE 'regular'
END AS actual_label
FROM orders
)
SELECT e.order_id, e.expected_label, a.actual_label
FROM expected e
JOIN actual a USING (order_id)
WHERE e.expected_label <> a.actual_label;
Если запрос вернул ноль строк, на этом наборе логика совпадает с договоренностью. Это не доказывает, что на больших данных не будет новых случаев, но уже защищает от типовой ошибки с порядком ветвей. После этого полезно отдельно посмотреть только на конфликтные и граничные заказы: с возвратом, с суммой около порога и с количеством позиций около порога.
Практический результат такого подхода в том, что вы проверяете разметку до отчета и до дашборда. Вместо спора «почему CASE странно посчитал» у вас появляется короткий список контрольных строк, на которых видно, какая именно ветвь сработала раньше времени.
Если хотите потренировать похожие задачи, во внутренней карточке каталога SQL для анализа данных сохранена программа с темами по фильтрации строк, комбинированию условий и использованию CASE. На официальной странице школы эти темы тоже перечислены, а для самостоятельной практики полезно отдельно проверить, есть ли в заданиях не только написание запроса, но и явная сверка результата на граничных строках.





















