Зачем нужен отдельный урок про WHERE
Кажется, что фильтрация — банальность: написал WHERE x = 1 — получил строки. Но именно в WHERE рождается большинство багов в SQL: предикат «почти правильный» отсеивает не то, что хотели, или пропускает строки с NULL, или работает медленно из-за порядка условий. В этом уроке мы аккуратно разложим, как СУБД на самом деле читает предикат.
Формально, WHERE p — это селекция σ из реляционной алгебры. Предикат p применяется к каждому кортежу независимо; кортеж остаётся в результате, если p равен TRUE. Не TRUE или NULL — только TRUE.
Операторы сравнения
Базовый набор для одного значения:
=равно,<>или!=не равно (оба варианта валидны,<>— стандартный)<,>,<=,>=строгое и нестрогое сравнениеIS NULL/IS NOT NULL— единственный способ проверить отсутствие значенияIS DISTINCT FROM/IS NOT DISTINCT FROM—, работающее с NULL как с обычным значениемNULL-aware сравнение
Текст сравнивается лексикографически, по правилам collation базы. Числа — естественно. Даты и timestamp — по хронологии.
Простые предикаты: клиенты, рождённые до 1990 года или с неизвестным годом рождения
Запомни: WHERE birth_year < 1990 не включает Ирину с birth_year = NULL, потому что NULL < 1990 — это NULL, не TRUE. Нужна отдельная ветка OR birth_year IS NULL.
AND, OR, NOT — булева логика над предикатами
Из простых предикатов собираются составные. Операторы:
AND— оба условия должны бытьTRUEOR— хотя бы одноTRUENOT— инверсия
В трёхзначной логике результаты не такие очевидные. Главное правило: NULL распространяется как «неизвестно».
NULL = «неизвестно». AND и OR ведут себя как в булевой логике, но иногда «короткое замыкание» побеждает NULL.
Практическое следствие: если в предикате участвует столбец, в котором допустимы NULL, всегда подумай — что должно произойти с такими строками. Их нужно явно включать через OR x IS NULL или явно отсекать через AND x IS NOT NULL.
Клиенты из России, родившиеся в 1990-х (с явной обработкой NULL):
Скобки и приоритет операторов
Это самая частая ловушка. Приоритет операторов в WHERE (от высокого к низкому):
NOTANDOR
Это значит: a OR b AND c читается как a OR (b AND c), а не (a OR b) AND c. Разница огромная.
Сравни два предиката — без скобок и со скобками. Получишь разные результаты:
Первый вариант вернёт всех россиян (любого возраста) плюс немцев, рождённых после 1990. Второй — только россиян и немцев, рождённых после 1990. Это разные множества.
Правило практики: если в WHERE есть и AND, и OR — расставляй скобки явно. Даже когда они «не нужны». Читателю кода через полгода они нужны.
BETWEEN — inclusive с обеих сторон
BETWEEN a AND b эквивалентен >= a AND <= b. Оба конца включены. Это часто удивляет — кажется, что должно быть строго.
x BETWEEN 10 AND 20 истинно для всех x от 10 до 20 включительно. Это inclusive с обеих сторон.
Ещё одна тонкость: x BETWEEN 20 AND 10 — это не «между 10 и 20». Это x >= 20 AND x <= 10, что всегда FALSE. Левая граница строго ниже правой.
Заказы, размещённые в марте 2025 — обрати внимание на правый конец интервала:
Этот запрос — классическая граничная ошибка с BETWEEN и timestamp. Конец дня не входит, потому что '2025-03-31' интерпретируется как 2025-03-31 00:00:00. Для интервалов дат с timestamp надёжнее писать >= start AND < next_period_start.
Короткое замыкание и порядок условий
PostgreSQL не гарантирует, что условия в AND или OR вычисляются слева направо. Оптимизатор может переставить их так, как считает выгоднее. Это значит:
- Нельзя полагаться на «защиту» от ошибки через порядок:
WHERE x IS NOT NULL AND x / 0 > 1теоретически может всё равно упасть на делении. - Для гарантированной защиты используй
CASEилиCOALESCE. - Для большинства задач это не важно — оптимизатор только улучшает производительность, не меняет результат.
NOT и инверсия
NOT инвертирует предикат, но NOT NULL остаётся NULL. Это часто ломает интуицию: NOT (x = 5) отсеивает не только строки с x != 5, но и строки с x IS NULL (потому что результат NOT NULL это NULL, а не TRUE).
Парадокс NOT и NULL — почему запрос пропускает Ирину:
Это та же история, что в первом уроке про NULL = NULL. NULL = 2000 это NULL, NOT NULL это снова NULL, не TRUE — строка отбрасывается.
Углубление: предикаты и индексы
WHERE — единственное место, где СУБД активно использует индексы. Поэтому форма предиката напрямую влияет на производительность:
WHERE col = 5— индекс работает идеально.WHERE col BETWEEN 5 AND 10— индекс работает (диапазонное сканирование).WHERE LOWER(col) = 'abc'— обычный индекс не работает, нужен.functional indexWHERE col + 1 = 5— индекс не работает, потому что СУБД не «откатит» арифметику обратно.
Подробнее про индексы — в модуле 11. Сейчас запомни принцип: столбец должен стоять слева от оператора в «голой» форме, без функций и арифметики.
Чек-лист
WHERE p— это селекция σ; кортеж остаётся, еслиpравноTRUE(неNULL).IS NULL/IS NOT NULL— единственные операторы для проверкиNULL.- Приоритет:
NOT>AND>OR. Смешиваешь — ставь скобки явно. BETWEEN a AND bвключает оба конца. Для timestamp-дат используй>= a AND < next.NOT (x = y)не включает строки сNULL— добавляйOR x IS NULL, если надо.- Функции и арифметика вокруг столбца ломают индекс — оборачивай выражение, не столбец.