秦绍鹏
秦绍鹏
Published on 2025-10-17 / 3 Visits
0
0

mysql中的回溯统计到底是什么?根据自己的理解整理

最近由于需要准备找工作,写项目的时候又对mysql过于疏忽,导致只会用但理解不是很深入,所以又找了本书去回顾mysql和数据库理论知识,学到回溯统计的时候感觉比较绕,故记录一下。

什么是回溯统计?

回溯统计 是指在标准的分组统计基础上,额外生成一层层的“小计”和“总计”行。它提供了一种多层次的、类似数据透视表或Excel中分类汇总的汇总视图。

回溯统计的步骤,以公司不同部门部门岗位的工资为例

  1. 先按照最细的维度分组并计算聚合值(如: 每个部门下的每个岗位的工资和)。

  2. 然后“回溯”一步,计算上一个维度的合计(如: 每个部门下所有岗位的工资和)。

  3. 最后“回溯”到最顶层,计算所有部门岗位的全局总计。

语法

在mysql中,回溯统计的核心语法是 WITH ROLLUP (当然,不区分大小写)。但是要配合GROUP BY一起使用。

因为

WITH ROLLUPGROUP BY 子句的一个扩展选项。

语法使用示例

SELECT column1, column2, ..., aggregate_function(column)
FROM table_name
GROUP BY column1, column2, ... WITH ROLLUP;

举例说明

该举例和上述说的例子一致,以公司不同部门部门岗位的工资为例

创建表employee

CREATE TABLE employee (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  department VARCHAR(50),
  role VARCHAR(50),
  salary DECIMAL(10, 2)
);

INSERT INTO employee VALUES
(1, '张三', '销售部', '经理', 10000),
(2, '李四', '销售部', '专员', 6000),
(3, '王五', '销售部', '专员', 5500),
(4, '赵六', '技术部', '架构师', 15000),
(5, '孙七', '技术部', '开发', 9000),
(6, '周八', '技术部', '开发', 8000),
(7, '吴九', '人事部', '主管', 7000);

使用场景:想查看每个部门、每个岗位的薪资总和,同时还要看每个部门的总薪资和整个公司的总薪资。

a) 普通的分组查询(没有回溯)

SELECT department, role, SUM(salary) AS total_salary
FROM employee
GROUP BY department, role;

结果

department

role

total_salary

人事部

主管

7000.00

技术部

开发

17000.00

技术部

架构师

15000.00

销售部

专员

11500.00

销售部

经理

10000.00

b) 使用 WITH ROLLUP 回溯统计

SELECT department, role, SUM(salary) AS total_salary
FROM employee
GROUP BY department, role WITH ROLLUP;

结果

department

role

total_salary

人事部

主管

7000.00

人事部

NULL

7000.00

<-- 人事部的小计

技术部

开发

17000.00

技术部

架构师

15000.00

技术部

NULL

32000.00

<-- 技术部的小计

销售部

专员

11500.00

销售部

经理

10000.00

销售部

NULL

21500.00

<-- 销售部的小计

NULL

NULL

60500.00

<-- 整个公司的总计

  1. 第2,5,8行是通过rollup生成的部门级别的小计,它“回溯”了role列,所有role列在此显示为NULLtotal_salary是该部门下的所有岗位的薪资总和。

  2. 最后一行是rollup生成的全局总计,他“回溯”了所有分组列(departmentrole),所以这两列都显示为NULLtatal_salary是所有记录的总和。

  3. 可以观察到“回溯”的顺序是从后向前 ,也就是先“回溯”的role列,然后是department

如何正确识别回溯统计行?

从上面的结果可以看到,回溯统计行的分组列会使用NULL进行标记填充,这是识别例子中小计/总计行的关键,但是这里有一个重要的注意事项:如果原始数据本来就存在NULL值,它会被ROLLUP当作一个有效分组,这可能会造成混淆

为了更好区分,mysql提供了GROUPING() 函数。

使用GROUPING() 函数

GROUPING(cloumn_name) 函数可以明确指示某列中的NULL是因为回溯产生的,还是原始数据就存在的。

  • 返回1:表示该行的NULL是由ROLLUP生成的小计/总计标记。

  • 返回0:表示该行的NULL是原始数据中的值,或者是一个有效的分组值。

修改上次的查询语句:

SELECT
  department,
  role,
  SUM(salary) AS total_salary,
  GROUPING(department) AS dept_rollup_flag,
  GROUPING(role) AS role_rollup_flag
FROM employee
GROUP BY department, role WITH ROLLUP;

