[特殊字符] 如何优雅地避免 SQL 多表 LEFT JOIN 造成的笛卡尔积放大问题?
在实际项目开发中,我们经常需要从多个数据表中统计和聚合项目相关数据。但如果处理不当,多表 LEFT JOIN 容易造成 数据行数异常放大 的问题,也就是我们常说的“笛卡尔积放大”。
本文通过一个简单示例,直观讲清问题产生的原因,并提供稳妥的解决方案。
🧩 问题场景
我们有如下三张表,结构如下:
a
表是项目表b
表是渠道B的数据c
表是渠道C的数据
表之间通过 项目编码(pr_code)
关联。
📊 示例数据如下:
a 表:
pr_code | 项目名称 |
---|---|
A001 | 项目A |
b 表(渠道B):
pr_code | 其他字段 |
---|---|
A001 | … |
A001 | … |
A001 | … |
共 3 条匹配记录
c 表(渠道C):
pr_code | 其他字段 |
---|---|
A001 | … |
共 1 条匹配记录
🧨 直接 LEFT JOIN 会出现什么问题?
我们尝试如下 SQL 来统计每个渠道的数量:
SELECT a.pr_code,COUNT(b.pr_code) AS b_count,COUNT(c.pr_code) AS c_count
FROM a
LEFT JOIN b ON a.pr_code = b.pr_code
LEFT JOIN c ON a.pr_code = c.pr_code
GROUP BY a.pr_code;
❗ 实际结果:
pr_code | b_count | c_count |
---|---|---|
A001 | 3 | 3 |
c_count 为什么变成 3?不是只有一条吗?
😱 根本原因:笛卡尔积
当你对 a
先和 b
做连接后,形成了 3 条记录,再与 c
的 1 条记录做连接时,每一条都匹配到了 c 的这 1 条记录,最终形成了 3 x 1 = 3 条记录。于是 c_count
也变成了 3,而不是预期的 1。
✅ 正确解决方案:各表先聚合,再合并
我们可以把每个表的数据先独立 GROUP BY
统计好,再用 UNION ALL
汇总所有渠道的数据,最后再聚合一次,就不会重复计算了。
✅ 示例 SQL:
SELECT pr_code,SUM(b_count) AS b_count,SUM(c_count) AS c_count,SUM(b_count + c_count) AS total
FROM (SELECT pr_code, COUNT(*) AS b_count, 0 AS c_countFROM bGROUP BY pr_codeUNION ALLSELECT pr_code, 0 AS b_count, COUNT(*) AS c_countFROM cGROUP BY pr_code
) AS combined
GROUP BY pr_code;
✅ 结果就正常了:
pr_code | b_count | c_count | total |
---|---|---|---|
A001 | 3 | 1 | 4 |
🧠 总结
- 多表 LEFT JOIN 时,如果子表有重复数据,连接后会放大行数。
- 不要直接对多表 LEFT JOIN 的结果做
COUNT(*)
,要先各自聚合,再合并统计。 - 通用处理方式是:
先分表统计 → UNION ALL 合并 → 最外层再 GROUP BY 聚合
✨ 建议应用场景
这种方式特别适用于:
- 多渠道汇总项目数量
- 多维度数据源汇总
- 保证每条来源数据只统计一次,避免重复