当前位置: 首页 > news >正文

SQL示例:获得积分最多的人,求和操作与去重的关系

本文分析了SQL查询中DISTINCT关键字错误使用导致积分计算错误的问题。


原SQL在子查询t1中使用DISTINCT导致重复积分记录被合并,使得最终求和结果偏小。


正确解法应移除DISTINCT,直接使用GROUP BY进行分组求和。


文章强调去重操作应根据业务需求决定:若重复记录代表独立业务事件(如多次交易)则不应去重;若为数据重复则需去重。


最佳实践是明确业务需求后,选择使用SUM()或SUM(DISTINCT)进行聚合计算。


题目:SQL290 获得积分最多的人(三)

描述

牛客每天有很多用户刷题,发帖,点赞,点踩等等,这些都会记录相应的积分。

有一个用户表(user),简况如下:

idname
1tm
2wwy
3zk
4qq
5lm

还有一个积分表(grade_info),简况如下:

user_idgrade_numtype
13add
23add
11reduce
33add
43add
53add
31reduce

第1行表示,user_id为1的用户积分增加了3分。

第2行表示,user_id为2的用户积分增加了3分。

第3行表示,user_id为1的用户积分减少了1分。

.......

最后1行表示,user_id为3的用户积分减少了1分。

请你写一个SQL查找积分最高的用户的id,名字,以及他的总积分是多少(可能有多个),查询结果按照id升序排序,以上例子查询结果如下:

idnamegrade_num
2wwy3
4qq3
5lm3

解释:

user_id为1和3的先加了3分,但是后面又减了1分,他们2个是2分,

其他3个都是3分,所以输出其他三个的数据。

错误解法

with t1 as( select distinct id, name, case when type='add' then grade_num else -grade_num end as new_grade from user u join grade_info g on u.id=g.user_id ), t2 as( select distinct id, name, sum(new_grade) over(partition by id) as score from t1 ) select id, name, score as grade_num from t2 where score=(select max(score) from t2) order by id;

主要问题分析

1.DISTINCT导致重复行被错误合并

t1中:

sql

select distinct id, name, case when type='add' then grade_num else -grade_num end as new_grade

如果同一个(id, name, new_grade)组合有多条记录,DISTINCT会合并成1行,导致t2中该分数只被计算一次,而不是多次。


示例:

  • 用户A:add+5,add+5,add+5

  • 正确总和:+15

  • 你的SQL:DISTINCT后只有1行 +5,总和变成 +5 ❌

2.t2中再次使用DISTINCT多余

t2DISTINCT也不必要,因为SUM(...) OVER(PARTITION BY id)已经为每个id生成相同的score值。

执行顺序

sql

t1: FROM → JOIN → CASE → DISTINCT → 生成 t1 t2: FROM t1 → 窗口函数 SUM() → DISTINCT → 生成 t2

真正的问题

t1DISTINCT在聚合之前去重 → 会导致丢失重复的new_grade记录→ 求和结果偏小

t2DISTINCT在聚合之后去重 → 此时每个idscore已经确定,去重只是去掉重复行 →不影响求和结果

正确的执行顺序

SQL的执行逻辑是:

  1. 先执行 FROM/JOIN→ 生成原始行

  2. 再执行 CASE→ 计算new_grade

  3. 然后执行 DISTINCT→ 对(id, name, new_grade)去重

  4. 最后执行t2中的窗口函数 SUM() OVER()→ 对去重后的结果求和

所以 t1 的DISTINCT确实在 t2 的求和之前,会影响结果。

如果业务上确保不会出现重复记录(比如有唯一约束),那么DISTINCT不会造成问题。

正确解法

with t1 as( select id, name, case when type='add' then grade_num else -grade_num end as new_grade from user u join grade_info g on u.id=g.user_id ), t2 as( select id, name, sum(new_grade) as score from t1 group by id,name ) select id, name, score as grade_num from t2 where score=(select max(score) from t2) order by id;

求和操作与去重的关系


-- 情况1:DISTINCT 在聚合函数外面 SELECT DISTINCT id, SUM(score) OVER() FROM table -- 执行顺序:先 SUM(),后 DISTINCT ✅ -- 情况2:DISTINCT 在子查询/CTE 里面 WITH t1 AS (SELECT DISTINCT ...) SELECT SUM(...) OVER() FROM t1 -- 执行顺序:先 DISTINCT(在子查询),后 SUM() ✅ -- 情况3:DISTINCT 作为聚合函数的参数 SELECT SUM(DISTINCT score) FROM table -- 执行顺序:先去重,后求和 ✅

