MySQL统计主表及关联子表行数:结果不准确及查询优化诉求
解决关联表统计行数不准的问题并优化查询
这问题我太熟了!你的原查询之所以统计不准,核心原因是左连接产生的笛卡尔积导致重复计数——一个a记录对应多个b,一个b又对应多个c,结果集里同一a/b/c的id会重复出现,COUNT()会把这些重复项都算进去,自然数值就偏大了。下面给你两种解决方案,以及对应的优化思路:
方案1:快速修复——使用COUNT(DISTINCT)去重
最简单的修复方式是给COUNT()加上DISTINCT关键字,确保每个id只被统计一次:
SELECT DATE(a.create_time) AS date, COUNT(DISTINCT a.id) AS total_a, COUNT(DISTINCT b.id) AS total_b, COUNT(DISTINCT c.id) AS total_c FROM `a` LEFT JOIN `b` ON `a`.`no` = `b`.`no` LEFT JOIN `c` ON `b`.`id` = `c`.`b_id` WHERE `a`.`status` = 1 GROUP BY DATE(a.create_time); -- 按日期分组是统计每日数据的必要操作,原语句应该遗漏了这部分
优缺点:
- ✅ 优点:写法简单,快速解决问题
- ❌ 缺点:当表数据量较大时,
DISTINCT需要对id进行去重排序,会增加数据库的计算开销,性能下降明显。
方案2:性能优化——子查询预统计(推荐)
更高效的方式是先分别统计各表的对应数据,再关联结果,从根源上避免笛卡尔积的产生。这种方式适合数据量大的场景:
方式A:按日期预统计各表数据
SELECT a_stats.date, a_stats.total_a, COALESCE(b_stats.total_b, 0) AS total_b, -- 用COALESCE处理NULL,返回0而不是空值 COALESCE(c_stats.total_c, 0) AS total_c FROM ( -- 先统计a表每天的有效记录数 SELECT DATE(create_time) AS date, COUNT(id) AS total_a FROM a WHERE status = 1 GROUP BY DATE(create_time) ) AS a_stats LEFT JOIN ( -- 统计关联a后,每天的b表记录数 SELECT DATE(a.create_time) AS date, COUNT(b.id) AS total_b FROM a LEFT JOIN b ON a.no = b.no WHERE a.status = 1 GROUP BY DATE(a.create_time) ) AS b_stats ON a_stats.date = b_stats.date LEFT JOIN ( -- 统计关联a->b后,每天的c表记录数 SELECT DATE(a.create_time) AS date, COUNT(c.id) AS total_c FROM a LEFT JOIN b ON a.no = b.no LEFT JOIN c ON b.id = c.b_id WHERE a.status = 1 GROUP BY DATE(a.create_time) ) AS c_stats ON a_stats.date = c_stats.date;
方式B:按关联键预统计(性能更优)
如果a.no和b.no是稳定的关联键,还可以先按no统计b和c的数量,再和a表关联,进一步减少结果集大小:
SELECT DATE(a.create_time) AS date, COUNT(a.id) AS total_a, COALESCE(SUM(b_count.total_b), 0) AS total_b, -- 对同一日期的b数量求和 COALESCE(SUM(c_count.total_c), 0) AS total_c FROM a LEFT JOIN ( -- 先统计每个no对应的b记录数 SELECT no, COUNT(id) AS total_b FROM b GROUP BY no ) AS b_count ON a.no = b_count.no LEFT JOIN ( -- 先统计每个no对应的c记录数(通过b关联) SELECT b.no, COUNT(c.id) AS total_c FROM b LEFT JOIN c ON b.id = c.b_id GROUP BY b.no ) AS c_count ON a.no = c_count.no WHERE a.status = 1 GROUP BY DATE(a.create_time);
优缺点:
- ✅ 优点:避免了大笛卡尔积,子查询的结果集更小,计算效率更高;不需要使用
DISTINCT,减少排序开销 - ✅ 扩展性好:后续要加其他统计维度,直接新增子查询即可
额外优化建议:添加索引
不管用哪种方案,添加合适的索引都能大幅提升查询速度:
- 给a表添加复合索引:
CREATE INDEX idx_a_status_create_time_no ON a(status, create_time, no); - 给b表添加索引:
CREATE INDEX idx_b_no_id ON b(no, id); - 给c表添加索引:
CREATE INDEX idx_c_b_id ON c(b_id);
这些索引能让数据库快速过滤数据、关联表和分组,减少磁盘IO和计算时间。
内容的提问来源于stack exchange,提问作者Serhat
相关产品推荐
相关产品推荐

