SQL

БЛОК 4. Сложные запросы — 14. Сложная фильтрация и CASE

📚 22 вопросовПройти тест →
Лекция

БЛОК 4. Сложные запросы — 14. Сложная фильтрация и CASE

SQL

14. Сложная фильтрация и CASE

🧭 Введение: когда простой WHERE уже не спасает

Пока условия простые, хватает WHERE status = 'paid'.
Но в боевых задачах фильтры быстро усложняются: разные статусы, даты, исключения, приоритеты, сегменты.
Здесь нужны вложенные логические конструкции и CASE WHEN, чтобы превратить бизнес-правила в понятный SQL.
Именно на этом этапе чаще всего появляются тихие баги из-за приоритета операторов и неочевидной логики.
💡 Совет: Перед написанием запроса формулируйте правило человеческим языком, а потом разбивайте на отдельные логические блоки в SQL.
Вывод: Сложная фильтрация — это не «хитрый синтаксис», а точный перенос бизнес-логики в запрос.

⚠️ Проблема -> решение

Типичная проблема:
  1. условия пишутся «в одну строку»,
  2. теряется контроль приоритета AND/OR/NOT,
  3. CASE используется без ELSE, и часть строк получает неожиданный NULL.
Последствия:
  1. сегменты пользователей считаются неверно,
  2. API возвращает неожиданные категории,
  3. ревью и поддержка запроса становятся дорогими.
Решение:
  1. явно группировать условия скобками,
  2. проверять приоритет операторов и граничные кейсы,
  3. строить вычисляемые поля через CASE с явным ELSE.
🟢 Если совсем просто: Сначала делим логику на блоки, потом пишем SQL и проверяем поведение на крайних примерах.
🎯 Как понять, что этап прошёл успешно: Вы можете объяснить, почему каждая строка попала в результат и какую ветку CASE она прошла.

🛠️ Чем помогает и как работает

Сложная фильтрация нужна почти в каждом production-проекте:
  • правила скидок и приоритетов заказов;
  • сегментация пользователей для CRM;
  • риск-оценка транзакций;
  • классификация событий в отчётах.
🟢 Если совсем просто: WHERE отбирает строки, CASE присваивает им категории и вычисляемые значения.
🎯 Как понять, что этап прошёл успешно: Вы умеете разделить логику на фильтрацию, классификацию и вычисляемые поля.
Чем помогает:
  • вложенные условия выражают сложные бизнес-правила;
  • правильный приоритет операторов предотвращает скрытые ошибки;
  • CASE создаёт прозрачные категориальные поля;
  • вычисляемые поля упрощают API и дашборды.
Как это работает:
  • Шаг 1: фиксируем логические группы условий.
  • Шаг 2: ставим скобки для контроля приоритета.
  • Шаг 3: добавляем CASE для классификации.
  • Шаг 4: валидируем ELSE и пограничные случаи.
  • Шаг 5: проверяем читаемость и производительность.
Вывод: Качество сложного SQL определяется ясной логикой, а не длиной запроса.

📚 Ключевые термины (простыми словами)

Перед практикой синхронизируем словарь.
🟢 Если совсем просто: Термины ниже отвечают на вопрос «как не потерять смысл в длинном условии».
🎯 Как понять, что этап прошёл успешно: Вы понимаете, где фильтрация, где классификация, и где риск логической ошибки.
  • Nested conditions (вложенные условия) — логические блоки с несколькими AND/OR/NOT.
  • Operator precedence (приоритет операторов) — порядок вычисления NOT -> AND -> OR.
  • CASE WHEN — условная конструкция для вычисления категории/значения.
  • Computed field (вычисляемое поле) — поле, которое считается в запросе, а не хранится в таблице.
  • Short-circuit thinking — подход, где условия проверяются от более критичных к менее критичным.
  • Boolean expression — логическое выражение, возвращающее TRUE/FALSE/UNKNOWN.
  • Default branch (ELSE) — ветка по умолчанию в CASE.
  • Logical grouping — осознанное объединение условий в отдельные блоки через скобки.
Вывод: Эти понятия дают базу для безопасной сложной фильтрации в production.

🧩 1. Вложенные условия в WHERE