结果

department

role

total_salary

dept_rollup_flag

role_rollup_flag

人事部

主管

7000.00

0

0

人事部

NULL

7000.00

0

1

<-- 部门小计

技术部

开发

17000.00

0

0

技术部

架构师

15000.00

0

0

技术部

NULL

32000.00

0

1

<-- 部门小计

销售部

专员

11500.00

0

0

销售部

经理

10000.00

0

0

销售部

NULL

21500.00

0

1

<-- 部门小计

NULL

NULL

60500.00

1

1

<-- 全局总计

可以看到

  • dept_rollup_flag = 0role_rollup_flag = 1 时,是部门小计。

  • dept_rollup_flag = 1role_rollup_flag = 1 时,是全局总计。

还可以使用case或if语句再次优化代码:

SELECT
  IF(GROUPING(department), ‘所有部门’, department) AS ‘部门’,
  IF(GROUPING(role), ‘所有岗位’, role) AS ‘岗位’,
  SUM(salary) AS total_salary
FROM employee
GROUP BY department, role WITH ROLLUP;

结果

部门

岗位

total_salary

销售部

专员

11500.00

销售部

经理

10000.00

销售部

所有岗位

21500.00

技术部

开发

17000.00

技术部

架构师

15000.00

技术部

所有岗位

32000.00

人事部

主管

7000.00

人事部

所有岗位

7000.00

所有部门

所有岗位

60500.00

这样输出的结果非常清晰易懂。

小结与一些要点

  1. 目的WITH ROLLUP 用于在分组聚合的基础上,生成分层的小计和总计行。

  2. 实现:在 GROUP BY 子句末尾加上 WITH ROLLUP

  3. 标识:回溯生成的行,其分组列的值会显示为 NULL

  4. 歧义处理:使用 GROUPING(column) 函数来准确区分回溯产生的 NULL 和原始数据的 NULL

  5. 排序ROLLUP 生成的行会出现在其所汇总的细粒度分组之后,最后是全局总计。

  6. 性能ROLLUP 会增加查询的计算开销,因为它需要计算多个层次的聚合。对于大数据集,需要考虑性能影响。

  7. ORDER BY:不能使用 ORDER BYROLLUP 的结果进行排序,因为 NULL 值的位置是固定的。如果需要排序,通常使用应用程序代码来处理。

多说一些

简单理解回溯

回溯就是一步步去除分组统计的字段然后再进行一次统计。

WITH ROLLUP 的工作方式就像一个 逐层剥离维度 的过程:

  1. 最细粒度GROUP BY department, role

    • 使用的维度:department + role

    • 统计:每个(部门, 岗位)组合的薪资和。

  2. 第一次回溯 (Rollup)GROUP BY department, NULL (概念上)

    • 去除的维度role

    • 使用的维度:只剩下 department

    • 统计:每个部门下所有岗位的薪资和(即部门小计)。

  3. 第二次回溯 (Rollup)GROUP BY NULL, NULL (概念上)

    • 去除的维度department (和 role)

    • 使用的维度:无(全局)

    • 统计:所有部门所有岗位的薪资和(即全局总计)。

无意义的使用

如果没有使用聚合函数(如 SUM, COUNT, AVG, MAX 等),WITH ROLLUP 就失去了它存在的价值,并且由于 GROUP BY 本身的行为,会导致逻辑问题。

不同的 COUNT() 函数意义

1.没有 GROUP BY

如果只有 COUNT(*) 而没有 GROUP BY,它统计的是整个表的总行数。

SELECT COUNT(*) AS total_employees FROM employee;

结果: 返回一个数字,比如 7,代表 employee 表总共有 7 条记录。

2.有 GROUP BY

当你使用了 GROUP BY,数据库会先根据你指定的列将数据分成不同的“组”或“桶”。然后,COUNT(*) 会作用在每一个单独的组上,分别统计每个组里有多少条记录。

示例 1:按部门分组 (GROUP BY department)

SELECT department, COUNT(*) AS employee_count
FROM employee
GROUP BY department;

结果

department

employee_count

人事部

1

技术部

3

销售部

3

示例 2:按部门和岗位分组 (GROUP BY department, role)

SELECT department, role, COUNT(*) AS count
FROM employee
GROUP BY department, role;

结果

department

role

count

人事部

主管

1

技术部

开发

2

技术部

架构师

1

销售部

专员

2

销售部

经理

1

最后一句

本文只是自己对于回溯的理解,如有偏颇请指出,感谢阅读。


Comment