最近由于需要准备找工作,写项目的时候又对mysql过于疏忽,导致只会用但理解不是很深入,所以又找了本书去回顾mysql和数据库理论知识,学到回溯统计的时候感觉比较绕,故记录一下。
什么是回溯统计?
回溯统计 是指在标准的分组统计基础上,额外生成一层层的“小计”和“总计”行。它提供了一种多层次的、类似数据透视表或Excel中分类汇总的汇总视图。
回溯统计的步骤,以公司不同部门部门岗位的工资为例
先按照最细的维度分组并计算聚合值(如: 每个部门下的每个岗位的工资和)。
然后“回溯”一步,计算上一个维度的合计(如: 每个部门下所有岗位的工资和)。
最后“回溯”到最顶层,计算所有部门岗位的全局总计。
语法
在mysql中,回溯统计的核心语法是 WITH ROLLUP
(当然,不区分大小写)。但是要配合GROUP BY
一起使用。
因为
WITH ROLLUP
是GROUP 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;
结果
b) 使用 WITH ROLLUP
回溯统计
SELECT department, role, SUM(salary) AS total_salary
FROM employee
GROUP BY department, role WITH ROLLUP;
结果
第2,5,8行是通过
rollup
生成的部门级别的小计,它“回溯”了role
列,所有role
列在此显示为NULL
,total_salary
是该部门下的所有岗位的薪资总和。最后一行是
rollup
生成的全局总计,他“回溯”了所有分组列(department
和role
),所以这两列都显示为NULL
,tatal_salary
是所有记录的总和。可以观察到“回溯”的顺序是
从后向前
,也就是先“回溯”的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;
结果
可以看到
当
dept_rollup_flag = 0
且role_rollup_flag = 1
时,是部门小计。当
dept_rollup_flag = 1
且role_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;
结果
这样输出的结果非常清晰易懂。
小结与一些要点
目的:
WITH ROLLUP
用于在分组聚合的基础上,生成分层的小计和总计行。实现:在
GROUP BY
子句末尾加上WITH ROLLUP
。标识:回溯生成的行,其分组列的值会显示为
NULL
。歧义处理:使用
GROUPING(column)
函数来准确区分回溯产生的NULL
和原始数据的NULL
。排序:
ROLLUP
生成的行会出现在其所汇总的细粒度分组之后,最后是全局总计。性能:
ROLLUP
会增加查询的计算开销,因为它需要计算多个层次的聚合。对于大数据集,需要考虑性能影响。与
ORDER BY
:不能使用ORDER BY
对ROLLUP
的结果进行排序,因为NULL
值的位置是固定的。如果需要排序,通常使用应用程序代码来处理。
多说一些
简单理解回溯
回溯就是一步步去除分组统计的字段然后再进行一次统计。
WITH ROLLUP
的工作方式就像一个 逐层剥离维度 的过程:
最细粒度:
GROUP BY department, role
使用的维度:
department
+role
统计:每个(部门, 岗位)组合的薪资和。
第一次回溯 (Rollup):
GROUP BY department, NULL
(概念上)去除的维度:
role
使用的维度:只剩下
department
统计:每个部门下所有岗位的薪资和(即部门小计)。
第二次回溯 (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;
结果
示例 2:按部门和岗位分组 (GROUP BY department, role)
SELECT department, role, COUNT(*) AS count
FROM employee
GROUP BY department, role;
结果
最后一句
本文只是自己对于回溯的理解,如有偏颇请指出,感谢阅读。