Skip to content

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

学习目标 ​

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

学完后,你应该能理解:

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

逗号和点号 ​

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

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

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

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

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

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

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

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

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

同一张表可以有多个角色 ​

审批单里会出现用户。

项目里也会出现用户。

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

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

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

要看关联条件。

例如:

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 聚合;
  • joinedload 和 selectinload 查询结果类似,但 SQL 形态不同。

AI Agent 课程学习文档。