SQLPathLab
SQL 实战指南 · MySQL 8.0

JOIN 后数据为什么重复或减少

从结果粒度、连接键唯一性和未匹配行三个方向定位 JOIN 行数异常。

问题与判断方法

JOIN 的第一步不是选择 INNER 还是 LEFT,而是定义结果粒度:结果中的一行究竟代表客户、订单,还是客户与订单的组合。只有先写清这句话,才能判断连接后的行数是正确展开还是意外放大。

连接键的基数决定重复方式。一对多连接会让一侧记录按匹配数重复,多对多连接会把两侧匹配数相乘;因此客户表与订单表连接后,同一客户出现多行并不一定是错误,但若后续仍按客户金额汇总,就可能重复计算。

结果减少通常来自连接类型或过滤位置。INNER JOIN 会去掉未匹配行;LEFT JOIN 虽然保留左表,但在 WHERE 中筛选右表字段又会去掉 NULL 行。排查时分别统计总行数、COUNT(DISTINCT 业务主键) 和右表主键为 NULL 的数量。

如果目标是一行一个客户,应在连接前把订单聚合到客户粒度,或在连接后明确 GROUP BY 客户字段。若目标是一行一个订单,则应保留订单主键并接受客户信息重复出现。

不要用 DISTINCT 作为默认修复。DISTINCT 只能删除完全相同的结果行,既可能掩盖错误连接,也不能阻止金额等非重复字段被重复汇总。

示例

示例数据表

表名:customers
customers
idname
101 Alice
102 Bob
103 Cindy
表名:orders
orders
customer_id
101
102
101
104

优化前 / 不推荐

SELECT c.id, c.name FROM customers c JOIN orders o ON c.id = o.customer_id ORDER BY c.id;

优化前运行结果

result
idname
101 Alice
101 Alice
102 Bob

优化后 / 推荐

SELECT c.id, c.name, COUNT(o.id) AS order_count FROM customers c LEFT JOIN orders o ON c.id = o.customer_id GROUP BY c.id, c.name ORDER BY c.id;

优化后运行结果

result
idnameorder_count
101 Alice 2
102 Bob 1
103 Cindy 0

为什么这样更好:前者的粒度是一行一笔匹配订单,所以 Alice 出现两次且 Cindy 消失;后者明确为一行一个客户,并把订单数聚合到相同粒度。

这篇内容是否清楚?