🐛 Типичные ошибки при работе с запросами

⚡ Топ-3 ошибки

  1. Забыть commit() — изменения не сохраняются в БД.
  2. NoResultFound от .one() — нет записи, а код ожидает ровно одну.
  3. Использовать session.query() в 2.x — устаревший API, в новых проектах использовать select() + session.scalars().

Ошибка 1: Забыли вызвать commit()

Неверно

with Session(engine) as session:
    session.add(User(name="Alice", age=30))
    # commit() не вызван — данные не сохранятся!

Верно

with Session(engine) as session:
    session.add(User(name="Alice", age=30))
    session.commit()  # обязательно!

Почему: без commit() изменения существуют только в рамках транзакции в памяти. При закрытии with-блока транзакция автоматически откатывается.

Ошибка 2: NoResultFound при .one()

Опасный код

with Session(engine) as session:
    # Если нет пользователя с id=999 — будет исключение!
    stmt = select(User).where(User.id == 999)
    user = session.scalars(stmt).one()  # raises NoResultFound

Безопасный вариант

with Session(engine) as session:
    # .one_or_none() — безопаснее, если результат может отсутствовать
    stmt = select(User).where(User.id == 999)
    user = session.scalars(stmt).one_or_none()
    if user:
        print(user.name)
    else:
        print("Пользователь не найден")

Когда использовать .one(): только когда вы уверены, что запись существует и единственная (например, lookup по уникальному полю в системе с гарантиями).

Ошибка 3: Нет проверки после .first()

Неверно

with Session(engine) as session:
    user = session.scalars(select(User)).first()
    print(user.name)  # AttributeError: 'NoneType' has no attribute 'name'
                      # если пользователей нет!

Верно

with Session(engine) as session:
    user = session.scalars(select(User)).first()
    if user:
        print(user.name)
    else:
        print("Список пуст")

Ошибка 4: Изменение объекта без сохранения

Неверно

with Session(engine) as session:
    user = session.get(User, 1)
    user.age = 35
    # commit() забыли — изменение не сохранится!

Верно

with Session(engine) as session:
    user = session.get(User, 1)
    if user:
        user.age = 35
        session.commit()  # только после этого UPDATE дойдёт до БД

Ошибка 5: Двойное равенство в Python vs SQL

Неверно

# Питон is/== — для Python-объектов
# в фильтре нужно использовать == (SQLAlchemy перегружает оператор)
stmt = select(User).where(User.name is "Alice")  # НЕВЕРНО — Python is, не SQL

Верно

# == в SQLAlchemy генерирует SQL WHERE name = 'Alice'
stmt = select(User).where(User.name == "Alice")  # верно

# Для проверки NULL — специальный метод
stmt = select(User).where(User.age.is_(None))    # WHERE age IS NULL

Ошибка 6: Использование session.query() в 2.x

Устаревший стиль (из лекции)

# session.query() — Legacy Query API, устарел в 2.x
users = session.query(User).filter(User.age > 25).all()
user = session.query(User).get(1)  # .get() на Query — устарел

Современный стиль (2.x)

from sqlalchemy import select

# select() + session.scalars() — рекомендуемый подход
stmt = select(User).where(User.age > 25)
users = session.scalars(stmt).all()

# session.get() на Session — современный способ по ключу
user = session.get(User, 1)

Примечание: session.query() продолжает работать в SQLAlchemy 2.x как legacy. Но в IDE появляется предупреждение об устаревании, а в будущей версии поддержка может быть удалена.

Ошибка 7: in_() с одиночным значением

Неверно

# in_() ожидает список/кортеж, не строку
stmt = select(User).where(User.name.in_("Alice"))  # итерирует по символам!

Верно

# Передавайте список или кортеж
stmt = select(User).where(User.name.in_(["Alice"]))   # список из одного элемента
# или используйте ==
stmt = select(User).where(User.name == "Alice")

Ошибка 8: like() — забыли % wildcard

Неверно

# Без % — точное совпадение, не поиск подстроки
stmt = select(User).where(User.name.like("Al"))  # найдёт только "Al", не "Alice"

Верно

# % — любое количество символов
stmt = select(User).where(User.name.like("Al%"))  # "Alice", "Alena", "Al" — все
stmt = select(User).where(User.name.like("%ice"))  # "Alice", "Clarice" — окончание
stmt = select(User).where(User.name.like("%li%"))  # содержит "li"