Фильтрация и трёхзначная логика
Сравнение с NULL через = даёт неизвестно (UNKNOWN), и строка не попадёт в WHERE. Ниже — логические операторы, шаблон WHERE 1=1 для удобной правки фильтров, работа NULL в AND/OR, ловушка NOT IN и оператор IS DISTINCT FROM в PostgreSQL.
Перед чтением: Операторы — общие понятия оператора, операнда, приоритетов и типов операций; здесь те же идеи в синтаксисе запросов.
Логические операторы
| Оператор | Смысл |
|---|---|
AND | Истина, только если оба операнда истинны |
OR | Истина, если хотя бы один операнд истинен |
NOT | Инверсия |
Приоритет: NOT → AND → OR.
Без скобок выражение 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 (текст запроса собирается по ходу выполнения).
Ссылки по теме:
- Взаимодействие приложений с СУБД
- PostgreSQL — практика (плейсхолдеры
$1,$2)
Пример из ежедневной работы аналитика — отключили проверку статуса одной строкой:
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в начале блока фильтров; - текст запроса полностью под его контролем;
- цель — удобство правки запроса.
- фрагмент
' 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 = NULL | UNKNOWN |
NULL <> NULL | UNKNOWN |
NULL > 5 | UNKNOWN |
5 = NULL | UNKNOWN |
Строка с 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 UNKNOWN | UNKNOWN |
FALSE AND UNKNOWN | FALSE |
TRUE OR UNKNOWN | TRUE |
FALSE OR UNKNOWN | UNKNOWN |
NOT UNKNOWN | UNKNOWN |
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;
Контрольные вопросы
- Почему
WHERE bonus = NULLне находит строки с пустым бонусом? - Чем
FALSE AND UNKNOWNотличается отTRUE AND UNKNOWN? - Когда
NOT INбезопаснее заменить наNOT EXISTS? - Зачем нужен
IS DISTINCT FROM? - В каких ситуациях
WHERE 1 = 1в скрипте аналитика и' OR 1=1--в форме входа решают разные задачи?
См. также
- Оператор SELECT — синтаксис и стиль
- Фильтрация и группировка в SQL
- Подзапросы, EXISTS и IN
- CRUD и DML
- Оптимизация SQL-запросов
- Типы SQL-инъекций
- SQL — реальные кейсы