Перейти к основному содержимому

Фильтрация и трёхзначная логика

Сравнение с NULL через = даёт неизвестно (UNKNOWN), и строка не попадёт в WHERE. Ниже — логические операторы, шаблон WHERE 1=1 для удобной правки фильтров, работа NULL в AND/OR, ловушка NOT IN и оператор IS DISTINCT FROM в PostgreSQL.

Разработчику Аналитику Тестировщику

Перед чтением: Операторы — общие понятия оператора, операнда, приоритетов и типов операций; здесь те же идеи в синтаксисе запросов.


Логические операторы

ОператорСмысл
ANDИстина, только если оба операнда истинны
ORИстина, если хотя бы один операнд истинен
NOTИнверсия

Приоритет: NOTANDOR.

Без скобок выражение a OR b AND c читается как a OR (b AND c). Для (a OR b) AND c скобки обязательны.

SELECT last_name, department, salary
FROM employees
WHERE (department = 'Sales' OR department = 'Marketing')
AND salary >= 80000;

WHERE 1=1 — переключаемые фильтры

Аналитик или разработчик часто пишет разовый запрос (ad-hoc, запрос "на сейчас" без сохранения в код приложения) прямо в SQL-клиенте СУБД. Например:

  • DBeaver
  • DataGrip
  • pgAdmin
  • вкладка SQL в Excel Power Query

Задача одна — быстро посчитать метрику, проверить гипотезу, сравнить сегменты. Условия в WHERE при этом меняют много раз за сессию.

Как обычно правят фильтр

Типичный запрос выглядит так:

SELECT COUNT(*)
FROM clients
WHERE country = 'China';

Чтобы сменить страну, строку с условием стирают и пишут заново. Чтобы вернуться к прошлому варианту — снова вспоминают и набирают текст. При двух и более условиях добавляется ещё одна сложность:

  • первое условие идёт сразу после WHERE;
  • каждое следующее — с AND или OR в начале строки;
  • если закомментировать среднее условие, легко оставить "висящий" AND и получить синтаксическую ошибку;
  • старые варианты нигде не сохранены, если не копировать их в комментарий вручную.

Шаблон с якорем 1 = 1

Удобнее сразу оформить все фильтры одинаково — каждый с AND в начале строки:

SELECT COUNT(*)
FROM clients
WHERE 1 = 1
AND country = 'China'
AND status = 'active';

1 = 1тавтология, выражение, которое всегда истинно (аналог фразы "число либо чётное, либо нечётное" для любого конкретного числа). В блоке WHERE оно выступает предикатом — условием, по которому СУБД решает, оставить строку в результате или нет. Для 1 = 1 ответ всегда "оставить", поэтому набор строк не сужается. Итог совпадает с тем же запросом без этой строки.

Зачем тогда писать лишнее? Строка WHERE 1 = 1 — фиксированная "точка старта". После неё каждый настоящий фильтр оформлен одинаково. Не нужно каждый раз решать, писать WHERE или AND.

Синтаксис WHERE и перечень типичных предикатов (=, IN, LIKE, IS NULL) — в Оператор SELECT.

Типичные сценарии

Временно убрать фильтр

  • закомментировать строку -- AND status = 'active';
  • остальной текст запроса остаётся корректным.

Держать несколько вариантов в одном файле

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

Добавить новый критерий

  • дописать в конец AND ...;
  • предыдущие строки не трогать.

Собрать запрос в коде приложения

  • в шаблоне заранее стоит WHERE 1 = 1;
  • программа дописывает только нужные AND column = ?;
  • так устроен многий динамический SQL (текст запроса собирается по ходу выполнения).

Ссылки по теме:

Пример из ежедневной работы аналитика — отключили проверку статуса одной строкой:

SELECT COUNT(*)
FROM clients
WHERE 1 = 1
AND country = 'China'
--AND status = 'active'
;

После WHERE 1 = 1 по-прежнему стоит хотя бы одно рабочее условие (country = 'China'), поэтому запрос выполняется без ошибки.

Производительность

Оптимизатор запросов — часть СУБД, которая выбирает план выполнения (какие индексы читать, в каком порядке соединять таблицы). На этапе разбора он обычно убирает бессмысленное 1 = 1 из плана. На скорость выборки это не влияет. Подробнее — Оптимизация SQL-запросов.

В PostgreSQL иногда пишут WHERE TRUE вместо WHERE 1 = 1 — смысл тот же.

Безопасность и похожая конструкция в атаках