不是“不能去重”,而是“去重会改变业务含义”,需要根据业务需求决定。


两种情况对比

1. 业务上需要去重(正确使用)

sql

-- 场景:每个用户每天只记录一次加分,但可能重复上报 SELECT DISTINCT user_id, score FROM records; -- 或者 SELECT user_id, SUM(DISTINCT score) FROM records GROUP BY user_id;

2. 业务上不需要去重(错误使用)

sql

-- 场景:记录每次真实交易,重复意味着多次交易 SELECT DISTINCT user_id, amount FROM transactions; -- ❌ 会丢失交易次数

各种聚合函数的去重规则

函数是否支持 DISTINCT说明
SUM()SUM(DISTINCT col)去重后求和
COUNT()COUNT(DISTINCT col)去重后计数
AVG()AVG(DISTINCT col)去重后平均
MAX()/MIN()❌ 不需要去重不影响结果
窗口函数SUM() OVER()❌ 不支持 DISTINCT必须在子查询中预先去重

你的SQL问题本质

sql

-- t1 中的 DISTINCT 在t2 的聚合之前执行 with t1 as( select distinct id, name, ... -- ❌ 去重发生在求和之前 )

这不是“聚合函数不能去重”,而是“在错误的时间点去重”。

正确做法:

sql

-- 方案1:明确需要去重,在聚合函数内使用 SUM(DISTINCT new_grade) -- 方案2:不需要去重,删除 DISTINCT -- 直接保留所有行

判断标准:问自己一个问题

“重复记录代表重复的业务事件吗?”

  • (如:多次下单、多次加分)→不能去重

  • (如:重复上报、数据错误)→可以去重


最佳实践

sql

-- ❌ 危险写法(容易出错) SELECT DISTINCT id, SUM(amount) OVER(PARTITION BY id) -- ✅ 清晰写法 SELECT id, SUM(amount) FROM table GROUP BY id -- ✅ 明确去重(业务明确需要) SELECT id, SUM(DISTINCT amount) FROM table GROUP BY id

核心原则:去重是业务决策,不是技术习惯。

http://www.jsqmd.com/news/785646/

相关文章:

  • 观察Taotoken在应对不同时段API请求压力时的稳定性表现
  • 从树状LSTM到神经符号计算:结构化表示与可解释推理的技术演进
  • CANN驱动DCMI自定义信息查询
  • ChatGPT编程能力实测:Kattis平台15%通过率揭示AI代码生成局限
  • 10分钟自动化部署OpenClaw AI助手:基于Ubuntu VPS的完整实践指南
  • 光纤稳定平台动态误差仿真系统GUI设计与实现【附程序】
  • 纵列式双旋翼无人机动力学建模与控制仿真【附模型】
  • 卫星通信遇到“太空天气”会怎样---电离层闪烁对卫星通信的影响
  • P4 猴痘病识别
  • Layui上传组件upload怎么监听大文件上传的百分比进度条
  • Flutter for OpenHarmony 跨平台开发:待办事项功能实战指南
  • CANN/AMCT创建蒸馏模型API
  • 开源OSINT终端Horus:构建本地优先的实时态势感知驾驶舱
  • 本地AI技能安全运行:基于MCP协议与沙盒隔离的Mac离线自动化方案
  • React:useTransition 超详细教程、为什么有了 Fiber,React 默认更新依然会卡顿?useDeferredValue超详细教程
  • ViGEmBus内核驱动深度解析:从系统架构到高级配置的完整技术指南
  • Scikit-learn:从问题到模型——监督学习的最小闭环
  • 将docx博客草稿转化为适于博客园发布的markdown文件
  • AI赋能可持续发展:从技术祛魅到实践审辨
  • CANN/asc-devkit:AlltoAllvWrite集合通信API
  • AI与Web 3.0深度融合:联邦学习、智能合约与AI代理的架构实践
  • 成都钢板代理商|专注西南板材一站式批发|获取盛世钢联免费钢板报价 - 四川盛世钢联营销中心
  • 海信扩大3C智能硬件版图,底气来自哪里?
  • 下肢外骨骼五连杆模型辨识与运动控制器设计【附仿真】
  • Webpack:Webpack 核心配置、什么是 Loader? 什么是plugin?webpack 构建流程
  • CANN/PTO-ISA文档导航
  • 昇腾CANN/ge常量折叠特性分析
  • AI赋能人才分析:从数据治理到模型落地的实战指南
  • 构式语法与人工智能融合:从可解释AI到具身智能体的语言理解新范式
  • AI金融研究13年文献计量分析:热点算法、应用场景与未来趋势