WHERE и фильтрация в SQL: условия, LIKE, BETWEEN и IN
Главный фильтр SQL: сравнения и логика с AND, OR, NOT, шаблоны LIKE с % и _, диапазоны BETWEEN, списки IN — и классическая ловушка = NULL.
Редакция Питоники
До этого урока SELECT возвращал всё подряд — пять строк, десять, сто тысяч. Настоящие запросы почти всегда с фильтром: товары дешевле тысячи, книги Чехова, заказы за март. Фильтр в SQL — предложение WHERE, и это самое часто используемое слово языка после SELECT. Разберём его по всем правилам: сравнения, логика, шаблоны, диапазоны и загадочные NULL.
Как устроена наша учебная таблица?
Чтобы результаты фильтров были осязаемыми, весь урок живёт на одной таблице книг: пять строк с названием, автором, годом и ценой. Каждый блок ниже создаёт её заново — база в песочнице с :memory: живёт от запуска до запуска блока, так что примеры можно менять без оглядки на соседние.
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("""
CREATE TABLE books (
id INTEGER PRIMARY KEY,
title TEXT,
author TEXT,
year INTEGER,
price REAL
)
""")
cur.executemany(
"INSERT INTO books (title, author, year, price) VALUES (?, ?, ?, ?)",
[
("Мастер и Маргарита", "Булгаков", 1967, 850.0),
("Собачье сердце", "Булгаков", 1925, 450.0),
("Преступление и наказание", "Достоевский", 1866, 600.0),
("Идиот", "Достоевский", 1869, 620.0),
("Вишнёвый сад", "Чехов", 1903, 390.0),
],
)
cur.execute("SELECT title, author, year, price FROM books")
for row in cur.fetchall():
print(row)
conn.close()
('Мастер и Маргарита', 'Булгаков', 1967, 850.0)
('Собачье сердце', 'Булгаков', 1925, 450.0)
('Преступление и наказание', 'Достоевский', 1866, 600.0)
('Идиот', 'Достоевский', 1869, 620.0)
('Вишнёвый сад', 'Чехов', 1903, 390.0)Как работает WHERE на простом сравнении?
Механика проста: движок берёт каждую строку таблицы и подставляет её значения в условие. Условие — выражение, которое даёт истину, ложь или NULL. Строка прошла проверку — летит в выдачу, не прошла — отброшена. Сравнивать можно колонку с числом, колонку со строкой и даже колонку с колонкой.
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, author TEXT, year INTEGER, price REAL)")
cur.executemany(
"INSERT INTO books (title, author, year, price) VALUES (?, ?, ?, ?)",
[
("Мастер и Маргарита", "Булгаков", 1967, 850.0),
("Собачье сердце", "Булгаков", 1925, 450.0),
("Преступление и наказание", "Достоевский", 1866, 600.0),
("Идиот", "Достоевский", 1869, 620.0),
("Вишнёвый сад", "Чехов", 1903, 390.0),
],
)
cur.execute("SELECT title FROM books WHERE year > 1900")
print("year > 1900 :", cur.fetchall())
cur.execute("SELECT title, price FROM books WHERE price <= 450.0")
print("price <= 450 :", cur.fetchall())
cur.execute("SELECT title, year FROM books WHERE author = 'Чехов'")
print("author = Чехов :", cur.fetchall())
conn.close()
year > 1900 : [('Мастер и Маргарита',), ('Собачье сердце',), ('Вишнёвый сад',)]
price <= 450 : [('Собачье сердце', 450.0), ('Вишнёвый сад', 390.0)]
author = Чехов : [('Вишнёвый сад', 1903)]Три оператора на витрине: строгое неравенство >, нестрогое <= (строка с ценой 450.0 попала — граница включается) и равенство =. Да, в SQL равенство — один знак =, а не двойной, как в Python; неравенство пишется <> или !=. Равенство строк честное: 'Чехов' и 'чехов' — разные значения, регистр важен.
Как соединять условия: AND, OR, NOT и скобки?
Сложные критерии собираются из простых связками AND (и), OR (или), NOT (не). Проблема в приоритетах: AND вычисляется раньше OR. Запрос WHERE price > 800 OR author = 'Чехов' AND price < 500 движок читает как цена > 800 ИЛИ (Чехов И цена < 500), а не как ты, возможно, хотел. Скобки ничего не стоят — ставь их всегда, когда в условии встретились оба оператора.
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, author TEXT, year INTEGER, price REAL)")
cur.executemany(
"INSERT INTO books (title, author, year, price) VALUES (?, ?, ?, ?)",
[
("Мастер и Маргарита", "Булгаков", 1967, 850.0),
("Собачье сердце", "Булгаков", 1925, 450.0),
("Преступление и наказание", "Достоевский", 1866, 600.0),
("Идиот", "Достоевский", 1869, 620.0),
("Вишнёвый сад", "Чехов", 1903, 390.0),
],
)
# AND крепче OR: движок читает это как price > 800 OR (Чехов AND price < 500)
cur.execute("""
SELECT title FROM books
WHERE price > 800.0 OR author = 'Чехов' AND price < 500.0
""")
print("без скобок :", cur.fetchall())
# скобки меняют смысл: (price > 800 OR Чехов) AND price < 500
cur.execute("""
SELECT title FROM books
WHERE (price > 800.0 OR author = 'Чехов') AND price < 500.0
""")
print("со скобками:", cur.fetchall())
# NOT и составное условие:
cur.execute("""
SELECT title FROM books
WHERE NOT author = 'Достоевский' AND year > 1900
""")
print("NOT :", cur.fetchall())
conn.close()
без скобок : [('Мастер и Маргарита',), ('Вишнёвый сад',)]
со скобками: [('Вишнёвый сад',)]
NOT : [('Мастер и Маргарита',), ('Собачье сердце',), ('Вишнёвый сад',)]Что делают LIKE, % и подчёркивание в шаблоне?
Когда точное равенство не нужно, а нужно «похоже на», включается оператор LIKE. Он сравнивает строку с шаблоном, где % — любое количество любых символов (в том числе ноль), а _ — ровно один любой символ. Классика: LIKE 'С%' — начинается на С, LIKE '%сердце' — заканчивается на «сердце», LIKE '_ишнёвый%' — первый символ любой, дальше «ишнёвый».
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, author TEXT, year INTEGER, price REAL)")
cur.executemany(
"INSERT INTO books (title, author, year, price) VALUES (?, ?, ?, ?)",
[
("Мастер и Маргарита", "Булгаков", 1967, 850.0),
("Собачье сердце", "Булгаков", 1925, 450.0),
("Преступление и наказание", "Достоевский", 1866, 600.0),
("Идиот", "Достоевский", 1869, 620.0),
("Вишнёвый сад", "Чехов", 1903, 390.0),
],
)
cur.execute("SELECT title FROM books WHERE title LIKE 'С%'")
print("LIKE 'С%':", cur.fetchall())
cur.execute("SELECT title FROM books WHERE title LIKE '%сердце'")
print("LIKE '%сердце':", cur.fetchall())
cur.execute("SELECT title FROM books WHERE title LIKE '_ишнёвый%'")
print("LIKE '_ишнёвый%':", cur.fetchall())
conn.close()
LIKE 'С%': [('Собачье сердце',)]
LIKE '%сердце': [('Собачье сердце',)]
LIKE '_ишнёвый%': [('Вишнёвый сад',)]Как фильтровать диапазон BETWEEN-ом?
Диапазон «от и до» записывается как BETWEEN нижняя AND верхняя, и обе границы включаются в результат. Это просто сахар: year BETWEEN 1860 AND 1930 значит то же, что year >= 1860 AND year <= 1930, — но читается быстрее и не оставляет шанса перепутать направление границ.
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, author TEXT, year INTEGER, price REAL)")
cur.executemany(
"INSERT INTO books (title, author, year, price) VALUES (?, ?, ?, ?)",
[
("Мастер и Маргарита", "Булгаков", 1967, 850.0),
("Собачье сердце", "Булгаков", 1925, 450.0),
("Преступление и наказание", "Достоевский", 1866, 600.0),
("Идиот", "Достоевский", 1869, 620.0),
("Вишнёвый сад", "Чехов", 1903, 390.0),
],
)
cur.execute("SELECT title, year FROM books WHERE year BETWEEN 1860 AND 1930")
print(cur.fetchall())
cur.execute("SELECT title, price FROM books WHERE price BETWEEN 400.0 AND 700.0")
print(cur.fetchall())
conn.close()
[('Собачье сердце', 1925), ('Преступление и наказание', 1866), ('Идиот', 1869), ('Вишнёвый сад', 1903)]
[('Собачье сердце', 450.0), ('Преступление и наказание', 600.0), ('Идиот', 620.0)]Второй запрос собрал «средний ценовой сегмент» от 400 до 700 включительно — с обеих сторон границы вошли. Обрати внимание: BETWEEN работает не только с числами, но и со строками — title BETWEEN 'А' AND 'К' отфильтрует названия по алфавиту, что бывает удобно для алфавитных указателей.
Как проверить вхождение в список IN-ом?
Проверка «значение — одно из перечисленных» выглядит как IN (значение1, значение2, ...). Она заменяет длинную цепочку OR: author IN ('Чехов', 'Булгаков') — то же, что author = 'Чехов' OR author = 'Булгаков', только короче и без риска напортачить со скобками. Отрицание — NOT IN.
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, author TEXT, year INTEGER, price REAL)")
cur.executemany(
"INSERT INTO books (title, author, year, price) VALUES (?, ?, ?, ?)",
[
("Мастер и Маргарита", "Булгаков", 1967, 850.0),
("Собачье сердце", "Булгаков", 1925, 450.0),
("Преступление и наказание", "Достоевский", 1866, 600.0),
("Идиот", "Достоевский", 1869, 620.0),
("Вишнёвый сад", "Чехов", 1903, 390.0),
],
)
cur.execute("SELECT title FROM books WHERE author IN ('Чехов', 'Булгаков')")
print("IN :", cur.fetchall())
cur.execute("SELECT title FROM books WHERE author NOT IN ('Достоевский', 'Булгаков')")
print("NOT IN :", cur.fetchall())
conn.close()
IN : [('Мастер и Маргарита',), ('Собачье сердце',), ('Вишнёвый сад',)]
NOT IN : [('Вишнёвый сад',)]У IN есть вторая жизнь: вместо списка значений в скобках можно передать другой SELECT — подзапрос, например «авторы, чьи книги дороже восьмисот». Подзапросы — отдельная тема; пока запомни сам оператор: в реальных отчётах IN встречается постоянно, особенно в связке с GROUP BY шестого урока.
Почему year = NULL не находит строки?
Вот классика, на которой спотыкается каждый второй. NULL — не значение, а отметка об отсутствии: «данные нет». Сравнение с NULL даёт не истину и не ложь, а третье состояние — неизвестность, поэтому WHERE = NULL не находит ничего. Ни заполненных, ни пустых строк: запрос выполняется без ошибок и молча возвращает пустоту. Проверяем.
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, year INTEGER)")
cur.executemany(
"INSERT INTO books (title, year) VALUES (?, ?)",
[
("Герой нашего времени", 1840),
("Неоконченная повесть", None),
("Записки охотника", 1852),
],
)
cur.execute("SELECT title FROM books WHERE year = NULL") # ЛОВУШКА
print("year = NULL :", cur.fetchall())
cur.execute("SELECT title FROM books WHERE year IS NULL") # правильно
print("IS NULL :", cur.fetchall())
cur.execute("SELECT title FROM books WHERE year IS NOT NULL")
print("IS NOT NULL :", cur.fetchall())
conn.close()
year = NULL : []
IS NULL : [('Неоконченная повесть',)]
IS NOT NULL : [('Герой нашего времени',), ('Записки охотника',)]Что вычисляется раньше: WHERE или SELECT?
Логический порядок выполнения такой: сначала FROM достаёт строки, потом WHERE их фильтрует, и только затем SELECT вычисляет выражения и собирает выдачу. Отсюда практический вывод: условие в WHERE всегда работает с исходными колонками таблицы, а не с алиасами из SELECT. Отфильтруем книги дешевле пятисот и сразу посчитаем цену со скидкой.
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, author TEXT, year INTEGER, price REAL)")
cur.executemany(
"INSERT INTO books (title, author, year, price) VALUES (?, ?, ?, ?)",
[
("Мастер и Маргарита", "Булгаков", 1967, 850.0),
("Собачье сердце", "Булгаков", 1925, 450.0),
("Преступление и наказание", "Достоевский", 1866, 600.0),
("Идиот", "Достоевский", 1869, 620.0),
("Вишнёвый сад", "Чехов", 1903, 390.0),
],
)
cur.execute("""
SELECT title, price - 100 AS discounted
FROM books
WHERE price < 500.0
""")
for row in cur.fetchall():
print(row)
conn.close()
('Собачье сердце', 350.0)
('Вишнёвый сад', 290.0)Условие написано по колонке price, хотя в выдаче она уже превращена в discounted: фильтр отработал раньше, вычисление — позже. Забавная деталь про SQLite: она, в отличие от большинства баз, разрешает писать и WHERE discounted < 500 — алиас она найдёт. Но так делать не стоит: в PostgreSQL и MySQL этот запрос упадёт с «нет такой колонки», а код, работающий только в одной базе, — это мина в проекте, который однажды переедет.
# Продакшн-сниппет: поиск по шаблону от пользователя
import sqlite3
def search_books(conn, needle):
"""Возвращает книги, в названии которых есть подстрока needle."""
# сам шаблон - тоже параметр: % приклеиваем к ЗНАЧЕНИЮ, не к SQL
cur = conn.execute(
"SELECT title FROM books WHERE title LIKE ?",
("%" + needle + "%",),
)
return cur.fetchall()
Что дальше
WHERE — это половина выразительности SQL: теперь ты вынимаешь из таблицы ровно нужные строки — по числам, строкам, шаблонам, диапазонам и спискам, с NULL обращаешься правильно. Две ступени наверх: в уроке 5 выдачей научатся управлять — ORDER BY, LIMIT и DISTINCT наведут порядок и сделают постраничную выдачу, а в шестом уроке появятся агрегаты и GROUP BY. И не забудь, откуда растут руки: параметры ? из урока 3 нужны и внутри WHERE — пользовательский критерий никогда не вклеивается в текст запроса.
WHERE — самое часто используемое слово SQL после SELECT: настоящий запрос почти всегда с фильтром.
Сначала предскажи ответ в голове — это главный навык программиста.
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE t (x INTEGER)")
cur.execute("INSERT INTO t VALUES (NULL)")
cur.execute("SELECT COUNT(*) FROM t WHERE x = NULL")
print(cur.fetchone()[0])
cur.execute("SELECT COUNT(*) FROM t WHERE x IS NULL")
print(cur.fetchone()[0])
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE words (w TEXT)")
cur.execute("INSERT INTO words VALUES ('нос')")
cur.execute("INSERT INTO words VALUES ('сон')")
cur.execute("INSERT INTO words VALUES ('стон')")
cur.execute("SELECT w FROM words WHERE w LIKE 'с_н'")
print(cur.fetchall())
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE t (a INTEGER, b INTEGER)")
cur.execute("INSERT INTO t VALUES (0, 0)")
cur.execute("INSERT INTO t VALUES (1, 1)")
cur.execute("SELECT a FROM t WHERE a = 0 OR a = 1 AND b = 1")
print(cur.fetchall())
1. Какие строки попадут в выдачу запроса с WHERE?
2. Как вычисляется WHERE price > 800 OR author = 'Чехов' AND price < 500?
3. Что найдёт шаблон LIKE '_%'? (подчёркивание и процент)
4. Чему равен результат выражения year = NULL для строки, где year — NULL?
5. Чем year BETWEEN 1860 AND 1930 отличается от year >= 1860 AND year <= 1930?
В редакторе подготовлена таблица с четырьмя книгами. Выведите название и год книг, написанных в XIX веке: условие WHERE с BETWEEN по колонке year от 1800 до 1899. Формат вывода — как в заготовке.
Как в SQL написать условие «содержит подстроку»?
Оператором LIKE с процентами по бокам: WHERE title LIKE '%слово%' — '%'* означает любое число любых символов. Значение для шаблона передавай параметром: cur.execute("... LIKE ?", ('%' + слово + '%',)) — тогда пользовательский ввод не сможет подменить сам запрос.
Почему WHERE column = NULL не работает?
Потому что NULL — не значение, а отметка об отсутствии данных, и любое сравнение с ним даёт «неизвестно», а не истину. Запрос отрабатывает без ошибок и возвращает ноль строк. Пустоту проверяют только IS NULL (найти пустые) и IS NOT NULL (найти заполненные).
В чём разница между = и LIKE?
= проверяет точное равенство значения, LIKE сравнивает строку с шаблоном, где % и _ обозначают «любые символы». LIKE 'Иванов' вернёт то же, что = 'Иванов', а LIKE 'Иванов%' — всё, что начинается с «Иванов». В SQLite LIKE не различает регистр только для латинских букв.
Какой приоритет у AND и OR в SQL?
AND вычисляется раньше OR, как умножение раньше сложения. Запрос WHERE a OR b AND c читается как a OR (b AND c). Если нужен другой смысл — ставь скобки: WHERE (a OR b) AND c. Скобки ничего не стоят, а от тихих ошибок фильтра спасают всегда.
Понравился урок? Сошлитесь на него
«Сравнение с NULL даёт не истину и не ложь, а третье состояние — неизвестность, поэтому WHERE = NULL не находит ничего.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
sqlite3 · Урок 3
INSERT и SELECT в SQLite: наполняем базу и читаем строки
Полный цикл данных: INSERT с параметрами ? и executemany, SELECT с выбором колонок, fetchone и fetchall, lastrowid — и живая демонстрация SQL-инъекции.
Pandas · Урок 4
Очистка данных в Pandas: пропуски NaN, дубликаты и типы
Настоящая грязная выгрузка из CRM: пропуски NaN, дубликаты и цены-строки. Чистим её isna, dropna, fillna, drop_duplicates и astype — шаг за шагом.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
NumPy · Урок 8
Булева индексация и маски: фильтрация данных в NumPy
Достаём из массива только нужное: булевы маски, операторы & | ~ вместо and/or, np.where в двух ролях, argmax и честная чистка выбросов в данных датчика.
фильтрация массива pythonnp.where примеры
Pandas · Урок 3
Выборка данных в Pandas: loc, iloc и фильтрация строк по условию
loc и iloc, булева маска, isin и between — достаём из таблицы заказов именно те строки и столбцы, которые нужны, и не попадаемся на SettingWithCopyWarning.
как отфильтровать строки в pandas по условиюфильтрация данных pandas
sqlite3 · Урок 6
Агрегаты и GROUP BY в SQL: COUNT, SUM, AVG и HAVING
Считаем, суммируем и усредняем по всей таблице и по группам: COUNT, SUM, AVG, GROUP BY и фильтр групп HAVING.
чем having отличается от wheresql count null