数据库多表查询:从课堂笛卡尔积到业务实践

把 Oracle 课堂笔记升级成备忘:笛卡尔积人话解释,电商/SaaS 场景下 JOIN 怎么用,一人公司写接口时的坑。

数据库多表查询:从课堂笛卡尔积,到电商 / SaaS 里的真实用法

很早以前学 Oracle,课堂笔记只写了几句:多表查询的理论基础是笛卡尔积;行数相乘、列数相加;尽量别用「笛卡尔全集」,因为里面错误组合爆炸。

这些话没错,但太抽象。后来做业务系统、独立站后台、SaaS 类工具,才反复遇到:表一定会拆,查询一定会关联;写错一个 JOIN 条件,轻则慢查询,重则数据串了。

这篇把课堂笔记升级成个人备忘。

先把笛卡尔积说成人话

两表无条件组合,就是所有可能配对。1000 个用户 × 1000 个订单,中间结果就是一百万行——其中绝大多数配对毫无业务意义。

所以多表查询的本质不是「把表拼起来」,而是:在巨大的可能组合里,用键(通常是外键)只留下有意义的关系。

真实业务里,表为什么要拆

以简化电商为例:

  • users:用户
  • orders:订单
  • order_items:订单明细
  • products:商品

为什么不做成一张宽表?因为订单会多次购买同一商品、商品信息会变、用户会有多笔订单。拆表是为了减少冗余、保证更新一致。代价就是:列表页、详情页、后台报表,几乎都要多表查询。

SaaS 也一样:accounts(租户)、members、subscriptions、invoices——权限与计费一出来,JOIN 就是日常。

几种关联,对应什么业务问题

  1. 内连接(INNER JOIN)

「只要发生过关系的行」。例如:查有订单的用户。没下过单的用户不会出现。适合成交分析,不适合「全体用户运营名单」。

  1. 左连接(LEFT JOIN)

「左表全保留,右表没有就空着」。例如:用户列表附带最近一笔订单;没有订单的用户也要展示。后台很多列表都是这个模式。

  1. 一对多时的聚合

订单对明细是一对多。直接 JOIN 再 SELECT *,行数会被明细放大。更常见是:先按订单聚合金额/件数,再回表;或在应用层分两次查。一人公司写接口时,这个坑特别常见——页面突然翻倍、分页错乱,往往是一对多 JOIN 没处理。

  1. 反连接(NOT EXISTS / LEFT JOIN IS NULL)

「找出没有某类记录的人」。例如:注册了但从没创建项目的用户。这类查询对增长和召回很有用。

我在业务里守的几条规矩

  • JOIN 条件写清楚,且尽量走索引字段。

ON a.user_id = b.user_id 这种;避免对字段包函数再关联。

  • 先想结果粒度,再写 SQL。

你要的是「每用户一行」还是「每订单一行」?粒度想错,后面怎么修都别扭。

  • 警惕隐式笛卡尔积。

FROM a, b WHERE ... 老写法里,漏条件就爆炸。现代 SQL 更推荐显式 JOIN ... ON ...。

  • 报表和在线接口分开看。

后台报表可以慢一点、可以预聚合;用户请求路径上的多表查询要控时间,必要时上缓存或冗余字段。

  • 读写场景不同,模型可以不同。

交易库保持范式;展示层用视图、缓存表、甚至文档型投影。独立开发也别害羞做「一点冗余」——正确的冗余能换稳定性。

一个极简示例(电商订单列表)

需求:后台看订单列表,显示订单号、用户昵称、商品件数、支付金额。

思路:

  • 订单表为主(一行一单);
  • JOIN 用户表取昵称;
  • 明细表用聚合得到件数;
  • 金额如果已落在订单表就别从明细反复算(下单时写入更稳)。

课堂只教「能连上」;业务要的是「连对、粒度对、性能可接受」。

写给过去的自己

笛卡尔积不是要你背公式,而是提醒:无约束的组合是废墟,有约束的组合才是业务。 今天做 shopagg、建站工具或任何有账号体系的产品,多表查询每天都在。把笔记留成这样,以后自己查也更快。

No comments yet