0Pricing
Coding Interview Prep · 课时

连接扩张与行数增加

了解连接为何可能返回比任一表更多的行,以及面试官如何考查这一点

连接扩张与行数增加 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。

连接返回过多行时

有一道很能体现水平的面试题听起来很简单:“连接返回的行数可能比较大的那张表还多吗?”答案是可以,这种现象称为扇出或行倍增。

如果候选人回答“连接只是合并表”,就说明他们没有意识到这一点。能够准确预测行数的候选人更容易被录用。本课将帮助您建立这种预测能力。

原因:一对多匹配

当左侧的一行匹配右侧的多行时,就会发生扇出。每次匹配都会生成一行独立的输出。

以客户和订单为例,Ada(一位客户)有两笔订单。连接会为每笔订单输出一行,因此 Ada 会出现两次。客户字段会重复,只有订单字段不同。

SELECT c.name, o.amount
FROM customers c
JOIN orders o ON o.customer_id = c.id;
-- Ada appears twice (she has 2 orders)
-- name | amount
-- Ada  | 50
-- Ada  | 20
-- Bob  | 99

计算输出行数

输出行数等于每个左表行的匹配数之和,而不是客户数量。

  • Ada -> 2 笔订单 -> 2 行
  • Bob -> 1 笔订单 -> 1 行
  • Cleo -> 0 笔订单 -> 0 行(被 INNER JOIN 丢弃)

总计为 3 行,尽管 customers 也有 3 行。将 Ada 的订单数改为 10 笔后,结果会增加到 11 行。

多对多会造成爆炸式增长

当两侧对于同一个键都有多个匹配项时,扇出会叠加。如果键 K 在左侧出现 3 次、在右侧出现 4 次,那么对于这个键,连接会生成 3 x 4 = 12 行。

这就是一个看似很小的连接会膨胀为数百万行的原因。面试官很喜欢在两侧都给出重复键,观察您能否发现这种倍增。

-- left has 3 rows with tag 'A', right has 4 rows with tag 'A'
SELECT l.id, r.id
FROM left_t l
JOIN right_t r ON r.tag = l.tag;
-- tag 'A' alone yields 3 * 4 = 12 output rows

聚合陷阱

这是面试官最常设置的一种错误。您将订单与订单明细连接以获取商品详情,然后对订单金额执行 SUM。由于每笔订单会扇出为多行商品明细,订单金额就会按每件商品重复计数。

此时 SUM 的结果会严重膨胀。查询看起来正确,甚至能够正常运行,这正是它危险的地方。

-- BUG: order.amount duplicated across items
SELECT SUM(o.amount) AS total
FROM orders o
JOIN order_items i ON i.order_id = o.id;
-- a 3-item order counts o.amount 3 times

观察数值膨胀

假设一笔订单的金额为 100,并包含三条订单明细。连接会生成三行,每行都带有金额 100。SUM(o.amount) 返回的是 300,而不是 100。

修复方法是按照正确的粒度进行聚合:对商品明细求和,或单独对去重后的订单求和。绝不要在扇出后的子表连接上对父表值执行 SUM。

o.id | o.amount | i.id
7    | 100      | 71
7    | 100      | 72
7    | 100      | 73
-- SUM(o.amount) = 300  (WRONG, should be 100)

修复方法 1:先聚合子表

最简洁的修复方法是,在子查询或 CTE 中预先聚合多的一侧,使每个父项恰好匹配一行汇总结果。这样既不会扇出,也不会导致数值膨胀。

这里会先将明细压缩为每笔订单一行,再执行连接,因此父表金额不会重复。

SELECT o.id, o.amount, i.item_count
FROM orders o
JOIN (
  SELECT order_id, COUNT(*) AS item_count
  FROM order_items
  GROUP BY order_id
) i ON i.order_id = o.id;

修复方法 2:COUNT(DISTINCT) 和条件求和

如果必须在扇出连接之后进行聚合,就要按照正确的粒度计数或求和。使用 COUNT(DISTINCT o.id) 可以统计订单数,而不是订单明细行数。

请注意:SUM(DISTINCT o.amount) 并不是安全的修复方法,因为不同订单可能合法地拥有相同金额,这样会被合并。预聚合更加可靠。

SELECT COUNT(DISTINCT o.id)   AS num_orders,
       COUNT(i.id)            AS num_items
FROM orders o
JOIN order_items i ON i.order_id = o.id;

在扇出造成影响前发现它

面试官喜欢的一种快速诊断方法是:检查您预期为“一”的一侧,其连接键是否唯一。如果不重复键的数量小于行数,说明这一侧存在重复项,并会造成扇出。

-- if this returns rows, order_id is NOT unique in order_items
SELECT order_id, COUNT(*) AS n
FROM order_items
GROUP BY order_id
HAVING COUNT(*) > 1;

使用计数验证数据粒度

在信任任何基于连接结果的聚合之前,请先检查行数是否合理。一种快速方法是,将连接后的行数与您预期作为数据粒度的表的行数进行比较。

如果连接上的 COUNT(*) 大于 orders 表的 COUNT(*),说明连接发生了扇出,任何按订单进行的聚合都可能出错。这项一行的检查拯救过许多面试回答。

-- joined rows should equal order count if no fan-out
SELECT COUNT(*) AS joined_rows
FROM orders o
JOIN order_items i ON i.order_id = o.id;

SELECT COUNT(*) AS order_rows FROM orders;
-- joined_rows > order_rows  =>  fan-out present

扇出不一定是错误

有时您确实希望每个子项对应一行。例如,列出每条订单明细及其订单头信息就是正确的扇出。关键在于了解目标粒度:每个实体应该产生多少行?

请在编写查询前先说明粒度。“我希望每个订单明细一行”与“我希望每个订单一行”会决定扇出究竟是功能还是错误。

快速检查

预测一对多连接的输出结果。

回顾:扇出与行倍增

需要记住的内容:

  • 连接会为每个匹配的行对输出一行,因此一对多匹配会使“一”这一侧重复。
  • 多对多键会产生倍增:对于该键,3 x 4 = 12 行。
  • 在扇出连接上聚合父表值会使求和和计数膨胀。
  • 可以通过预先聚合子表修复,或者按照正确的粒度进行计数或求和(例如 COUNT(DISTINCT))。
  • 始终先说明预期的粒度;只有当扇出违背了预期粒度时,它才是错误。

常见问题解答

「连接扩张与行数增加」课时是免费的吗?

是的 — 「连接扩张与行数增加」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。

「连接扩张与行数增加」这节课中我会学到什么?

了解连接为何可能返回比任一表更多的行,以及面试官如何考查这一点 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Coding Interview Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「连接扩张与行数增加」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Coding Interview Prep 课中编写并运行代码吗?

能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. INNER JOIN 如何匹配行
  2. 连接中的 ON 与 WHERE
  3. 连接扩张与行数增加
  4. 连接三个或更多表
← 返回 Coding Interview Prep