📖 Контекст практикума

Модели User + Address · 20 задач · SQLAlchemy 2.x совместимый API

⚡ Контекст практикума

Все задачи используют два класса: User(id, name, age) и Address(id, description, user_id). Связь: один User — много Address (one-to-many). Запросы идут через session.query().

  • Блок 1 (задачи 1–10): базовые CRUD + сортировка + limit + exists
  • Блок 2 (задачи 11–15): func.avg/max/min, group_by, having, subquery
  • Блок 3 (задачи 16–20): join, outerjoin, group_by+join, filter по связи, обновление адреса

Схема базы данных практикума

Все 20 задач работают с двумя связанными таблицами:

# Модели (SQLAlchemy 2.x, декларативный стиль)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy import String, Integer, ForeignKey
from typing import List, Optional

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = 'users'
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(50))
    age: Mapped[int] = mapped_column(Integer)
    addresses: Mapped[List["Address"]] = relationship(
        "Address", back_populates="user", cascade="all, delete-orphan"
    )

class Address(Base):
    __tablename__ = 'addresses'
    id: Mapped[int] = mapped_column(primary_key=True)
    description: Mapped[Optional[str]] = mapped_column(String(255))
    user_id: Mapped[int] = mapped_column(ForeignKey('users.id'))
    user: Mapped["User"] = relationship("User", back_populates="addresses")
Примечание о session.query(): в задачах практикума используется session.query() — это «legacy API», совместимый со всеми версиями SQLAlchemy. Новый стиль SQLAlchemy 2.x использует select() и session.execute(). Сравнение см. в Старый vs Новый.

Тестовые данные для практикума

Чтобы запросы возвращали результаты, нужны данные. Типичный набор:

from sqlalchemy import create_engine
from sqlalchemy.orm import Session

engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)

with Session(engine) as session:
    users = [
        User(name="Alice", age=25),
        User(name="Bob", age=30),
        User(name="Charlie", age=22),
        User(name="Diana", age=35),
        User(name="Eve", age=28),
    ]
    session.add_all(users)
    session.flush()  # назначить id без commit

    addresses = [
        Address(description="Berlin", user=users[0]),
        Address(description="Paris", user=users[1]),
        Address(description="Berlin", user=users[2]),
        Address(description="London", user=users[3]),
        # users[4] (Eve) — без адреса
    ]
    session.add_all(addresses)
    session.commit()

Три группы задач

Блок 1: Базовые запросы (задачи 1–10)

Охватывают фундаментальные операции CRUD, сортировку и ограничения на количество результатов:

  • Поиск (.filter(), .first(), .all())
  • Обновление (получить объект → изменить поле → commit())
  • Добавление (session.add() + commit())
  • Удаление (session.delete() + commit())
  • Сортировка (order_by()), ограничение (limit())
  • Поиск по id (session.get())
  • Проверка существования (.exists())

Блок 2: Агрегации (задачи 11–15)

Используют func из sqlalchemy:

  • func.avg() — среднее значение
  • func.max() / func.min() — максимум/минимум
  • func.count() + group_by() — группировка с подсчётом
  • having() — фильтрация по результатам агрегации
  • subquery() — вложенный запрос как источник данных

Блок 3: Связи и JOIN (задачи 16–20)

Работа с отношением User–Address:

  • join() — INNER JOIN (только пользователи с адресами)
  • outerjoin() — LEFT OUTER JOIN (включая пользователей без адресов)
  • Фильтрация по полям связанной таблицы
  • Обновление адреса через атрибут user.addresses[0]