Урок курса

WHERE: фильтрация строк

SQL с нуля: бесплатный курс с практикой

В прошлом уроке мы выбирали, какие столбцы вернуть и как их назвать. Сейчас займёмся другим измерением — какие строки вообще попадут в результат. Именно это делает WHERE.

WHERE и операторы сравнения: механизм фильтрации строк

WHERE записывается сразу после FROM и содержит условие — логическое выражение, которое вычисляется для каждой строки таблицы. Если оно равно TRUE, строка включается в результат. Если FALSE или NULL — отбрасывается. Никакого исключения: именно три возможных значения (TRUE / FALSE / NULL) определяют судьбу строки.

Предположим, таблица products содержит столбцы product_id, title и price. Самый прямой запрос:

SELECT title, price
FROM products
WHERE price > 10000;

SQL проходит по каждой строке, подставляет значение price и проверяет неравенство. Строки с price 32 000 или 15 000 проходят; строки с price 1 200 — нет.

Шесть операторов сравнения работают предсказуемо:

  • = — равно (для чисел и строк)
  • <> — не равно
  • <, > — строго меньше / больше
  • <=, >= — меньше или равно / больше или равно

Пример с точным совпадением:

SELECT title, price
FROM products
WHERE price = 1200;

А вот обратный случай:

SELECT title, price
FROM products
WHERE price <> 1200;

Операторы сравнения работают и со строками. WHERE title = 'Мышь' найдёт строку с точным совпадением названия. Сравнение строк идёт лексикографически, поэтому > и < для строк тоже имеют смысл, хотя на практике их используют реже, чем для чисел.

Анимация последовательной проверки строк таблицы условием WHERE price больше или равно 500 и переноса подходящих строк в результат
WHERE проверяет строки и оставляет подходящие

AND, OR, NOT: порядок приоритета и управление скобками

Реальные условия фильтрации редко состоят из одного сравнения. Когда их несколько, SQL соединяет их через AND, OR и NOT — и здесь кроется ловушка для невнимательных.

Приоритет такой: NOT вычисляется первым, потом AND, потом OR. Это не договорённость, а жёсткое правило языка, аналогичное умножению перед сложением в арифметике.

Рассмотрим условие:

SELECT title, price
FROM products
WHERE price > 5000 OR price < 2000 AND title = 'Мышь';

Интуитивно может казаться, что фильтр означает «(дорогие или дешёвые) товары с названием Мышь». На деле SQL разбирает это как:

WHERE price > 5000 OR (price < 2000 AND title = 'Мышь')

AND связывает price < 2000 и title = 'Мышь' в первую очередь. OR применяется последним. Значит, в результат войдут все товары дороже 5 000 (независимо от названия) плюс дешёвые мыши. Это совсем не то, что предполагалось.

Чтобы получить именно «(дорогие или дешёвые) И название Мышь», нужны скобки:

SELECT title, price
FROM products
WHERE (price > 5000 OR price < 2000) AND title = 'Мышь';

Скобки не меняют синтаксис — они только явно задают порядок вычисления. Используйте их всякий раз, когда смешиваете AND и OR в одном условии: это не стиль, а защита от логической ошибки, которую потом очень трудно отловить.

NOT инвертирует результат следующего за ним условия:

SELECT title, price
FROM products
WHERE NOT price > 10000;

Это эквивалентно WHERE price <= 10000, но NOT удобен, когда инвертируемое выражение сложное и переписывать его вручную неудобно.

IN и BETWEEN: сокращённая запись списка и диапазона

IN и BETWEEN — это не новые логические механизмы, а удобная форма записи условий, которые иначе пришлось бы разворачивать в цепочки OR или AND.

Вместо:

WHERE product_id = 101 OR product_id = 103 OR product_id = 107

можно написать:

SELECT title, price
FROM products
WHERE product_id IN (101, 103, 107);

Результат идентичен, но при пяти-шести значениях IN читается несравнимо лучше. Значения в списке лучше записывать в одном типе, совместимом с типом столбца. Смешанные литералы могут вести себя по-разному в разных СУБД из-за неявного приведения типов.

NOT IN инвертирует проверку — вернёт все строки, где product_id не входит в список. Есть одна важная оговорка: если в список NOT IN попадает хотя бы один NULL, запрос не вернёт ни одной строки. Это следствие NULL-семантики, которую разберём в следующей секции; пока просто держите в уме, что список значений для NOT IN не должен содержать NULL.

BETWEEN закрывает другой частый случай — диапазон:

SELECT title, price
FROM products
WHERE price BETWEEN 1000 AND 32000;

Оба границы включаются. Это эквивалентно WHERE price >= 1000 AND price <= 32000. Форма BETWEEN короче и явно сигнализирует читателю, что речь идёт о непрерывном диапазоне, а не о двух независимых условиях.

NOT BETWEEN исключает диапазон:

SELECT title, price
FROM products
WHERE price NOT BETWEEN 1000 AND 32000;

Это строки с price < 1000 или price > 32000.

