如何将同一发票中相同product_id的行合并为一行并删除其余行?
合并同一发票下相同产品的行(求和quantity和total)
我有如下订单明细表格:
| id | invoice_id | product_id | quantity | total |
|---|---|---|---|---|
| 1 | 5 | 10 | 2 | 100 |
| 2 | 5 | 10 | 1 | 50 |
| 3 | 5 | 11 | 1 | 200 |
| 4 | 5 | 11 | 1 | 200 |
我希望将同一发票(invoice_id相同)中拥有相同product_id的行进行合并:把这些行的quantity和total值相加后合并到其中一行(示例里保留了组内最小的id),然后删除其余重复的行。最终输出表格应如下所示:
| id | invoice_id | product_id | quantity | total |
|---|---|---|---|---|
| 1 | 5 | 10 | 3 | 150 |
| 3 | 5 | 11 | 2 | 400 |
我原本考虑使用一个SQL函数来返回拥有相同invoice_id和product_id的id列表,再对quantity和总价使用聚合函数。是否存在更简便的实现方式?
当然有更简洁的实现方式!完全不用写复杂的自定义函数,用基础的分组聚合或者窗口函数就能轻松搞定,分两种常用场景给你说明:
场景1:仅需查询合并后的结果(不修改原表)
如果只是想获取合并后的数据集,直接用GROUP BY搭配聚合函数就能实现,还能精准匹配你示例里保留组内最小id的需求:
SELECT MIN(id) AS id, invoice_id, product_id, SUM(quantity) AS quantity, SUM(total) AS total FROM your_table_name GROUP BY invoice_id, product_id ORDER BY id;
逻辑解释:
GROUP BY invoice_id, product_id:自动把"同发票+同产品"的行归为一组MIN(id):取每组里最小的id作为合并后的行ID(和你的示例输出完全匹配),如果想保留最大ID就换成MAX(id)SUM(quantity)和SUM(total):分别对每组的数量、总价做求和计算
场景2:需要修改原表(删除重复行,保留合并后的数据)
如果要直接修改原表,留下合并后的行并删除冗余行,可以用窗口函数ROW_NUMBER()标记每组的目标行,再进行更新和删除操作:
方式一:用CTE标记并操作
WITH ranked_rows AS ( SELECT *, -- 给每组的行按ID排序,标记序号 ROW_NUMBER() OVER ( PARTITION BY invoice_id, product_id ORDER BY id ASC ) AS rn, -- 提前计算每组的总数量和总价 SUM(quantity) OVER (PARTITION BY invoice_id, product_id) AS total_qty, SUM(total) OVER (PARTITION BY invoice_id, product_id) AS total_amt FROM your_table_name ) -- 先更新组内第一行的数量和总价为求和后的值 UPDATE ranked_rows SET quantity = total_qty, total = total_amt WHERE rn = 1; -- 再删除组内其他冗余行 DELETE FROM ranked_rows WHERE rn > 1;
方式二:临时表替换法(更兼容部分数据库)
如果你的数据库对CTE的更新/删除支持有限,可以用临时表中转:
-- 1. 将合并结果存入临时表 CREATE TABLE temp_merged AS SELECT MIN(id) AS id, invoice_id, product_id, SUM(quantity) AS quantity, SUM(total) AS total FROM your_table_name GROUP BY invoice_id, product_id; -- 2. 清空原表(注意备份数据!) TRUNCATE TABLE your_table_name; -- 3. 把合并后的数据插回原表 INSERT INTO your_table_name (id, invoice_id, product_id, quantity, total) SELECT * FROM temp_merged; -- 4. 可选:删除临时表 DROP TABLE temp_merged;
注意事项:
- 把
your_table_name替换成你实际的表名 - 操作原表前务必备份数据,避免误删
- 不同数据库(MySQL、PostgreSQL、SQL Server等)在语法细节上略有差异,比如CTE的更新权限、临时表语法,可以根据你的数据库调整
这种原生SQL的方式比自定义函数简单太多,性能也更优,完全能满足你的需求~
内容的提问来源于stack exchange,提问作者VishnuVS
相关产品推荐
相关产品推荐

