🦏 第10章 多表查询
📄 一句话概括

笛卡儿积就是「每一行跟对方每一行都配一次」:12 个学生 × 3 个班 = 36 行。 JOIN 后面不写 ON,等价于 CROSS JOIN,都是笛卡儿积。 所以「少写一个 on」不报错,只让结果从 12 行变成 36 行。 行数按乘法涨:两张 1000 行的表是 100 万行,三张就是 10 亿行。 数据库不拦你,只会慢慢跑。

📝 本章和前面几章的关系

前两篇都是「按条件配对」,这一篇讲「不配对」——听起来像错误,其实是个正经的运算。 它 99% 的出场是事故(漏写条件),剩下 1% 是「我就要全集」(算组合、造日历)。 这一篇一半讲怎么防它,一半讲什么时候该用。

一、笛卡儿积是什么:m × n 行

FROM students, classes 不写条件,给 36 行。 12 × 3,每一行学生跟每一行班级都配了一次,不管有没有关系。 看前六行就明白了:张伟配一班、二班、三班,然后王芳配一班、二班、三班。 这叫「笛卡儿积」,也叫「交叉连接」。 它的数学定义就是「所有可能的组合」, 所以行数一定是两张表行数的乘积——这一点可以用来快速判断「是不是连错了」。

二、漏写配对条件,就是笛卡儿积

JOIN 后面不写 ON,等价于 CROSS JOIN,都是 36 行。 所以「少写一个 on」不会报错,只会让结果从 12 行变成 36 行。 这是多表查询最常见的事故: 报表数字突然大了三倍,代码看着没问题——就是少了个 on。 而且它特别难发现:数字变大不像报错那样刺眼,往往要等业务方来问。 排查办法很简单: 先 count(*) 一下,行数对不上就是连错了。 更稳的办法是「连完再 count 一次主表」, 两次数字必须一样——不一样就说明配对丢了或者炸了。

三、行数按乘法涨,很容易炸

两张 1000 行的表做笛卡儿积是 100 万行;三张就是 10 亿行。 数据库不会拦你,它只会慢慢跑,跑到磁盘满或者超时。 最危险的不是「表大」,是「表不大但连了四张」: 每张 100 行,四张就是 1 亿行。 四张各 100 行的表看着都小,组合起来能压死服务器。 所以「多表查询前先数行数」是个好习惯: 12 × 3 还能算,1000 × 1000 × 1000 就该先想清楚条件了。 写完 SQL 先 EXPLAIN 看一眼也行,第 12 章会讲。

四、少数情况下,笛卡儿积真的有用

一是算组合:「所有班级 × 所有科目」做一张完整的成绩表骨架, 再用左连接把实际成绩填进去,没考的留 NULL。 这种「先造全集、再补数据」的手法在报表里很常见。 二是造日历:「所有日期 × 所有门店」,再左连接实际销量, 就能得到「某天某店销量为 0」的行——不用它的话,没销量的那天根本不会出现在结果里。 这两种用法的共同点是「我就要全集」,而不是「我忘了写条件」。 写的时候加一句注释说明意图,以后没人会以为是 bug。 这一句注释能省掉未来某个人的半小时。

五、写笛卡儿积的两种姿势

CROSS JOIN 显式写出来,意图最清楚; 逗号写法 FROM a, b 更短,但和「忘了写 where」长得一模一样,容易误读。 所以真要笛卡儿积,就用 CROSS JOIN。 这样任何人扫一眼就知道「这是故意的」,不用去数 where 里少了什么。

六、动手 Demo:36 行是怎么来的,以及少写 on 的后果

Demo 四段:先 count 一下笛卡儿积的行数、看前六行长什么样; 再证明「JOIN 不写 ON」和「CROSS JOIN」都是 36 行;然后算行数怎么涨,最后讲它什么时候真有用。 第三段那个「36 行」是这一篇的核心数字——记住它,以后看到行数变成乘积就知道是漏了条件。

import _q46 as Q
c = Q.con()

# 笛卡儿积:12 x 3 = 36 行
Q.show(c, "SELECT COUNT(*) AS n FROM students, classes")
Q.show(c, "SELECT s.name AS 学生, c.name AS 班级 "
          "FROM students s, classes c LIMIT 6")

# 漏写 on 就是笛卡儿积;cross join 也是
Q.show(c, "SELECT COUNT(*) AS n FROM students JOIN classes")
Q.show(c, "SELECT COUNT(*) AS n FROM students CROSS JOIN classes")

实跑输出:

