🐛 Типичные ошибки: SQLAlchemy-запросы

Симптом → Причина → Как исправить

⚡ Топ-5 ошибок практикума

  1. Забыть session.commit() — изменения остаются только в памяти, в БД не записываются.
  2. Address.id == None вместо Address.id.is_(None) — SAWarning в 2.x, неверное поведение в некоторых СУБД.
  3. Не импортировать funcNameError: name 'func' is not defined. Нужно: from sqlalchemy import func.
  4. Обращение к subq.column_name вместо subq.c.column_nameAttributeError при использовании подзапроса.
  5. Не вызвать Base.metadata.create_all(engine) перед сессией — OperationalError: no such table.

Ошибки при работе с сессией

Ошибка 1: Забыть session.commit()

Симптом: данные исчезают после закрытия сессии. Повторный запрос возвращает старые данные.

Причина: session.add(), session.delete() и изменения атрибутов — это операции внутри транзакции. Без commit() транзакция откатывается при закрытии сессии.

# НЕПРАВИЛЬНО:
user = User(name="Charlie", age=40)
session.add(user)
# commit() пропущен — Charlie не появится в БД

# ПРАВИЛЬНО:
user = User(name="Charlie", age=40)
session.add(user)
session.commit()  # теперь данные сохранены

Ошибка 2: Изменение объекта без предварительного запроса

Симптом: создаётся новый объект вместо обновления существующего.

Причина: нельзя изменить запись, не получив её из БД сначала.

# НЕПРАВИЛЬНО:
user = User(name="Bob", age=25)  # создаёт НОВОГО пользователя, не обновляет
session.add(user)
session.commit()

# ПРАВИЛЬНО:
user = session.query(User).filter(User.name == "Bob").first()
if user:
    user.age = 25
    session.commit()

Ошибки при фильтрации

Ошибка 3: Использование == None вместо is_(None)

Симптом: SAWarning: Comparison to None using == operator is not supported in SQLAlchemy 2.x. В некоторых СУБД может вернуть неверный результат.

Причина: в SQL NULL-значения сравниваются через IS NULL, а не через = NULL. SQLAlchemy 2.x генерирует предупреждение при использовании == None.

# НЕПРАВИЛЬНО (устаревший стиль):
.filter(Address.id == None)

# ПРАВИЛЬНО (явный IS NULL):
.filter(Address.id.is_(None))

Ошибка 4: Забыть проверить результат на None перед обращением

Симптом: AttributeError: 'NoneType' object has no attribute 'name'.

Причина: .first() возвращает None если запись не найдена. Обращение к user.name без проверки вызывает исключение.

# НЕПРАВИЛЬНО:
user = session.query(User).filter(User.name == "Unknown").first()
print(user.name)  # AttributeError!

# ПРАВИЛЬНО:
user = session.query(User).filter(User.name == "Unknown").first()
if user:
    print(user.name)
else:
    print("User not found")

Ошибки при агрегациях

Ошибка 5: Не импортировать func

Симптом: NameError: name 'func' is not defined.

Причина: func — это объект из модуля sqlalchemy, он не импортируется автоматически.

# НЕПРАВИЛЬНО:
avg = session.query(func.avg(User.age)).scalar()  # NameError!

# ПРАВИЛЬНО:
from sqlalchemy import func
avg = session.query(func.avg(User.age)).scalar()

Ошибка 6: Неверный доступ к столбцам подзапроса

Симптом: AttributeError: 'Subquery' object has no attribute 'average_age'.

Причина: к столбцам подзапроса нужно обращаться через атрибут .c (columns).

# НЕПРАВИЛЬНО:
subq = session.query(func.avg(User.age).label('average_age')).subquery()
session.query(User).filter(User.age > subq.average_age)  # AttributeError!

# ПРАВИЛЬНО:
subq = session.query(func.avg(User.age).label('average_age')).subquery()
session.query(User).filter(User.age > subq.c.average_age)  # OK

Ошибка 7: Использовать WHERE вместо HAVING после group_by

Симптом: неправильные результаты или OperationalError.

Причина: filter() добавляет условие WHERE (до агрегации). Для фильтрации по результатам агрегации нужен having().

# НЕПРАВИЛЬНО: filter после group_by не даёт нужного результата
session.query(User.age, func.count(User.id))
    .group_by(User.age)
    .filter(func.count(User.id) > 1)  # некорректно!

# ПРАВИЛЬНО:
session.query(User.age, func.count(User.id))
    .group_by(User.age)
    .having(func.count(User.id) > 1)  # HAVING

Ошибки при JOIN

Ошибка 8: Перепутать join и outerjoin

Симптом: в результатах нет пользователей без адресов (или наоборот — NULL-строки, когда они не нужны).

Причина: join() — INNER JOIN (только совпадения с обеих сторон). outerjoin() — LEFT OUTER JOIN (все строки из левой таблицы).

# join — только пользователи С адресами:
session.query(User.name, Address.description).join(User.addresses)

# outerjoin — ВСЕ пользователи, у кого нет адреса Address.id будет NULL:
session.query(User.name).outerjoin(User.addresses).filter(Address.id.is_(None))

Ошибка 9: Не вызвать create_all перед первой сессией

Симптом: OperationalError: no such table: users.

Причина: объявление класса-модели в Python не создаёт таблицу в БД. Нужно явно вызвать create_all.

# НЕПРАВИЛЬНО: сессия до create_all
with Session(engine) as session:
    session.add(User(name="Alice", age=25))  # OperationalError!

# ПРАВИЛЬНО:
Base.metadata.create_all(engine)  # сначала создать таблицы
with Session(engine) as session:
    session.add(User(name="Alice", age=25))
    session.commit()