You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQLAlchemy 2.x中如何限制每个QuizTopic仅返回3条关联Quiz记录

在SQLAlchemy 2.x中限制关联查询的返回数量

问题分析

当前用joinedload加载关联数据时,会拉取每个QuizTopic下的全部Quiz记录。SQLAlchemy的常规关联加载不支持直接给单组关联数据设数量限制,得靠子查询或窗口函数实现「按topic_id分组取前3条」的逻辑。

解决方案1:用窗口函数子查询筛选目标记录

通过子查询先拿到每个topic_id对应的前3条Quiz的ID,再关联查询只加载这些匹配的记录,适合一次性批量加载场景:

修改orm.py中的查询代码:

async def get_content_topics(session: AsyncSession, limit: int, continue_after: int):
    # 子查询:给每个topic下的quiz编号,取前3条的id
    quiz_subquery = (
        select(
            Quiz.id,
            # 按topic_id分组,按quiz的id排序(可换成你需要的排序字段,比如创建时间)
            func.row_number().over(
                partition_by=Quiz.topic_id,
                order_by=Quiz.id
            ).label("row_num")
        )
    ).subquery()

    smt = (
        select(QuizTopic)
        .order_by(QuizTopic.id)
        .offset(continue_after).limit(limit)
        .options(
            load_only(QuizTopic.id, QuizTopic.title),
            # 用contains_eager加载筛选后的quiz
            contains_eager(QuizTopic.quizzes_info).load_only(
                Quiz.id, Quiz.logo_url, Quiz.title, Quiz.meta
            )
        )
        .join(QuizTopic.quizzes_info)
        .join(quiz_subquery, Quiz.id == quiz_subquery.c.id)
        .filter(quiz_subquery.c.row_num <= 3)
    )
    result = await session.scalars(smt)
    return result.unique().all()

解决方案2:动态加载关联数据(按需查询)

如果不需要一次性加载所有关联数据,可以修改关系的加载模式,在拿到QuizTopic后单独查询前3条Quiz,适合小批量topic场景:

首先修改models.py中的关系定义:

class QuizTopic(Base):
    __tablename__ = "quiz_topic"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    title: Mapped[str] = mapped_column(String(60))

    quizzes_info: Mapped[list["Quiz"]] = relationship(
        back_populates="topic_info",
        cascade="all, delete",
        passive_deletes=True,
        lazy='dynamic'  # 改为动态加载,返回Query对象而非直接返回列表
    )

然后修改查询函数:

async def get_content_topics(session: AsyncSession, limit: int, continue_after: int):
    smt = (
        select(QuizTopic)
        .order_by(QuizTopic.id)
        .offset(continue_after).limit(limit)
        .options(load_only(QuizTopic.id, QuizTopic.title))
    )
    topics = await session.scalars(smt)
    topics_list = topics.unique().all()
    
    # 给每个topic单独取前3条quiz
    for topic in topics_list:
        topic.quizzes_info = await topic.quizzes_info.limit(3).options(
            load_only(Quiz.id, Quiz.logo_url, Quiz.title, Quiz.meta)
        ).all()
    return topics_list

注意事项

  • 方案1的窗口函数需要数据库支持(如PostgreSQL、MySQL 8.0+),旧版本数据库需用其他方式实现分组取前N条。
  • 方案2会产生N+1查询(每个topic对应一条查询),topic数量多时性能会受影响,谨慎使用。
  • 排序字段可根据业务需求调整,比如换成Quiz.create_time.desc()来取最新的3条记录。

内容的提问来源于stack exchange,提问作者TheJecksMan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 13:07:41