В учебном скрипте аналитика

  • автор сам пишет WHERE 1 = 1 в начале блока фильтров;
  • текст запроса полностью под его контролем;
  • цель — удобство правки запроса.

В SQL-инъекции

  • фрагмент ' OR 1=1-- попадает в запрос через поле ввода приложения;
  • приложение склеивает SQL из строк без параметров;
  • тавтология OR 1=1 делает условие входа истинным для всех строк.

В коде продакшена ручную склейку текста дополняют защитными слоями.

  • Параметризованные запросы — значения передаются отдельно ($1, ?, @param).
  • ORM и query builder — ORM и работа с данными.

Шаблон WHERE 1 = 1 остаётся инструментом для ручных и отладочных запросов. Для API приложения его обычно заменяют параметрами и конструктором запросов.

UPDATE и DELETE

Тот же приём работает в командах DML:

UPDATE clients
SET status = 'archived'
WHERE 1 = 1
AND country = 'China'
AND last_order_at < '2024-01-01';

Перед запуском UPDATE или DELETE с несколькими AND имеет смысл сначала выполнить тот же набор условий в SELECT и убедиться, что затронуты нужные строки.


Сравнение с NULL

В SQL три значения предиката — TRUE, FALSE, UNKNOWN (когда участвует NULL).

ВыражениеРезультат
NULL = NULLUNKNOWN
NULL <> NULLUNKNOWN
NULL > 5UNKNOWN
5 = NULLUNKNOWN

Строка с UNKNOWN в WHERE не попадает в результат (как при FALSE).

Правильная проверка:

SELECT name FROM employees WHERE manager_id IS NULL;
SELECT name FROM employees WHERE manager_id IS NOT NULL;

Ошибка — сравнение с NULL через =:

-- неверно: всегда пустой результат
SELECT name FROM employees WHERE manager_id = NULL;

-- верно
SELECT name FROM employees WHERE manager_id IS NULL;

UNKNOWN в AND и OR

ВыражениеРезультат
TRUE AND UNKNOWNUNKNOWN
FALSE AND UNKNOWNFALSE
TRUE OR UNKNOWNTRUE
FALSE OR UNKNOWNUNKNOWN
NOT UNKNOWNUNKNOWN

NOT IN и NULL

value NOT IN (1, 2, NULL) даёт UNKNOWN для любого value, потому что сравнение с NULL не даёт TRUE. Итог — пустая выборка.

Используйте NOT EXISTS или исключите NULL из подзапроса (см. Подзапросы, EXISTS и IN).


PostgreSQL — IS DISTINCT FROM

Обычное сравнение не различает "оба NULL" и "оба не NULL". Оператор IS DISTINCT FROM трактует два NULL как равные:

SELECT a, b
FROM t
WHERE a IS DISTINCT FROM b; -- строки, где значения различаются (включая NULL)

SELECT a, b
FROM t
WHERE a IS NOT DISTINCT FROM b; -- эквивалент "равно с учётом NULL"

Практические рекомендации

  • Разовые запросы с часто меняющимися фильтрами удобно начинать с WHERE 1=1 — каждое условие отдельной строкой с AND, отключение через --.
  • При смешении AND и OR всегда ставьте скобки, даже если приоритет кажется очевидным.
  • Для опциональных полей явно ветвите: (col = :val OR (:val IS NULL AND col IS NULL)) или COALESCE.
  • Избегайте NOT IN с подзапросами без гарантии NOT NULL в сравниваемом столбце.
  • Для поиска по шаблону: LIKE 'префикс%' может использовать индекс; LIKE '%суффикс' — обычно нет (без pg_trgm).

Пример — сотрудники без отдела или с зарплатой выше порога

CREATE TABLE employees (
emp_id SERIAL PRIMARY KEY,
last_name TEXT NOT NULL,
department TEXT,
salary NUMERIC(10, 2)
);

SELECT last_name, department, salary
FROM employees
WHERE department IS NULL
OR salary > 100000;

Контрольные вопросы

  1. Почему WHERE bonus = NULL не находит строки с пустым бонусом?
  2. Чем FALSE AND UNKNOWN отличается от TRUE AND UNKNOWN?
  3. Когда NOT IN безопаснее заменить на NOT EXISTS?
  4. Зачем нужен IS DISTINCT FROM?
  5. В каких ситуациях WHERE 1 = 1 в скрипте аналитика и ' OR 1=1-- в форме входа решают разные задачи?

См. также