SQL 变换带有小计的结果集

SQL 变换带有小计的结果集,你想创建一个包含小计的报表,并希望把该结果重新格式化,使之更具可读性。例如,你已经按照要求创建了一张报表,其中包含每个部门的所有管理者,以及每个管理者下属所有员工的工资合计值。

SQL 变换带有小计的结果集 问题描述

你想创建一个包含小计的报表,并希望把该结果重新格式化,使之更具可读性。例如,你已经按照要求创建了一张报表,其中包含每个部门的所有管理者,以及每个管理者下属所有员工的工资合计值。同时,你也希望能看到两种额外的小计:去掉管理者之后、每个部门的所有下属员工的工资总额,以及上述结果集中所有工资的合计值(在部门工资小计的基础上再求和,得出总计)。现在,你得到的报表如下所示。

DEPTNO        MGR        SAL
------ ---------- ----------
    10       7782       1300
    10       7839       2450
    10                  3750
    20       7566       6000
    20       7788       1100
    20       7839       2975
    20       7902        800
    20                 10875
    30       7698       6550
    30       7839       2850
    30                  9400
                       24025

你想提供一个更具可读性的报表,因此希望把上述结果转换成如下所示的形式,以便更清楚明白地表达每个小计的含义。

MGR      DEPT10     DEPT20     DEPT30      TOTAL
---- ---------- ---------- ---------- ----------
7566          0       6000          0
7698          0          0       6550
7782       1300          0          0
7788          0       1100          0
7839       2450       2975       2850
7902          0        800          0
           3750      10875       9400      24025

SQL 变换带有小计的结果集 解决方案

首先使用 GROUP BYROLLUP 扩展生成小计,接着执行一次结果集变换(使用聚合运算和 CASE 表达式)以创建出新报表所需的列。GROUPING 函数能帮助我们方便地识别出哪些是小计(也就是说,小计都是由 ROLLUP 产生出来的,正常的 GROUP BY 无法生成小计)。由于不同的数据库对 Null 排序的处理方式各有不同,我们可能需要为某些解决方案增加 ORDER BY,以便最终的结果集和上述目标结果集相一致。
DB2 和 Oracle
使用 GROUP BYROLLUP 扩展,然后借助 CASE 表达式把数据格式化成更具可读性的报表。

 1  select mgr,
 2         sum(case deptno when 10 then sal else 0 end) dept10,
 3         sum(case deptno when 20 then sal else 0 end) dept20,
 4         sum(case deptno when 30 then sal else 0 end) dept30,
 5         sum(case flag when '11' then sal else null end) total
 6    from (
 7  select deptno,mgr,sum(sal) sal,
 8         cast(grouping(deptno) as char(1))||
 9         cast(grouping(mgr) as char(1)) flag
10    from emp
11   where mgr is not null
12   group by rollup(deptno,mgr)
13         ) x
14   group by mgr

SQL Server
使用 GROUP BYROLLUP 扩展,然后借助 CASE 表达式把数据格式化成更具可读性的报表。

 1  select mgr,
 2         sum(case deptno when 10 then sal else 0 end) dept10,
 3         sum(case deptno when 20 then sal else 0 end) dept20,
 4         sum(case deptno when 30 then sal else 0 end) dept30,
 5         sum(case flag   when '11' then sal else null end) total
 6    from (
 7  select deptno,mgr,sum(sal) sal,
 8         cast(grouping(deptno) as char(1))+
 9         cast(grouping(mgr)    as char(1)) flag
10    from emp
11   where mgr is not null
12   group by deptno,mgr with rollup
13         ) x
14   group by mgr

MySQLPostgreSQL
这两种数据库都不支持 GROUPING 函数。

SQL 变换带有小计的结果集 扩展知识

