Skip to content

066. 关系预加载写法与 SQL 阅读

学习目标

这一节继续讲 SQLModel 的关系预加载。

学完后,你应该能理解:

  • joinedload 里逗号和点号的区别;
  • 同一张用户表为什么能表示不同业务角色;
  • 如何从生成 SQL 判断关联条件;
  • 用 ORM 关系统计项目已花费金额的局限;
  • joinedloadselectinload 的基本差异。

逗号和点号

预加载参数里有两种常见写法。

逗号表示同时预加载主表上的多个关系。

例如查询审批单时,同时查:

  • 审批单所属项目;
  • 审批单申请人。

点号表示沿着某个关系继续往下查。

例如查询审批单时,先查项目,再查项目负责人。

这两个写法表达的不是同一层关系。

如果字段本身在主表上,用逗号分隔。

如果要查关联表内部的关系,用点号继续访问。

同一张表可以有多个角色

审批单里会出现用户。

项目里也会出现用户。

它们都来自用户表,但业务含义不同:

  • 项目的负责人;
  • 审批单的申请人。

所以关联同一张用户表时,不能只看表名。

要看关联条件。

例如:

sql
project.leader_id = user.id

表示项目负责人。

sql
approve.user_id = user.id

表示审批申请人。

看 SQL 判断关系

ORM 代码很方便,但也容易变成黑盒。

复杂关系查询一定要看生成的 SQL。

重点看:

  • 主表是哪张;
  • join 了哪些表;
  • ON 后面的条件是什么;
  • 同一张表是否被重复关联;
  • 字段顺序和别名是否符合预期。

只有看到真实 SQL,才能判断 ORM 是否按业务关系在查。

项目已花费金额

项目可以关联多张审批单。

审批单里有金额和状态。

如果只统计已通过审批的金额,就可以在项目模型上做一个计算属性。

逻辑类似:

py
total = 0
for item in approve_list:
    if item.status == "approved":
        total += item.amount
return total

这样业务层访问项目时,可以直接拿到项目已花费金额。

ORM 统计的代价

这种做法能跑通,但实现不轻。

它需要:

  • 定义项目和审批单关系;
  • 预加载审批单列表;
  • 一对多结果去重;
  • 在 Python 对象里计算金额;
  • 再确认结果和 SQL 聚合一致。

如果只是做统计查询,一条 SQL 可能更直接。

尤其是要分页、筛选、按统计结果排序时,ORM 关系属性会变得吃力。

分页和排序限制

项目已花费金额是查出来以后再算的。

所以如果想按这个金额排序,就不能简单依赖 Python 里的计算属性。

原因是数据库分页和排序发生在查询阶段。

而计算属性是在数据回到 Python 之后才出现。

如果接口需要按统计字段分页或排序,更适合写聚合 SQL。

关系预加载

joinedload 可以理解成关联预加载。

它通过 join 把关联数据一次查出来。

优点是直观,适合需要根据关联字段筛选或排序的场景。

缺点是 SQL 会变大。

一对多关系还会导致主表行膨胀,需要 unique 去重。

选择预加载

selectinload 可以理解成选择预加载。

它通常不会把所有表塞进一条大 join。

它会先查主表,再根据主表 ID 用 IN 查询关联数据。

查询结果对业务层来说可以一样。

但 SQL 执行方式不同。

大数据量场景下,选择预加载经常比一条大 join 更稳。

这一节的重点

这一节把关系预加载从“会写”推进到“会判断”。

你需要记住:

  • 主表关系用逗号;
  • 关联表里的关系用点号;
  • 同一张表可能承担多个业务角色;
  • 复杂 ORM 查询要看生成 SQL;
  • 统计字段如果要分页排序,优先考虑 SQL 聚合;
  • joinedloadselectinload 查询结果类似,但 SQL 形态不同。

AI Agent 课程学习文档。