lazy='raise' is the equivalent of a test with assertNumQueries: if someone forgets eager loading, they get an exception instead of 1,000 hidden queries.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | # ====================================================================== # Q: Write a model and a query with SQLAlchemy 2.0 without N+1. # # lazy='raise' is the equivalent of a test with assertNumQueries: if someone # forgets eager loading, they get an exception instead of 1,000 hidden queries. # joinedload suits many-to-one (a single JOIN). selectinload suits one-to-many # (a second query with WHERE id IN (...)), because a JOIN would multiply the # rows. # ====================================================================== from sqlalchemy import ForeignKey, String, select from sqlalchemy.orm import (DeclarativeBase, Mapped, mapped_column, relationship, selectinload, joinedload) class Base(DeclarativeBase): ... class Customer(Base): __tablename__ = 'customers' id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(200)) orders: Mapped[list['Order']] = relationship(back_populates='customer', lazy='raise') # N+1 -> exception class Order(Base): __tablename__ = 'orders' id: Mapped[int] = mapped_column(primary_key=True) customer_id: Mapped[int] = mapped_column(ForeignKey('customers.id'), index=True) total: Mapped[int] # in cents customer: Mapped[Customer] = relationship(back_populates='orders') stmt = (select(Customer) .options(selectinload(Customer.orders)) # 2 queries in total .where(Customer.name.ilike('a%')) .order_by(Customer.id)) customers = session.scalars(stmt).all() |