Когда условий много, запрос нужно читать как набор логических блоков, а не как одну длинную строку.
Скобки здесь не «лишние», а инструмент контроля бизнес-смысла.
🟢 Если совсем просто: Один блок = одно бизнес-правило, несколько блоков = итоговая фильтрация.
🎯 Как понять, что этап прошёл успешно: Вы можете вслух прочитать условие запроса как текстовое правило без двусмысленности.
Назначение: Явно структурировать сложную фильтрацию по логическим группам.
Простыми словами: Сначала объединяем близкие условия в блоки, потом связываем блоки между собой.
Для новичка: Не экономьте на скобках в сложных выражениях — это снижает риск ошибки.
Аналогия: Это как юридический договор: пункты должны быть явно сгруппированы, иначе смысл спорный.
Пример:
SELECT  o.id,  o.user_id,  o.total_amountFROM orders oWHERE (        o.status = 'paid'        AND o.total_amount >= 1000      )   OR (        o.status = 'processing'        AND o.created_at >= DATE '2026-03-01'      );
🔎 Как это происходит на практике:
  • Контекст: витрина «важные заказы» для оператора.
  • Действия: собирают два независимых правила и объединяют через OR.
  • Результат: каждая группа условий читается отдельно и тестируется отдельно.
Характеристики:
  • хорошо документирует бизнес-логику;
  • снижает риск случайного изменения смысла;
  • упрощает ревью и отладку.
Когда использовать: Всегда, когда в WHERE больше одного логического блока.
Вывод: Скобки в сложной фильтрации — обязательная часть инженерного качества.

⚖️ 2. Приоритет операторов AND / OR / NOT

SQL вычисляет логические операторы по приоритету:
NOT -> AND -> OR.
Если не учитывать это явно, можно получить корректный синтаксис, но неверный результат.
Поэтому в боевых запросах приоритет лучше фиксировать скобками.
🟢 Если совсем просто: Без скобок SQL может понять условие не так, как вы задумали.
🎯 Как понять, что этап прошёл успешно: Вы можете переписать любое сложное условие с явными скобками и тем же смыслом.
Назначение: Избежать скрытых логических ошибок в многоусловных фильтрах.
Простыми словами: A OR B AND C = A OR (B AND C), а не (A OR B) AND C.
Для новичка: Если в выражении есть и AND, и OR — почти всегда ставьте скобки.
Аналогия: Это как математика: сначала умножение, потом сложение.
Пример:
-- Опасная формаSELECT  u.id,  u.emailFROM users uWHERE u.is_vip = true   OR u.region = 'EU'  AND u.is_blocked = false; -- Явная и безопасная формаSELECT  u.id,  u.emailFROM users uWHERE (u.is_vip = true OR u.region = 'EU')  AND u.is_blocked = false;
🧨 Антипример: тестовые строки ломают сегмент
-- Хотели: VIP или EU, но не blocked, и исключить тестовые emailWHERE (u.is_vip = true OR u.region = 'EU')  AND u.is_blocked = false  AND u.email NOT ILIKE '%test%'
Если убрать первые скобки, смысл меняется, и часть blocked записей может пройти.
🕳️ 3-valued logic (TRUE/FALSE/UNKNOWN) — тихая ловушка В SQL логические выражения могут возвращать UNKNOWN, когда участвует NULL.
WHERE оставляет только TRUE, а FALSE и UNKNOWN отбрасываются.
Мини-пример:
-- Если last_order_at = NULL, сравнение даст UNKNOWNWHERE last_order_at >= DATE '2026-03-01'
Практика:
  • для nullable-дат используйте явную логику last_order_at IS NOT NULL AND last_order_at >= ...;
  • COALESCE применяйте только когда это действительно бизнес-значение по умолчанию.
🔎 Как это происходит на практике:
  • Контекст: сегментация VIP-пользователей.
  • Действия: команда добавляет блокировку в общий фильтр.
  • Результат: без скобок часть строк ошибочно проходит.
Характеристики:
  • ошибка не вызывает падение запроса;
  • баг часто заметен только в бизнес-метриках;
  • легко предотвратить дисциплиной скобок.
Когда использовать: При любом сочетании AND/OR/NOT в одном условии.
Вывод: Явный приоритет — базовая защита от самых дорогих логических багов.

🧮 3. CASE WHEN для классификации