Когда что выбрать: IN хорош для перечисления конкретных значений, BETWEEN — для числовых или дат-диапазонов. Если значения несмежные и их больше двух-трёх, IN выигрывает по читаемости. Если условие — именно «от и до», BETWEEN точнее передаёт намерение.

LIKE, IS NULL и IS NOT NULL: шаблонный и NULL-поиск

LIKE нужен, когда точное значение строки неизвестно, но известна её часть или структура. Шаблон содержит обычные символы и два специальных метасимвола.

% заменяет любое количество символов — в том числе ноль:

SELECT title
FROM products
WHERE title LIKE 'Но%';

Этот запрос найдёт «Ноутбук», «Носитель», «Но» и любую другую строку, начинающуюся на «Но».

_ заменяет ровно один символ:

SELECT title
FROM products
WHERE title LIKE '_ышь';

Шаблон _ышь соответствует строкам из четырёх символов, где последние три — «ышь». «Мышь» подходит, «Камышь» — нет (лишний символ). Именно фиксированная длина отличает _ от %.

NOT LIKE инвертирует: вернёт строки, не соответствующие шаблону.

Теперь о NULL. NULL — это не пустая строка и не ноль; это отсутствие значения как такового. Попытка сравнить с ним через = или <> не даёт ни TRUE, ни FALSE:

-- Этот запрос вернёт 0 строк, даже если NULL-строки есть
SELECT * FROM products WHERE price = NULL;

Выражение price = NULL вычисляется в NULL (неизвестно), а не в TRUE. WHERE пропускает только TRUE — значит, ни одна строка не пройдёт фильтр. Это не баг и не ошибка синтаксиса; движок честно выполнит запрос и честно вернёт пустой результат.

Для работы с NULL есть специальный синтаксис:

SELECT title, price
FROM products
WHERE price IS NULL;
SELECT title, price
FROM products
WHERE price IS NOT NULL;

IS NULL — это не оператор сравнения, а отдельная конструкция языка, которая явно проверяет отсутствие значения. Она всегда возвращает TRUE или FALSE, никогда NULL. Поэтому именно она корректно отбирает строки с незаполненными полями.

Практическое правило: если столбец может хранить NULL (а большинство столбцов могут, если нет ограничения NOT NULL), условия = NULL и <> NULL нужно заменять на IS NULL и IS NOT NULL — иначе фильтр молча пропустит все такие строки.

Проверим WHERE с пустым значением

В таблице три заказа: с известной суммой 100, с неизвестной суммой NULL и с нулевой суммой. NULL обозначает отсутствие значения, а не число 0. Проверим фильтры на SQLite в памяти. Python нужен только как простой способ запустить SQL без отдельного сервера.

import sqlite3
with sqlite3.connect(":memory:") as db:
    db.execute("CREATE TABLE orders (id INTEGER, amount INTEGER)")
    db.executemany("INSERT INTO orders VALUES (?, ?)", [(1,100),(2,None),(3,0)])
    known = db.execute("SELECT id FROM orders WHERE amount > 0 ORDER BY id").fetchall()
    missing = db.execute("SELECT id FROM orders WHERE amount IS NULL").fetchall()
    wrong = db.execute("SELECT id FROM orders WHERE amount = NULL").fetchall()
    assert known == [(1,)] and missing == [(2,)] and wrong == []
    print(known, missing, wrong)

Сравнение = NULL не возвращает строку с пропуском. В WHERE остаются только строки, для которых условие истинно. Для самопроверки найдите строки с суммой 0 и сравните результат с IS NULL. При переносе запросов в другой диалект сохраняйте смысл NULL и отдельно проверяйте специфичные функции.

Продолжите разбор: GROUP BY и HAVING.

Задача 1

Таблица products содержит строки с price: 75000, 1200, 2000, 32000, 3500, 8900. Какое условие WHERE вернёт только строки с price 1200 и 3500 — без лишних строк?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 2

В таблице customers есть строки, у которых email не заполнен. Какой запрос корректно найдёт именно такие строки?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 3

Как SQL вычисляет условие WHERE price > 5000 OR price < 2000 AND title = 'Мышь' без дополнительных скобок?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 4

Таблица products содержит строки:
• (101, 'Ноутбук', 75000)
• (102, 'Мышь', 1200)
• (103, 'Монитор', 32000)

Аналитик написал условие: WHERE price <= 32000 AND NOT product_id = 102.

Какой product_id вернёт этот запрос? Введите число.

Без регистрации · результат не сохраняется

Задача 5

Товары из выбранных категорий и диапазона

В таблице products категория товара хранится в столбце category. Отберите из products товары категорий books и games, цена которых находится в диапазоне от 1000 до 5000 включительно. Верните product_id, title и price в указанном порядке. Каждая проверка устроена так, что условию соответствует не более одной строки.

РешениеSQL · SQLite

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 6

Контакты с подходящим именем

Из таблицы customers верните customer_id и name для клиентов, чьё имя начинается с латинской буквы A и содержит после неё хотя бы один символ. В результат должны попадать только клиенты с заполненным email. Каждая проверка даёт не более одной подходящей строки.

РешениеSQL · SQLite

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задачи к уроку

Все задачи курса →

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку