Ошибки при работе с сессией
Ошибка 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()