CASE WHEN превращает набор условий в категорию или вычисляемое значение.
Это ключевой инструмент для отчётов, статусов и риск-оценки.
🟢 Если совсем просто: CASE = «если условие выполнено, присвой метку/значение».
🎯 Как понять, что этап прошёл успешно: У каждой строки есть понятная категория, а не неожиданный NULL.
Назначение: Классифицировать строки и вычислять бизнес-поля в одном запросе.
Простыми словами: CASE проверяет условия по порядку и берёт первую подходящую ветку.
Для новичка: Всегда добавляйте ELSE, чтобы явно контролировать «прочие» случаи.
⚠️ CASE — это выражение, а не «магический if»
  • CASE в SELECT классифицирует или считает поле;
  • CASE в WHERE тоже просто вычисляет выражение, которое должно стать TRUE.
Пример условия включения через CASE:
WHERE CASE        WHEN u.is_blocked THEN false        WHEN u.is_vip THEN true        WHEN u.region = 'EU' AND u.is_active THEN true        ELSE false      END
В большинстве случаев читабельнее обычная логика со скобками, но этот паттерн иногда полезен.
Аналогия: Это как правила сортировки заявок: критичные, средние, обычные.
Пример:
SELECT  o.id AS order_id,  o.total_amount,  o.status,  CASE    WHEN o.status = 'chargeback' THEN 'critical'    WHEN o.status = 'paid' AND o.total_amount >= 5000 THEN 'high'    WHEN o.status = 'paid' THEN 'normal'    ELSE 'review'  END AS risk_tierFROM orders o;
📊 Условные метрики через CASE (частый паттерн)
SELECT  COUNT(*) AS total_users,  SUM(CASE WHEN u.is_vip THEN 1 ELSE 0 END) AS vip_users,  SUM(CASE WHEN u.region = 'EU' AND u.is_blocked = false THEN 1 ELSE 0 END) AS eu_unblocked_usersFROM users u;
Правило: SUM(CASE ... THEN 1 ELSE 0 END) - это счётчик по условию и обычно читается проще, чем набор отдельных запросов.
🔎 Как это происходит на практике:
  • Контекст: операторский дашборд по рискам.
  • Действия: статусы и суммы переводят в единый risk_tier.
  • Результат: фронтенд получает готовую категорию без дублирования логики.
Характеристики:
  • порядок веток имеет значение;
  • ELSE предотвращает неожиданный NULL;
  • удобно для вычисления категорий в API-ответе.
Когда использовать: Когда в выборке нужна человекочитаемая бизнес-классификация.
Вывод: CASE WHEN — стандартный способ делать бизнес-логику явно и прозрачно.

🧱 4. Вычисляемые поля и повторное использование логики

Вычисляемые поля в SELECT позволяют не хранить производные данные в таблице.
Но если логика сложная, её лучше вынести в CTE, чтобы не дублировать в нескольких местах.
🟢 Если совсем просто: Считаем поле один раз, потом используем его дальше в запросе.
🎯 Как понять, что этап прошёл успешно: Логика не копируется по 2-3 раза, а изменяется в одном месте.
Назначение: Сделать сложный SQL поддерживаемым и читаемым.
Простыми словами: CTE помогает сначала посчитать поле, потом фильтровать/сортировать по нему.
Для новичка: Не пытайтесь вставлять огромный CASE в каждый блок — лучше вынести в WITH.
Аналогия: Это как именовать сложную формулу переменной, а не писать её каждый раз заново.
Пример:
WITH scored_orders AS (  SELECT    o.id,    o.user_id,    o.status,    o.total_amount,    CASE      WHEN o.status = 'chargeback' THEN 100      WHEN o.status = 'paid' AND o.total_amount >= 5000 THEN 70      WHEN o.status = 'paid' THEN 30      ELSE 10    END AS risk_score  FROM orders o)SELECT  s.id AS order_id,  s.user_id,  s.risk_scoreFROM scored_orders sWHERE s.risk_score >= 50ORDER BY s.risk_score DESC;
🔎 Как это происходит на практике:
  • Контекст: риск-модель для ручной проверки заказов.
  • Действия: сначала считают risk_score, потом фильтруют и сортируют.
  • Результат: логика ясная и переиспользуемая.
Характеристики:
  • снижает дублирование кода;
  • упрощает поддержку;
  • делает ревью проще.
Когда использовать: Когда одно и то же вычисляемое условие нужно в нескольких местах запроса.
Вывод: CTE + вычисляемые поля — базовый паттерн для поддерживаемого сложного SQL.

🧠 5. Сложные логические конструкции в реальных правилах

