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|
+---+----+-------+-------+------+--------+-----+
相关推荐
瀚高PG实验室3 小时前
PostgreSQL到HighgoDB数据迁移
数据库·postgresql·瀚高数据库
打码人的日常分享4 小时前
智能制造数字化工厂解决方案
数据库·安全·web安全·云计算·制造
三水不滴4 小时前
Redis 过期删除与内存淘汰机制
数据库·经验分享·redis·笔记·后端·缓存
-孤存-5 小时前
MyBatis数据库配置与SQL操作全解析
数据库·mybatis
2301_822366356 小时前
使用Scikit-learn构建你的第一个机器学习模型
jvm·数据库·python
万邦科技Lafite7 小时前
一键获取京东商品评论信息,item_reviewAPI接口指南
java·服务器·数据库·开放api·淘宝开放平台·京东开放平台
自可乐7 小时前
Milvus向量数据库/RAG基础设施学习教程
数据库·人工智能·python·milvus
weixin_307779137 小时前
C#实现两个DocumentDB实例之间同步数据
开发语言·数据库·c#·云计算
盒马coding7 小时前
深度解密MySQL2PG工具MySQL至PostgreSQL语法全景拆解过程
数据库·mysql·postgresql
tb_first8 小时前
万字超详细苍穹外卖学习笔记2
java·jvm·数据库·spring·tomcat·maven