Фильтрация: where и операторы
where превращает выборку всего подряд в точный ответ: сравнения, in_, like, цепочки условий как И и or_ как ИЛИ.
Редакция Питоники
Запрос из прошлого урока возвращал всё подряд — все задачи, все строки. Реальные вопросы всегда уже: какие дела ещё не сделаны? что с приоритетом повыше? какие задачи про покупки? Ответ даёт where — условие, которое база проверяет для каждой строки. И самое приятное: условие записывается почти как обычный Python, только вместо строк SQL — выражения над колонками.
Как и в разделе про SQL, договоримся об учебной базе: пять задач с флажками и приоритетами. Первый блок собирает её и печатает целиком — эталон, с которым будем сверять каждый фильтр.
from sqlalchemy import create_engine, String, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
done: Mapped[bool] = mapped_column(default=False)
priority: Mapped[int] = mapped_column(default=1)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all([
Task(title="вынести мусор", priority=2),
Task(title="купить хлеб", priority=1),
Task(title="купить молоко", priority=1),
Task(title="полить цветы", priority=3, done=True),
Task(title="позвонить маме", priority=2),
])
session.commit()
for t in session.scalars(select(Task).order_by(Task.id)):
print(t.id, t.title, t.done, t.priority)
1 вынести мусор False 2 2 купить хлеб False 1 3 купить молоко False 1 4 полить цветы True 3 5 позвонить маме False 2
В остальных блоках урока подготовка та же самая — модель, движок, пять задач, — просто она опущена для краткости. Каждый блок самодостаточен: жми «Запустить» и меняй что угодно.
where: условие как Python-выражение
Фильтр навешивается на запрос методом .where(): передаёшь выражение над колонками — получаешь только строки, для которых оно истинно. Классика: активные задачи, то есть с флажком done равным ложь.
from sqlalchemy import create_engine, String, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
done: Mapped[bool] = mapped_column(default=False)
priority: Mapped[int] = mapped_column(default=1)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all([
Task(title="вынести мусор", priority=2),
Task(title="купить хлеб", priority=1),
Task(title="купить молоко", priority=1),
Task(title="полить цветы", priority=3, done=True),
Task(title="позвонить маме", priority=2),
])
session.commit()
for t in session.scalars(
select(Task).where(Task.done == False).order_by(Task.id)
):
print(t.id, t.title)
1 вынести мусор 2 купить хлеб 3 купить молоко 5 позвонить маме
Из пяти задач остались четыре — «полить цветы» отсеялся, у него done равен истина. Теперь про двойное равенство, на котором спотыкаются все. Условие в ORM — это Python-выражение над колонками: двойное равенство здесь не опечатка, а способ получить критерий сравнения. Task.done == False не сравнивает ничего прямо сейчас — оно строит объект-критерий, который база превратит в WHERE done = 0. Одиночное = в Python-выражении вообще не существует: это присваивание, и синтаксис его не пропустит.
select(Task).where(Task.done = False)
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
Сравнения: знакомые операторы
Над числовыми колонками работают все привычные операторы: >, >=, <, <=, ==, !=. Отберём срочное — приоритет выше первого — и наоборот, самую простую очередь с приоритетом ровно один.
from sqlalchemy import create_engine, String, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
done: Mapped[bool] = mapped_column(default=False)
priority: Mapped[int] = mapped_column(default=1)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all([
Task(title="вынести мусор", priority=2),
Task(title="купить хлеб", priority=1),
Task(title="купить молоко", priority=1),
Task(title="полить цветы", priority=3, done=True),
Task(title="позвонить маме", priority=2),
])
session.commit()
print("Приоритет выше первого:")
for t in session.scalars(
select(Task).where(Task.priority > 1).order_by(Task.id)
):
print(" ", t.id, t.title)
print("Приоритет ровно один:")
for t in session.scalars(
select(Task).where(Task.priority == 1).order_by(Task.id)
):
print(" ", t.id, t.title)
Приоритет выше первого: 1 вынести мусор 4 полить цветы 5 позвонить маме Приоритет ровно один: 2 купить хлеб 3 купить молоко
Три задачи с приоритетом 2 и 3 против двух задач с приоритетом 1 — сверься с эталонной распечаткой базы, числа сходятся. Сравнивать можно и строки с датами: Task.title > "н", Entry.created_at >= нужная_дата — база сравнит по своим правилам.
in_: значение из списка
Когда точных значений несколько, цепочка из равенств через or_ выглядит громоздко. Для этого есть in_: колонка должна совпасть с любым элементом списка.
from sqlalchemy import create_engine, String, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
done: Mapped[bool] = mapped_column(default=False)
priority: Mapped[int] = mapped_column(default=1)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all([
Task(title="вынести мусор", priority=2),
Task(title="купить хлеб", priority=1),
Task(title="купить молоко", priority=1),
Task(title="полить цветы", priority=3, done=True),
Task(title="позвонить маме", priority=2),
])
session.commit()
for t in session.scalars(
select(Task)
.where(Task.title.in_(["купить хлеб", "купить молоко"]))
.order_by(Task.id)
):
print(t.id, t.title)
2 купить хлеб 3 купить молоко
Список может прийти откуда угодно — из формы, из конфига, из другого запроса: фильтр собирается под данные на лету. Это та самая компонуемость, ради которой ORM и берут.
like: шаблон строки
Поиск по части строки делает like с шаблоном: знак процента заменяет любое количество символов, включая пустое. Шаблон "%купить%" найдёт строки, где слово «купить» стоит где угодно — в начале, середине или конце.
from sqlalchemy import create_engine, String, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
done: Mapped[bool] = mapped_column(default=False)
priority: Mapped[int] = mapped_column(default=1)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all([
Task(title="вынести мусор", priority=2),
Task(title="купить хлеб", priority=1),
Task(title="купить молоко", priority=1),
Task(title="полить цветы", priority=3, done=True),
Task(title="позвонить маме", priority=2),
])
session.commit()
for t in session.scalars(
select(Task).where(Task.title.like("%купить%")).order_by(Task.id)
):
print(t.id, t.title)
2 купить хлеб 3 купить молоко
Шаблоны как в SQL: %купить — только концовка, купить% — только начало, к__пить — подчёркивание вместо ровно одного символа. Все тонкости шаблонов разбирались в уроке про WHERE и LIKE, там же про регистр: для текста с заглавными буквами like в SQLite чувствителен к регистру, и об этом стоит помнить на настоящих данных.
Цепочка where — это И
Несколько условий навешиваются цепочкой: .where(...).where(...) — каждое добавляет своё требование, и строка должна удовлетворить всем сразу. Это логическое И, что бы ни подсказывала интуиция.
from sqlalchemy import create_engine, String, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
done: Mapped[bool] = mapped_column(default=False)
priority: Mapped[int] = mapped_column(default=1)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all([
Task(title="вынести мусор", priority=2),
Task(title="купить хлеб", priority=1),
Task(title="купить молоко", priority=1),
Task(title="полить цветы", priority=3, done=True),
Task(title="позвонить маме", priority=2),
])
session.commit()
for t in session.scalars(
select(Task)
.where(Task.done == False)
.where(Task.priority >= 2)
.order_by(Task.id)
):
print(t.id, t.title)
1 вынести мусор 5 позвонить маме
Флажок «не готово» оставлял четыре задачи, приоритет от двух — три. Пересечение — всего две: задачи 1 и 5. Каждое условие сузило список, как и положено И. Короткая запись того же смысла — функция and_: where(and_(Task.done == False, Task.priority >= 2)) — удобно, когда условия собираются в список динамически.
or_: хотя бы одно условие
Логическое ИЛИ строится функцией or_ из sqlalchemy: строка попадёт в выдачу, если истинно хотя бы одно из условий. Спросим: что выполнено или что срочное?
from sqlalchemy import create_engine, String, select, or_
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
done: Mapped[bool] = mapped_column(default=False)
priority: Mapped[int] = mapped_column(default=1)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all([
Task(title="вынести мусор", priority=2),
Task(title="купить хлеб", priority=1),
Task(title="купить молоко", priority=1),
Task(title="полить цветы", priority=3, done=True),
Task(title="позвонить маме", priority=2),
])
session.commit()
for t in session.scalars(
select(Task)
.where(or_(Task.done == True, Task.priority == 3))
.order_by(Task.id)
):
print(t.id, t.title, t.done, t.priority)
4 полить цветы True 3
Условия пересеклись на одной задаче: «полить цветы» и выполнена, и срочная. Остальные не подошли ни под один критерий — вот и весь ответ. Импорт у or_ и and_ общий с остальным инструментарием: from sqlalchemy import or_, and_.
from sqlalchemy import create_engine, String, select, or_
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
done: Mapped[bool] = mapped_column(default=False)
priority: Mapped[int] = mapped_column(default=1)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all([
Task(title="вынести мусор", priority=2),
Task(title="купить хлеб", priority=1),
Task(title="купить молоко", priority=1),
Task(title="полить цветы", priority=3, done=True),
Task(title="позвонить маме", priority=2),
])
session.commit()
print("Два where - это И:")
for t in session.scalars(
select(Task)
.where(Task.done == False)
.where(Task.priority == 3)
.order_by(Task.id)
):
print(" ", t.id, t.title)
print("or_ - это ИЛИ:")
for t in session.scalars(
select(Task)
.where(or_(Task.done == False, Task.priority == 3))
.order_by(Task.id)
):
print(" ", t.id, t.title)
Два where - это И: or_ - это ИЛИ: 1 вынести мусор 2 купить хлеб 3 купить молоко 4 полить цветы 5 позвонить маме
Первая программа: умный фильтр
Соберём приёмы в одну выборку: активные задачи с приоритетом не ниже двух, отсортированные по срочности, с подсчётом найденного. Три условия, сортировка и счётчик — типовой запрос настоящего списка дел.
from sqlalchemy import create_engine, String, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
done: Mapped[bool] = mapped_column(default=False)
priority: Mapped[int] = mapped_column(default=1)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all([
Task(title="вынести мусор", priority=2),
Task(title="купить хлеб", priority=1),
Task(title="купить молоко", priority=1),
Task(title="полить цветы", priority=3, done=True),
Task(title="позвонить маме", priority=2),
])
session.commit()
rows = session.scalars(
select(Task)
.where(Task.done == False)
.where(Task.priority >= 2)
.order_by(Task.priority)
).all()
for t in rows:
print(t.title, "- приоритет", t.priority)
print("Найдено:", len(rows))
вынести мусор - приоритет 2 позвонить маме - приоритет 2 Найдено: 2
Обрати внимание: фильтрация и сортировка живут в одном запросе-цепочке, а подсчёт делает уже Python — список-то маленький. На больших таблицах счётчик переедет в базу через func.count(), о чём будет отдельный урок. Условия можно навешивать и на связанные таблицы через join — это тема урока 11.
Что дальше
Ты фильтруешь базу как на родном языке: сравнения через знакомые операторы, списки через in_, шаблоны через like, комбинирование через цепочки where, and_ и or_. База пока только читается и пополняется — в следующем уроке данные научатся обновляться и удаляться, и CRUD-квадрат замкнётся: создавать, читать, изменять, удалять.
Каждое .where() в цепочке сужает выдачу — это И. Для «хотя бы одно» существует только or_ с его явным списком условий.
Сначала предскажи ответ в голове — это главный навык программиста.
# в базе задачи с приоритетами:
# 1 вынести мусор (2), 2 купить хлеб (1),
# 3 полить цветы (3, done=True)
rows = session.scalars(
select(Task).where(Task.priority > 1).order_by(Task.id)
).all()
for t in rows:
print(t.id, t.title)
rows = session.scalars(
select(Task).where(Task.title.like("купить%")).order_by(Task.id)
).all()
print(len(rows))
# в базе: 1 дело А (done=False), 2 дело Б (done=True),
# 3 дело В (done=False)
rows = session.scalars(
select(Task).where(or_(Task.done == True, Task.id == 1)).order_by(Task.id)
).all()
print(len(rows))
1. Как в where записать условие «done равен False»?
2. Что делает цепочка .where(A).where(B)?
3. Как выбрать задачи, у которых title — «хлеб» или «молоко»?
4. Что найдёт шаблон like("%купить%")?
5. Запрос с where(Task.done == False).where(Task.priority == 3) вернул пустой список, хотя подходящие «или-или» строки есть. Почему?
Собери «умный фильтр» по учебной базе пяти задач: выбери активные задачи (done равен False) с приоритетом не ниже 2, отсортируй по возрастанию id и выведи каждую в формате «id название», а последней строкой — «Найдено: N». Для сверки: должны остаться задачи 1 и 5.
Как написать несколько условий в where SQLAlchemy?
Цепочкой: select(Task).where(A).where(B) — условия складываются как логическое И, строка должна пройти все. Для динамического списка условий есть and_(A, B). Логическое ИЛИ выражается только функцией or_(A, B) из sqlalchemy.
Почему в where нужно двойное равенство?
Потому что условие — это Python-выражение: Task.done == False вычисляет критерий сравнения и передаёт его базе. Одиночное = в выражении — синтаксическая ошибка Python ещё до запуска. Не путай с текстом SQL-запроса, где в WHERE пишется одиночное =: там это язык SQL, здесь — Python.
Как найти строки, содержащие часть слова?
Через like с процентом: Task.title.like("%купить%") найдёт строки, где «купить» стоит в любом месте. Процент заменяет любое количество символов, подчёркивание — ровно один. Учти, что в SQLite like чувствителен к регистру для текста со смешанным регистром.
Чем in_ лучше цепочки or_?
Для проверки колонки против списка значений in_ короче и быстрее читается: Task.title.in_(список) вместо or_(Task.title == x, Task.title == y, ...). Плюс список для in_ можно построить во время работы программы — из формы, конфига или другого запроса, а or_ пришлось бы собирать вручную.
Понравился урок? Сошлитесь на него
«Условие в ORM — это Python-выражение над колонками: двойное равенство здесь не опечатка, а способ получить критерий сравнения.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
SQLAlchemy · Урок 6
select: первые запросы к таблице
select(Task) возвращает строки-объекты: перебор циклом, одна строка через first(), одно значение через scalar().
SQLAlchemy · Урок 8
Обновление и удаление записей
Данные записаны — теперь их надо менять: два пути SQLAlchemy, ручной через объект и массовый через update() и delete().
sqlite3 · Урок 4
WHERE и фильтрация в SQL: условия, LIKE, BETWEEN и IN
Главный фильтр SQL: сравнения и логика с AND, OR, NOT, шаблоны LIKE с % и _, диапазоны BETWEEN, списки IN — и классическая ловушка = NULL.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
NumPy · Урок 8
Булева индексация и маски: фильтрация данных в NumPy
Достаём из массива только нужное: булевы маски, операторы & | ~ вместо and/or, np.where в двух ролях, argmax и честная чистка выбросов в данных датчика.
фильтрация массива pythonnp.where примеры
json · Урок 11
Фильтрация данных: найти нужное в JSON
Включение с условием для отбора, next с default для первого подходящего, any и all для вопросов ко всей коллекции — работаем с выгрузкой заказов.
python фильтрация json данныхфильтр списка словарей python
SQLAlchemy · Урок 11
Join в ORM: запросы по связям
Навигация читает связь у готового объекта, join фильтрует по чужой таблице в одном запросе: все задачи автора по имени.
sqlalchemy joinsqlalchemy join запросы