Иногда одно правило зависит сразу от нескольких факторов: статуса, времени, суммы, региона и исключений.
В таком случае полезно разделять «включающие» и «исключающие» условия.
🟢 Если совсем просто: Сначала пишем, кто может попасть в сегмент, потом отдельно — кто должен быть исключён.
🎯 Как понять, что этап прошёл успешно: Запрос легко проверяется на кейсах «должен попасть / не должен попасть».
Назначение: Собрать сложную фильтрацию без потери читаемости и контроля.
Простыми словами: Логика «include/exclude» в SQL снижает количество спорных интерпретаций.
Для новичка: Пишите исключения отдельным блоком AND NOT (...).
Аналогия: Это как правила допуска на мероприятие: кто проходит и кто исключается.
Пример:
SELECT  u.id AS user_id,  u.emailFROM users uWHERE (        (u.is_vip = true AND u.is_active = true)        OR (u.region = 'EU' AND u.last_order_at >= DATE '2026-02-01')      )  AND NOT (        u.is_blocked = true        OR u.email LIKE '%test%'      );
🔎 Как это происходит на практике:
  • Контекст: сегмент для дорогой рекламной кампании.
  • Действия: формируют условия включения и список исключений.
  • Результат: меньше ложноположительных записей в кампании.
Характеристики:
  • логика читается как бизнес-требование;
  • удобно тестировать по таблице кейсов;
  • меньше риск «забыть исключение».
Когда использовать: Когда фильтр содержит одновременно сложные условия включения и исключения.
Вывод: Явная структура include/exclude делает сложные фильтры надежными и проверяемыми.

⚡ 6. Производительность сложных фильтров и CASE

Даже корректный по логике запрос может быть медленным, если условия неселективны или написаны неудачно.
Особенно это касается больших таблиц и выражений с функциями над колонками.
Частая причина медленных фильтров
WHERE lower(email) = 'a@b.com'
Такой фильтр часто не использует индекс по email.
Варианты:
  • хранить email в нормализованном виде;
  • сравнивать без функции по колонке;
  • при необходимости использовать специализированный индекс под выражение.
🟢 Если совсем просто: Чем раньше и точнее вы режете набор данных, тем дешевле запрос.
🎯 Как понять, что этап прошёл успешно: Вы можете объяснить, какие части условия используют индексы, а какие нет.
Назначение: Сделать сложную фильтрацию предсказуемой по времени выполнения.
Простыми словами: Ранние селективные условия + аккуратные выражения = быстрый запрос.
Для новичка: Функции/касты в WHERE (lower(email), created_at::text) часто мешают индексному плану.
Аналогия: Сначала отобрать нужные папки по индексу, потом читать документы внутри.
Пример:
SELECT  o.id,  o.user_id,  CASE    WHEN o.status = 'paid' AND o.total_amount >= 5000 THEN 'priority'    ELSE 'normal'  END AS queue_typeFROM orders oWHERE o.created_at >= DATE '2026-03-01'  AND o.created_at < DATE '2026-03-08'  AND o.status IN ('paid', 'processing');
🔎 Как это происходит на практике:
  • Контекст: очередь обработки заказов в near-real-time.
  • Действия: сначала сужают период и статусы, потом считают CASE.
  • Результат: стабильная латентность даже на больших объёмах.
Характеристики:
  • селективность фильтра критична;
  • индексы на фильтрах и ключах обязательны;
  • EXPLAIN помогает увидеть слабые места.
Когда использовать: Всегда для сложных фильтров на таблицах с большим объёмом данных.
Вывод: Производительность сложного SQL — это инженерная дисциплина, а не удача.

🆚 Сравнение: фильтрация и CASE

КонструкцияЧто решаетКогда лучшеРиск
Скобки в WHEREЯвный порядок логикиAND/OR в одном условииПотеря смысла без скобок
CASE WHENКлассификация и вычисляемые поляОтчёты, API, очередиNULL без ELSE
CTE + CASEПовторное использование сложной логикиДлинные запросыЛишняя сложность без необходимости
AND NOT (...)Явные исключенияinclude/exclude сегментыОшибка при смешивании исключений
Вывод: Чистая структура условий важнее «короткого» SQL.

