SQL 多列求和先去重详解
这是一个非常实用的问题!当需要对多列求和,但某些列存在重复值时,需要先去除重复再进行求和。让我详细讲解几种常见场景和写法。
一、核心概念:什么时候需要先去重再求和?
| 场景 | 说明 | 示例 |
|---|---|---|
| 订单明细 | 同一订单有多条商品记录,但只想计算一次运费 | 订单A有3件商品,运费只应计算1次 |
| 用户统计 | 同一用户有多条登录记录,但只想计算一次VIP积分 | 用户一天登录10次,VIP积分只算1次 |
| 部门汇总 | 同一部门有多名员工,但部门预算只应算一次 | 部门预算不应按员工人数重复累加 |
二、常用解决方法
方法1:使用 DISTINCT + 子查询(最常用)
sql
SELECT SUM(DISTINCT 列名) AS 去重后求和FROM 表名;
注意:SUM(DISTINCT ...) 只能对单列去重求和。
sql
-- 示例:计算不重复的运费总额SELECT SUM(DISTINCT freight) AS 去重运费总额FROM order_details;
方法2:使用子查询先去重,再求和
适用于多列组合去重后求和。
sql
-- 示例:按订单ID去重后,计算运费总额SELECT SUM(freight) AS 去重运费总额FROM ( SELECT DISTINCT order_id, freight FROM order_details) t;
方法3:使用 ROW_NUMBER() 去重后求和(最精确)
适用于有复杂去重逻辑(如按某列分组,取最新记录)。
sql
-- 示例:每个订单只取最新一条记录的运费进行求和SELECT SUM(freight) AS 最新运费总额FROM ( SELECT order_id, freight, ROW_NUMBER() OVER(PARTITION BY order_id ORDER BY create_time DESC) rn FROM order_details) tWHERE rn = 1;
三、完整示例讲解
示例表结构
sql
-- 假设有一张订单明细表CREATE TABLE order_details ( id INT, order_id VARCHAR(20), -- 订单号 product_name VARCHAR(50), -- 商品名称 quantity INT, -- 数量 price DECIMAL(10,2), -- 单价 freight DECIMAL(10,2) -- 运费(同一订单多次出现,运费重复));
示例数据
id | order_id | product_name | quantity | price | freight |
1 | ORD001 | 手机 | 1 | 2999 | 10 |
2 | ORD001 | 手机壳 | 2 | 50 | 10 |
3 | ORD002 | 电脑 | 1 | 5999 | 20 |
4 | ORD002 | 鼠标 | 1 | 89 | 20 |
5 | ORD003 | 耳机 | 3 | 199 | 15 |
场景1:计算商品总金额(不去重)
sql
SELECT SUM(quantity * price) AS 商品总金额FROM order_details;
结果:1*2999 + 2*50 + 1*5999 + 1*89 + 3*199 = 2999 + 100 + 5999 + 89 + 597 = 9784
场景2:计算运费总额(不去重)
sql
SELECT SUM(freight) AS 运费总额FROM order_details;
结果:10 + 10 + 20 + 20 + 15 = 75(错误!ORD001的运费被算了2次,ORD002的运费被算了2次)
场景3:计算运费总额(先去重,正确写法)
sql
-- 方法A:使用 DISTINCT 子查询SELECT SUM(freight) AS 正确运费总额FROM ( SELECT DISTINCT order_id, freight FROM order_details) t;
结果:ORD001(10) + ORD002(20) + ORD003(15) = 45(正确)
sql
-- 方法B:使用 GROUP BY 子查询SELECT SUM(freight) AS 正确运费总额FROM ( SELECT order_id, freight FROM order_details GROUP BY order_id, freight) t;
场景4:多列组合去重后求和
假设需要计算:每个订单的运费 × 订单的某种系数,且同一订单的运费和系数都相同。
sql
-- 错误写法:直接求和会重复累加SELECT SUM(freight * coefficient) FROM order_details;-- 正确写法:先去重,再计算SELECT SUM(freight * coefficient) AS 去重后总额FROM ( SELECT DISTINCT order_id, freight, coefficient FROM order_details) t;
四、各数据库写法对比
| 数据库 | 去重求和写法 | 说明 |
|---|---|---|
| MySQL | SUM(DISTINCT freight) | 仅限单列 |
| SQL Server | SUM(DISTINCT freight) | 仅限单列 |
| PostgreSQL | SUM(DISTINCT freight) | 仅限单列 |
| Oracle | SUM(DISTINCT freight) | 仅限单列 |
| 所有数据库 | SELECT SUM(freight) FROM (SELECT DISTINCT order_id, freight FROM t) sub | 多列组合去重 |
五、性能优化建议
| 场景 | 推荐写法 | 原因 |
|---|---|---|
| 单列去重 | SUM(DISTINCT 列) | 简洁高效 |
| 多列组合去重 | 子查询 + DISTINCT | 逻辑清晰 |
| 大数据量 | 子查询 + GROUP BY | 比 DISTINCT 有时更优 |
| 复杂去重逻辑 | ROW_NUMBER() 窗口函数 | 最灵活精确 |
六、总结
| 问题 | 答案 |
|---|---|
| 单列去重求和怎么写? | SUM(DISTINCT 列名) |
| 多列组合去重求和怎么写? | SELECT SUM(列) FROM (SELECT DISTINCT 列1, 列2 FROM 表) t |
| 为什么要先去重? | 避免重复值被多次累加,导致结果错误 |
| 典型应用场景? | 运费计算、预算汇总、积分统计等 |
一句话总结:SQL 中进行多列求和时,如果需要先去掉重复值,单列可用 SUM(DISTINCT 列),多列组合则需要先用子查询(DISTINCT 或 GROUP BY)去重,再在外层求和,以避免重复数据被多次累加导致结果错误