MySQL中实现“小计”与“合计”

最近经常在工作中遇到统计数据报告场景,其中要求某个分组的小计或合计,在此记录下MySQL中实现小计的方法。

场景描述

假设我们有一张销售业绩表 sales_records,包含字段:部门 (dept)、员工 (user)、销售额 (amount)。我们需要统计:

  1. 每个部门下每个员工的销售额(明细)。
  2. 每个部门的销售额(部门小计)。
  3. 全公司的总销售额(总计)。

一、 MySQL 中的实现方案

建表与初始化数据

sql 复制代码
-- 创建测试表
CREATE TABLE sales_records (
    id INT AUTO_INCREMENT PRIMARY KEY,
    dept VARCHAR(50) NOT NULL COMMENT '部门',
    user_name VARCHAR(50) NOT NULL COMMENT '员工姓名',
    amount DECIMAL(10, 2) NOT NULL COMMENT '销售额'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 插入测试数据
INSERT INTO sales_records (dept, user_name, amount) VALUES
('研发部', '张三', 1000.00),
('研发部', '李四', 2000.00),
('研发部', '王五', 1500.00),
('销售部', '赵六', 3000.00),
('销售部', '钱七', 4000.00),
('市场部', '孙八', 2500.00);

方法一:UNION ALL

sql 复制代码
-- 1. 明细统计
SELECT dept, user_name, SUM(amount) as total_amount, 1 as sort_type
FROM sales_records
GROUP BY dept, user_name

UNION ALL

-- 2. 部门小计
SELECT dept, '小计' as user_name, SUM(amount), 2 as sort_type
FROM sales_records
GROUP BY dept

UNION ALL

-- 3. 总计
SELECT '总计' as dept, '小计' as user_name, SUM(amount), 3 as sort_type
FROM sales_records

ORDER BY FIELD(dept, '总计') , dept, sort_type, user_name;
  • UNION ALL: 将多个查询结果集合并
  • FIELD(dept, '总计'):当 dept 是"总计"时返回 1,否则返回 0

执行结果:

bash 复制代码
+----+---------+------------+---------+
|dept|user_name|total_amount|sort_type|
+----+---------+------------+---------+
|市场部 |孙八       |2500.00     |1        |
|市场部 |小计       |2500.00     |2        |
|研发部 |张三       |1000.00     |1        |
|研发部 |李四       |2000.00     |1        |
|研发部 |王五       |1500.00     |1        |
|研发部 |小计       |4500.00     |2        |
|销售部 |赵六       |3000.00     |1        |
|销售部 |钱七       |4000.00     |1        |
|销售部 |小计       |7000.00     |2        |
|总计  |小计       |14000.00    |3        |
+----+---------+------------+---------+

方法二:ROLLUP

WITH ROLLUP 的作用是在 GROUP BY 的分组基础上,自动向上层级进行聚合。WITH ROLLUP 的名字来源于"卷轴"。它的逻辑非常直观:从最细粒度的分组开始,一级一级向上卷动计算,直到最顶层。

假设你的 SQL 是:

GROUP BY A, B, C WITH ROLLUP

数据库内部会实际上帮你执行以下 4 种 组合的聚合(按顺序):

  1. GROUP BY A, B, C (最细粒度:明细)
  2. GROUP BY A, B (去掉 C:按 A,B 小计)
  3. GROUP BY A (去掉 B:按 A 小计)
  4. GROUP BY () (去掉 A:总计)
sql 复制代码
SELECT
    dept,
    user_name,
    SUM(amount) as total_amount
FROM
    sales_records
GROUP BY
    dept, user_name
WITH ROLLUP;

执行结果

bash 复制代码
+----+---------+------------+
|dept|user_name|total_amount|
+----+---------+------------+
|市场部 |孙八       |2500.00     |
|市场部 |null     |2500.00     |
|研发部 |张三       |1000.00     |
|研发部 |李四       |2000.00     |
|研发部 |王五       |1500.00     |
|研发部 |null     |4500.00     |
|销售部 |赵六       |3000.00     |
|销售部 |钱七       |4000.00     |
|销售部 |null     |7000.00     |
|null|null     |14000.00    |
+----+---------+------------+
  • GROUP BY dept, user_name WITH ROLLUP: MySQL 会先按 (dept, user_name) 分组,然后按 (dept) 分组(即 user_name 为 NULL),最后全表聚合(dept 和 user_name 均为 NULL)。

优化:使用 GROUPING 函数优化

sql 复制代码
SELECT
    IF(GROUPING(dept), '总计', dept) AS dept,
    IF(GROUPING(user_name), '小计', user_name) AS user_name,
    SUM(amount) as total_amount
FROM
    sales_records
GROUP BY
    dept, user_name
WITH ROLLUP;
  • 小计和总计行显示为 NULL,显示不友好。在 MySQL 8.0 中,我们可以使用 GROUPING() 函数来判断当前行是否是聚合行,并结合if函数进行替换。

执行结果

sql 复制代码
+----+---------+------------+
|dept|user_name|total_amount|
+----+---------+------------+
|市场部 |孙八       |2500.00     |
|市场部 |小计       |2500.00     |
|研发部 |张三       |1000.00     |
|研发部 |李四       |2000.00     |
|研发部 |王五       |1500.00     |
|研发部 |小计       |4500.00     |
|销售部 |赵六       |3000.00     |
|销售部 |钱七       |4000.00     |
|销售部 |小计       |7000.00     |
|总计  |小计       |14000.00    |
+----+---------+------------+

方式三:使用窗口函数

sql 复制代码
SELECT
    dept AS 部门,
    user_name AS 员工姓名,
    amount AS 销售额,
    SUM(amount) OVER (PARTITION BY dept) AS 部门销售额小计,
    ROUND(amount * 100.0 / SUM(amount) OVER (PARTITION BY dept), 2) AS 占部门比例,
    SUM(amount) OVER () AS 公司销售额总计,
    ROUND(amount * 100.0 / SUM(amount) OVER (), 2) AS 占公司比例
FROM sales_records
ORDER BY
    部门销售额小计 DESC,
    销售额 DESC;
  • SUM(...) OVER(PARTITION BY dept): 这里的"小计"是附在每一行后面的。它不会减少行数,只是增加列。这在做"占比分析"时比较有用。

执行结果

bash 复制代码
+---+----+-------+-------+------+--------+-----+
|部门 |员工姓名|销售额    |部门销售额小计|占部门比例 |公司销售额总计 |占公司比例|
+---+----+-------+-------+------+--------+-----+
|销售部|钱七  |4000.00|7000.00|57.14 |14000.00|28.57|
|销售部|赵六  |3000.00|7000.00|42.86 |14000.00|21.43|
|研发部|李四  |2000.00|4500.00|44.44 |14000.00|14.29|
|研发部|王五  |1500.00|4500.00|33.33 |14000.00|10.71|
|研发部|张三  |1000.00|4500.00|22.22 |14000.00|7.14 |
|市场部|孙八  |2500.00|2500.00|100.00|14000.00|17.86|
+---+----+-------+-------+------+--------+-----+
相关推荐
短剑重铸之日3 分钟前
《7天学会Redis》Day 5 - Redis Cluster集群架构
数据库·redis·后端·缓存·架构·cluster
007php0073 分钟前
mySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据
数据库·redis·git·mysql·面试·职场和发展·php
lkbhua莱克瓦246 分钟前
进阶-存储过程3-存储函数
java·数据库·sql·mysql·数据库优化·视图
老邓计算机毕设43 分钟前
SSM心理健康系统84459(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·心理健康系统·在线咨询
碎像44 分钟前
10分钟搞定 MySQL 通过Binlog 数据备份和恢复
数据库·mysql
+VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue小型房屋租赁系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
win x1 小时前
Redis 分布式锁
数据库·redis·分布式
2501_944521001 小时前
rn_for_openharmony商城项目app实战-商品评价实现
javascript·数据库·react native·react.js·ecmascript·harmonyos
冉冰学姐2 小时前
SSM心理健康系统59q3n(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架应用·心理健康系统·心理文章
heartbeat..2 小时前
零基础学 SQL:DQL/DML/DDL/DCL 核心知识点汇总(附带连接云服务器数据库教程)
java·服务器·数据库·sql