🧠 Must-Know (запомнить)

  • Приоритет операторов: NOT -> AND -> OR.
  • Если в фильтре есть AND и OR, ставьте явные скобки.
  • В CASE порядок веток имеет значение.
  • CASE без ELSE может вернуть неожиданный NULL.
  • Вычисляемую бизнес-логику лучше выносить в CTE при повторном использовании.
  • Фильтры include/exclude лучше оформлять отдельными блоками.
  • Функции и касты над колонками в WHERE могут ломать индексный путь.
🧷 NULL-safe проверки
  • = NULL никогда не сработает -> используйте IS NULL.
  • != NULL тоже не сработает -> используйте IS NOT NULL.
  • Для PostgreSQL есть NULL-safe оператор сравнения: a IS NOT DISTINCT FROM b.
-- NULL-safe равенство (PostgreSQL)a IS NOT DISTINCT FROM b
Вывод: Эти правила закрывают большинство ошибок в сложной фильтрации.

❌ Частые мифы

Миф: Скобки в WHERE можно не писать, SQL и так поймёт. ✅ Как правильно: в сложной логике скобки обязательны для контроля смысла. 📎 Почему это важно: синтаксис может быть валиден, но результат — неверный.
Миф: CASE нужен только для красивого вывода. ✅ Как правильно: CASE — рабочий инструмент бизнес-классификации и вычислений. 📎 Почему это важно: без него логика расползается по приложению и отчётам.
Миф: ELSE в CASE не обязателен. ✅ Как правильно: лучше всегда явно задавать ветку по умолчанию. 📎 Почему это важно: иначе часть данных получает NULL без явного контроля.
Миф: Если фильтр логически корректен, о производительности думать не надо. ✅ Как правильно: на больших таблицах форма условий и селективность критичны. 📎 Почему это важно: иначе SLA API и стоимость инфраструктуры ухудшаются.

🎤 Часто спрашивают на собеседованиях

Вопрос: Какой приоритет у NOT, AND, OR в SQL? ✅ Ответ: сначала NOT, затем AND, затем OR; в сложных условиях лучше ставить явные скобки.
Вопрос: Зачем нужен CASE WHEN? ✅ Ответ: для вычисления категорий и значений по условиям прямо в запросе.
Вопрос: Почему важно писать ELSE в CASE? ✅ Ответ: чтобы контролировать ветку по умолчанию и избежать неожиданного NULL.
Вопрос: Как сделать сложный фильтр читаемым? ✅ Ответ: разбить условия на логические блоки, использовать скобки и осмысленные алиасы.
Вопрос: Что такое вычисляемое поле? ✅ Ответ: поле, которое считается в SELECT (например, через CASE) и не хранится физически в таблице.
Вопрос: Как оптимизировать сложную фильтрацию? ✅ Ответ: ранние селективные условия, индексы на ключевых фильтрах и проверка плана через EXPLAIN.

🚨 Типичные ошибки

  • Писать длинное условие WHERE без скобок.
  • Предполагать неверный приоритет AND/OR.
  • Забывать ELSE в CASE.
  • Дублировать один и тот же сложный CASE в нескольких местах.
  • Смешивать условия включения и исключения без явной структуры.
  • Использовать функции/касты над индексируемыми полями в фильтре.
  • Проверять только «happy path» и не тестировать граничные кейсы.
Вывод: Главные ошибки в этой теме — логические, а не синтаксические.

✅ Best Practices

  • Группируйте условия по смыслу и фиксируйте скобками.
  • Явно документируйте include/exclude логику внутри WHERE.
  • В CASE всегда добавляйте ELSE.
  • Для сложной повторяемой логики используйте CTE.
  • Пишите вычисляемые поля с понятными alias для API.
  • Тестируйте фильтр на граничных примерах до релиза.
  • Проверяйте план выполнения для тяжёлых запросов.
Вывод: Хороший сложный SQL — это сочетание правильной логики, читаемости и предсказуемой производительности.

🏁 Заключение

Сложная фильтрация и CASE — это ядро прикладного SQL в аналитике и backend-разработке.
Именно здесь запрос либо становится надежным, либо превращается в источник тихих багов.
Если вы контролируете приоритет операторов, аккуратно строите логические блоки и осознанно используете вычисляемые поля,
вы получаете стабильные и объяснимые результаты.
Вывод: Сильный инженер умеет не просто написать WHERE, а доказать корректность каждой ветки фильтрации.
🎯

Проверьте знания

Закрепите материал — пройдите тест по теме «БЛОК 4. Сложные запросы — 14. Сложная фильтрация и CASE»

Пройти тест →