MySQL/SQL中SUM聚合求和时如何排除重复行得到正确结果
交易表重复数据导致金额求和偏差修复方案
问题核心逻辑
- 重复数据判定规则:同一
cashier_id(收银员ID)下,folio(单据号)相同的记录属于重复行,统计时仅需计算1次 - 原查询缺陷:直接对全表数据做聚合,重复行的金额被多次累加,导致求和结果、笔数统计值偏大;同时原SQL存在语法错误:
cash_amount聚合行末尾多了1个多余逗号,执行会触发语法报错 - 示例偏差说明:测试数据中
cashier_id=1下folio=0001(金额2500)重复2次,原查询统计结果为6000,去重后正确结果应为3500;cashier_id=2无重复数据,统计值10000为正确结果
可用修复写法
写法1:通用兼容写法(适配所有SQL版本)
先通过DISTINCT按「收银员+单据号」维度完成去重,再基于去重后的结果集做分组聚合,逻辑简单兼容性强,适合绝大多数场景:
SELECT t.cashier_id AS cashier_id, SUM(t.cash_amount) AS cash_amount, COUNT(0) AS ticket_number, t.date AS date FROM ( -- 子查询先完成去重,重复单据仅保留1条有效记录 SELECT DISTINCT cashier_id, folio, cash_amount, DATE(created_at) AS date FROM `jysparki_jis`.`api_transactions` WHERE DATE(created_at) >= '2022-01-01' AND dte_type_id IN (39, 61) AND cashier_id <> 0 ) t GROUP BY t.cashier_id, t.date
写法2:窗口函数写法(支持MySQL8.0+、PostgreSQL等支持窗口函数的环境)
如果重复行存在内容差异(比如重复上传的单据有状态、更新时间区别),可以用ROW_NUMBER()自定义保留规则,比如保留最新创建的记录:
SELECT cashier_id, SUM(cash_amount) AS cash_amount, COUNT(0) AS ticket_number, DATE(created_at) AS date FROM ( SELECT *, -- 按收银员+单据号分组,按创建时间倒序,最新的记录排第1位 ROW_NUMBER() OVER (PARTITION BY cashier_id, folio ORDER BY created_at DESC) AS rn FROM `jysparki_jis`.`api_transactions` WHERE DATE(created_at) >= '2022-01-01' AND dte_type_id IN (39, 61) AND cashier_id <> 0 ) t WHERE rn = 1 -- 仅保留每个分组下排序第1的有效记录 GROUP BY cashier_id, DATE(created_at)
避坑说明
- 禁止直接使用
SUM(DISTINCT cash_amount)实现去重求和:如果同一收银员下存在两个不同单据金额恰好相等,会被误判为重复数据剔除,导致求和结果偏小 - 单据笔数统计
COUNT(0)必须放在去重逻辑之后计算,否则会把重复行统计为多笔交易,导致笔数指标失真 - 过滤条件中
dte_type_id的多值判断用IN (39,61)写法替代多个OR判断,可读性更强、执行效率一致
内容的提问来源于stack exchange,提问作者Junior Developer
相关产品推荐
相关产品推荐

