Learning Platform
Урок 05.01 · 15 мин
Начальный
WHEREPredicatesANDORNOTBETWEENOperator precedence

Зачем нужен отдельный урок про 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-aware сравнение
    , работающее с NULL как с обычным значением

Текст сравнивается лексикографически, по правилам collation базы. Числа — естественно. Даты и timestamp — по хронологии.

Простые предикаты: клиенты, рождённые до 1990 года или с неизвестным годом рождения

PostgreSQL

Запомни: WHERE birth_year < 1990 не включает Ирину с birth_year = NULL, потому что NULL < 1990 — это NULL, не TRUE. Нужна отдельная ветка OR birth_year IS NULL.

AND, OR, NOT — булева логика над предикатами

Из простых предикатов собираются составные. Операторы:

  • AND — оба условия должны быть TRUE
  • OR — хотя бы одно TRUE
  • NOT — инверсия

В трёхзначной логике результаты не такие очевидные. Главное правило: NULL распространяется как «неизвестно».

Трёхзначная логика AND и OR

NULL = «неизвестно». AND и OR ведут себя как в булевой логике, но иногда «короткое замыкание» побеждает NULL.

AND
TRUE AND TRUETRUE
TRUE AND NULLNULLНе знаем, потому что от NULL может быть и TRUE, и FALSE
FALSE AND NULLFALSEКороткое замыкание: FALSE уже всё решает
NULL AND NULLNULL
OR
TRUE OR NULLTRUEКороткое замыкание: TRUE уже всё решает
FALSE OR NULLNULL
NULL OR NULLNULL
NOT NULLNULL

Практическое следствие: если в предикате участвует столбец, в котором допустимы NULL, всегда подумай — что должно произойти с такими строками. Их нужно явно включать через OR x IS NULL или явно отсекать через AND x IS NOT NULL.

Клиенты из России, родившиеся в 1990-х (с явной обработкой NULL):

PostgreSQL

Скобки и приоритет операторов

Это самая частая ловушка. Приоритет операторов в WHERE (от высокого к низкому):

  1. NOT
  2. AND
  3. OR

Это значит: a OR b AND c читается как a OR (b AND c), а не (a OR b) AND c. Разница огромная.

Сравни два предиката — без скобок и со скобками. Получишь разные результаты:

PostgreSQL

Первый вариант вернёт всех россиян (любого возраста) плюс немцев, рождённых после 1990. Второй — только россиян и немцев, рождённых после 1990. Это разные множества.

Правило практики: если в WHERE есть и AND, и OR — расставляй скобки явно. Даже когда они «не нужны». Читателю кода через полгода они нужны.

BETWEEN — inclusive с обеих сторон

BETWEEN a AND b эквивалентен >= a AND <= b. Оба конца включены. Это часто удивляет — кажется, что должно быть строго.

BETWEEN — оба конца включены

x BETWEEN 10 AND 20 истинно для всех x от 10 до 20 включительно. Это inclusive с обеих сторон.

x = 9FALSE
x = 10TRUEЛевая граница включена
x = 15TRUE
x = 20TRUEПравая граница включена
x = 21FALSE
эквивалентx >= 10 AND x <= 20
NOT BETWEENx < 10 OR x > 20
вниманиепорядок границ важенx BETWEEN 20 AND 10 всегда FALSE — нижняя граница должна быть слева

Ещё одна тонкость: x BETWEEN 20 AND 10 — это не «между 10 и 20». Это x >= 20 AND x <= 10, что всегда FALSE. Левая граница строго ниже правой.

Заказы, размещённые в марте 2025 — обрати внимание на правый конец интервала:

PostgreSQL

Этот запрос — классическая граничная ошибка с 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 — почему запрос пропускает Ирину:

PostgreSQL

Это та же история, что в первом уроке про NULL = NULL. NULL = 2000 это NULL, NOT NULL это снова NULL, не TRUE — строка отбрасывается.

Углубление: предикаты и индексы

WHERE — единственное место, где СУБД активно использует индексы. Поэтому форма предиката напрямую влияет на производительность:

  • WHERE col = 5 — индекс работает идеально.
  • WHERE col BETWEEN 5 AND 10 — индекс работает (диапазонное сканирование).
  • WHERE LOWER(col) = 'abc' — обычный индекс не работает, нужен
    functional index
    .
  • WHERE col + 1 = 5 — индекс не работает, потому что СУБД не «откатит» арифметику обратно.

Подробнее про индексы — в модуле 11. Сейчас запомни принцип: столбец должен стоять слева от оператора в «голой» форме, без функций и арифметики.

Проверка знанийKnowledge check
Запрос WHERE country = 'RU' OR country = 'DE' AND birth_year > 1990. Какие строки он вернёт: всех RU + всех DE с birth_year > 1990, или только людей из RU и DE, рождённых после 1990?
ОтветAnswer
Первое — всех RU плюс только тех немцев, кто рождён после 1990. Приоритет AND выше OR, поэтому предикат читается как country = 'RU' OR (country = 'DE' AND birth_year > 1990). Чтобы получить «только RU и DE, рождённых после 1990», нужны явные скобки: (country = 'RU' OR country = 'DE') AND birth_year > 1990. Это самая частая ошибка в WHERE — всегда расставляй скобки, когда смешиваешь AND и OR.
Selectivity: как оптимизатор оценивает WHERE

Чек-лист

  • 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, если надо.
  • Функции и арифметика вокруг столбца ломают индекс — оборачивай выражение, не столбец.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Каков порядок приоритета операторов в WHERE PostgreSQL (от высокого к низкому)?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6