在开发业务报表时,SQL查询返回的数据出现重复行是非常常见的故障。通常表现为原本应该只有一条记录的订单、用户或交易,在报表中展现了多条,造成求和、计数等指标错误。要解决这类SQL报表数据重复问题,我们需要从产生重复的原因入手,再针对性地优化去重逻辑。
一、报表数据重复的常见原因
most报表重复数据并不是数据库存了重复记录,而是查询逻辑导致的。主要场景有以下几类:
- 多表LEFT JOIN时,右表存在一对多关系,导致左表一行被放大成多行。
- 使用了UNION但没有过滤重复,或UNION ALL后未做去重。
- GROUP BY字段选择不当,聚合粒度比业务主键更粗,掩盖了差异行。
二、基础去重:DISTINCT与GROUP BY
如果重复是整个行级别完全一致,最简单的是用DISTINCT。例如报表只需要不重复的用户ID列表:
-- 基础去重,去除完全重复的行 SELECT DISTINCT user_id, order_date FROM t_order_report WHERE order_date = '2023-10-01';
但当重复是因为关联放大引起的,DISTINCT可能效率很低。更推荐用GROUP BY明确聚合粒度:
-- 按订单主键分组,避免一对多关联带来的重复 SELECT o.order_id, o.user_id, SUM(p.pay_amount) AS total_pay FROM t_order o LEFT JOIN t_payment p ON o.order_id = p.order_id GROUP BY o.order_id, o.user_id;
三、关联前子查询去重优化
当右表一对多且只需取右表某个聚合值,应先在子查询里完成去重或聚合,再关联,减少放大:
-- 先对支付表按订单聚合,再关联订单表,避免重复行
SELECT o.order_id, a.paid_total
FROM t_order o
LEFT JOIN (
SELECT order_id, SUM(pay_amount) AS paid_total
FROM t_payment
GROUP BY order_id
) a ON o.order_id = a.order_id;
四、通过窗口函数定位重复
有时我们需要标记重复数据以便排查。可以用ROW_NUMBER()给同一业务主键的记录编号:
-- 给同一order_id内的记录编号,rn大于1即为重复
SELECT order_id, user_id,
ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY id) AS rn
FROM t_order_report;
| 去重方式 | 适用场景 | 性能注意点 |
|---|---|---|
| DISTINCT | 整行重复且量小 | 大数据量排序开销大 |
| GROUP BY | 需聚合指标 | 分组字段需含业务主键 |
| 子查询预聚合 | 一对多关联 | 减少关联行数最有效 |
五、总结
优化SQL报表去重逻辑,重点在于先弄清重复是存储问题还是查询放大问题。优先用子查询预聚合或正确GROUP BY控制粒度,谨慎使用DISTINCT。配合对执行计划的检查,才能既准确又高效地解决报表数据重复问题。