--------------------------------------------------------------------
第五十六课:笛卡儿积——不配对,直接全配
--------------------------------------------------------------------

  笛卡儿积是什么:m × n 行
  SQL> SELECT COUNT(*) AS n FROM students, classes
  n
  ---
  36
  (1 行)
  **12 × 3 = 36 行。** **每一行学生跟每一行班级都配了一次,不管有没有关系。**
  SQL> SELECT s.name AS 学生, c.name AS 班级 FROM students s, classes c LIMIT 6
  学生     班级
  --------------
  张伟     一班
  张伟     二班
  张伟     三班
  王芳     一班
  王芳     二班
  王芳     三班
  (6 行)
  **前六行都是张伟**,配了一班、二班、三班,然后又来王芳配三个班。
  **这叫「笛卡儿积」,也叫「交叉连接」。**

  漏写配对条件,就是笛卡儿积
  SQL> SELECT COUNT(*) AS n FROM students JOIN classes
  n
  ---
  36
  (1 行)
  SQL> SELECT COUNT(*) AS n FROM students CROSS JOIN classes
  n
  ---
  36
  (1 行)
  **`JOIN` 后面不写 `ON`,等价于 `CROSS JOIN`,都是 36 行。**
  **所以「少写一个 `on`」不会报错,只会让结果从 12 行变成 36 行。**
  **这是多表查询最常见的事故**:
  **报表数字突然大了三倍,代码看着没问题——就是少了个 `on`。**
  **排查办法很简单:先 `count(*)` 一下,行数对不上就是连错了。**

  行数按乘法涨,很容易炸
  **两张 1000 行的表做笛卡儿积是 100 万行;三张就是 10 亿行。**
  **数据库不会拦你,它只会慢慢跑,跑到磁盘满或者超时。**
  **所以「多表查询前先数行数」是个好习惯**:
  **12 × 3 还能算,1000 × 1000 × 1000 就该先想清楚条件了。**

  少数情况下,笛卡儿积真的有用
  **一是算组合**:**「所有班级 × 所有科目」做一张完整的成绩表骨架**,
  **再用左连接把实际成绩填进去,没考的留 NULL。**
  **这种「先造全集、再补数据」的手法在报表里很常见。**
  **二是造日历**:**「所有日期 × 所有门店」,再左连接实际销量**,
  **就能得到「某天某店销量为 0」的行**——**不用它的话,没销量的那天根本不会出现在结果里。**
  **这两种用法的共同点是「我就要全集」**,
  **而不是「我忘了写条件」。** **写的时候加一句注释说明意图,以后没人会以为是 bug。**

  写笛卡儿积的两种姿势
  **`CROSS JOIN` 显式写出来**,意图最清楚;
  **逗号写法 `FROM a, b`** 更短,但和「忘了写 where」长得一样,**容易误读。**
  **所以真要笛卡儿积,就用 `CROSS JOIN`。**
  **这样任何人扫一眼就知道「这是故意的」,不用去数 where 里少了什么。**

第一段 36 行:12 × 3。 第二段前六行:张伟配三个班、王芳配三个班。 第三段和第四段都是 36 行:JOIN 不写 ON 与 CROSS JOIN 完全等价。 后三段没有 SQL,讲行数怎么炸、什么时候真该用笛卡儿积,以及为什么用 CROSS JOIN 而不用逗号。

30 秒实验

  1. SELECT COUNT(*) FROM students JOIN classes 给 36 而不是 12,最可能的原因是什么?
  2. 两张 500 行的表做笛卡儿积是多少行?三张呢?
  3. 「找出所有班级 × 所有科目、没考的留 NULL」,为什么要先笛卡儿积再左连接?

常见误区

  1. JOIN 忘了写 ON。 不报错,行数变成乘积,报表数字凭空变大,很难第一时间发现。
  2. 以为表小就没事。 四张各 100 行的表连起来是 1 亿行,小表也能把服务器压垮。
  3. 真要笛卡儿积却用逗号写法。 和「忘了写 where」长得一样,以后维护的人会当成 bug 去「修」。
自测三问(点开对答案) 1. 两表笛卡儿积的行数怎么算? —— 两张表行数的乘积。12 个学生 × 3 个班 = 36 行。 2. `JOIN` 不写 `ON` 会怎样? —— 不报错,等价于 `CROSS JOIN`,结果是两张表的笛卡儿积。 3. 什么场景下笛卡儿积是对的? —— 需要「全集」的时候:算所有组合、造日期乘门店的日历骨架,再接左连接补数据。

最后一种连法最绕:10.6 自连接——一张表跟自己连。

相关笔记