以上两种解决方案大致相同,但在字符串连接和 GROUPING 函数调用的方式上略有不同。由于它们非常相似,下面的讨论部分在展示中间结果的时候将以 SQL Server 解决方案为准(也会兼顾 DB2 和 Oracle)。
首先为每个 DEPTNO 的每个 MGR 计算出其下属员工的 SAL 合计值。基本的想法是希望打印出某个部门特定管理者下属所有员工的工资合计值。例如,下面的查询可以比较 KING 下属的 DEPTNO 10DEPTNO 30 的所有员工的工资。

select deptno,mgr,sum(sal) sal
  from emp
 where mgr is not null
 group by mgr,deptno
 order by 1,2
 
DEPTNO        MGR        SAL
------ ---------- ----------
    10       7782       1300
    10       7839       2450
    20       7566       6000
    20       7788       1100
    20       7839       2975
    20       7902        800
    30       7698       6550
    30       7839       2850

然后使用 GROUP BYROLLUP 扩展,计算每个 DEPTNO 对应的工资小计以及全体员工(不包括最高管理者)的工资总额。

select deptno,mgr,sum(sal) sal
  from emp
 where mgr is not null
 group by deptno,mgr with rollup
 
DEPTNO        MGR        SAL
------ ---------- ----------
    10       7782       1300
    10       7839       2450
    10                  3750
    20       7566       6000
    20       7788       1100
    20       7839       2975
    20       7902        800
    20                 10875
    30       7698       6550
    30       7839       2850
    30                  9400
                       24025

计算出了小计之后,我们需要知道如何判断哪些值是(由 ROLLUP 产生的)小计,哪些值是由正常的 GROUP BY 产生的结果。使用 GROUPING 函数创建位图可以把小计从正常的聚合运算值中分离出来。

select deptno,mgr,sum(sal) sal,
       cast(grouping(deptno) as char(1))+
       cast(grouping(mgr)    as char(1)) flag
  from emp
 where mgr is not null
 group by deptno,mgr with rollup
 
DEPTNO        MGR        SAL FLAG
------ ---------- ---------- ----
    10       7782       1300 00
    10       7839       2450 00
    10                  3750 01
    20       7566       6000 00
    20       7788       1100 00
    20       7839       2975 00
    20       7902        800 00
    20                 10875 01
    30       7698       6550 00
    30       7839       2850 00
    30                  9400 01
                       24025 11

如果要讲得更清楚明白一点的话,就是 FLAG 值等于 00 的行都是正常的聚合运算结果。FLAG 值等于 01 的行是由 ROLLUP 生成的、每个 DEPTNOSAL 合计值(因为 DEPTNOROLLUP 的第一个参数;如果变换一下顺序,例如改为“GROUP BY MGR, DEPTNO WITH ROLLUP”,结果会大相径庭)。FLAG 值等于 11 的行是由 ROLLUP 产生的、所有 SAL 的合计值。
现在,所需的数据都已经准备好了,接下来要使用 CASE 表达式把它们重新格式化为一个更直观的结果集。我们的目标是为跨部门的每位管理者显示其下属员工的工资合计值。如果一位管理者在某个部门没有下属员工,那就返回 0;否则,将返回该管理者在给定部门所有下属员工的工资合计值。除此之外,我们还要在报表的最后面增加一个 TOTAL 列以打印报表中全部工资的总计。满足上述所有要求的解决方案如下所示。

select mgr,
       sum(case deptno when 10 then sal else 0 end) dept10,
       sum(case deptno when 20 then sal else 0 end) dept20,
       sum(case deptno when 30 then sal else 0 end) dept30,
       sum(case flag   when '11' then sal else null end) total
  from (
select deptno,mgr,sum(sal) sal,
       cast(grouping(deptno) as char(1))+
       cast(grouping(mgr)    as char(1)) flag
  from emp
 where mgr is not null
 group by deptno,mgr with rollup
       ) x
 group by mgr
 order by coalesce(mgr,9999)
 
MGR      DEPT10     DEPT20     DEPT30      TOTAL
---- ---------- ---------- ---------- ----------
7566          0       6000          0
7698          0          0       6550
7782       1300          0          0
7788          0       1100          0
7839       2450       2975       2850
7902          0        800          0
           3750      10875       9400      24025

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程