SQLAlchemy 2.0 model and query without N+1 (selectinload, lazy='raise')

Backend Live Coding November 13, 2026 python

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.

python
 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()