SQL 计算所有可能的表达式组合的小计

SQL 计算所有可能的表达式组合的小计,你想按照 DEPTNOJOB 以及 JOB/DEPTNO 组合分别计算出工资合计值。同时,你也希望得到 EMP 表的工资总计。你希望得到如下所示的结果集。

SQL 计算所有可能的表达式组合的小计 问题描述

你想按照 DEPTNOJOB 以及 JOB/DEPTNO 组合分别计算出工资合计值。同时,你也希望得到 EMP 表的工资总计。你希望得到如下所示的结果集。

DEPTNO JOB       CATEGORY                  SAL
------ --------- --------------------- -------
    10 CLERK     TOTAL BY DEPT AND JOB    1300
    10 MANAGER   TOTAL BY DEPT AND JOB    2450
    10 PRESIDENT TOTAL BY DEPT AND JOB    5000
    20 CLERK     TOTAL BY DEPT AND JOB    1900
    30 CLERK     TOTAL BY DEPT AND JOB     950
    30 SALESMAN  TOTAL BY DEPT AND JOB    5600
    30 MANAGER   TOTAL BY DEPT AND JOB    2850
    20 MANAGER   TOTAL BY DEPT AND JOB    2975
    20 ANALYST   TOTAL BY DEPT AND JOB    6000
       CLERK     TOTAL BY JOB             4150
       ANALYST   TOTAL BY JOB             6000
       MANAGER   TOTAL BY JOB             8275
       PRESIDENT TOTAL BY JOB             5000
       SALESMAN  TOTAL BY JOB             5600
    10           TOTAL BY DEPT            8750
    30           TOTAL BY DEPT            9400
    20           TOTAL BY DEPT           10875
                 GRAND TOTAL FOR TABLE   29025

SQL 计算所有可能的表达式组合的小计 解决方案

近年来,对于 GROUP BY 语法的扩展使得本问题的解决变得容易多了。对于那些尚不支持这一类扩展语法的数据库,就必须(通过自连接或多个标量子查询)手动计算出多种层次的小计。
DB2
对于 DB2,需要把 GROUPING 函数的返回值转换为 CHAR(1) 类型。

 1  select deptno,
 2         job,
 3         case cast(grouping(deptno) as char(1))||
 4              cast(grouping(job) as char(1))
 5              when '00' then 'TOTAL BY DEPT AND JOB'
 6              when '10' then 'TOTAL BY JOB'
 7              when '01' then 'TOTAL BY DEPT'
 8              when '11' then 'TOTAL FOR TABLE'
 9         end category,
10         sum(sal)
11    from emp
12   group by cube(deptno,job)
13   order by grouping(job),grouping(deptno)

Oracle
使用 GROUP BY 子句的 CUBE 扩展以及字符串连接操作符 ||

 1  select deptno,
 2         job,
 3         case grouping(deptno)||grouping(job)
 4              when '00' then 'TOTAL BY DEPT AND JOB'
 5              when '10' then 'TOTAL BY JOB'
 6              when '01' then 'TOTAL BY DEPT'
 7              when '11' then 'GRAND TOTAL FOR TABLE'
 8         end category,
 9         sum(sal) sal
10    from emp
11   group by cube(deptno,job)
12   order by grouping(job),grouping(deptno)

SQL Server
使用 GROUP BY 子句的 CUBE 扩展。对于 SQL Server,需要把 GROUPING 函数的返回值转换成 CHAR(1) 类型,并且要使用字符串连接操作符 +(不同于 Oracle 的||操作符)。

 1  select deptno,
 2         job,
 3         case cast(grouping(deptno)as char(1))+
 4              cast(grouping(job)as char(1))
 5              when '00' then 'TOTAL BY DEPT AND JOB'
 6              when '10' then 'TOTAL BY JOB'
 7              when '01' then 'TOTAL BY DEPT'
 8              when '11' then 'GRAND TOTAL FOR TABLE'
 9         end category,
10         sum(sal) sal
11    from emp
12   group by deptno,job with cube
13   order by grouping(job),grouping(deptno)

PostgreSQLMySQL
使用多个 UNION ALL,把每种类型的合计合并到一起。

 1  select deptno, job,
 2         'TOTAL BY DEPT AND JOB' as category,
 3         sum(sal) as sal
 4    from emp
 5   group by deptno, job
 6   union all
 7  select null, job, 'TOTAL BY JOB', sum(sal)
 8    from emp
 9   group by job
10  union all
11 select deptno, null, 'TOTAL BY DEPT', sum(sal)
12   from emp
13  group by deptno
14  union all
15 select null,null,'GRAND TOTAL FOR TABLE', sum(sal)
16   from emp

SQL 计算所有可能的表达式组合的小计 扩展知识

Oracle、DB2 和 SQL Server
这 3 种数据库的解决方案大体相同。首先使用聚合函数 SUM,按照 DEPTNOJOB 分组,找出每个 JOBDEPTNO 组合对应的工资合计值。

select deptno, job, sum(sal) sal
  from emp
 group by deptno, job
 
DEPTNO JOB           SAL
------ --------- -------
    10 CLERK        1300
    10 MANAGER      2450
    10 PRESIDENT    5000
    20 CLERK        1900
    20 ANALYST      6000
    20 MANAGER      2975
    30 CLERK         950
    30 MANAGER      2850
    30 SALESMAN     5600

下一步是生成 JOBDEPTNO 的工资小计,以及全表的工资总计。使用 GROUP BY 子句的 CUBE 扩展,分别按照 DEPTNOJOB 和全表的维度执行 SAL 列的聚合运算。

select deptno,
       job,
       sum(sal) sal
  from emp
 group by cube(deptno,job)
 
DEPTNO JOB           SAL
------ --------- -------
                   29025
       CLERK        4150
       ANALYST      6000
       MANAGER      8275
       SALESMAN     5600
       PRESIDENT    5000
    10              8750
    10 CLERK        1300
    10 MANAGER      2450
    10 PRESIDENT    5000
    20             10875
    20 CLERK        1900
    20 ANALYST      6000
    20 MANAGER      2975
    30              9400
    30 CLERK         950
    30 MANAGER      2850
    30 SALESMAN     5600

然后,使用 GROUPING 函数和 CASE 表达式把上述结果格式化为更有意义的输出。GROUPING(JOB) 的返回值应该是 1 或 0,这取决于 SAL 值是否来自 CUBE。如果结果来自 CUBE,则返回值为 0,否则返回 1。GROUPING(DEPTNO) 的返回值也是如此。再回头看一下本解决方案的第一步,我们知道它是按照 DEPTNOJOB 进行分组的。因此,如果 SAL 值是基于 DEPTNOJOB 组合计算而来的,那么 GROUPING 函数调用的返回值将是 0。执行结果显示如下。

select deptno,
       job,
       grouping(deptno) is_deptno_subtotal,
       grouping(job) is_job_subtotal,
       sum(sal) sal
  from emp
 group by cube(deptno,job)
 order by 3,4
 
DEPTNO JOB       IS_DEPTNO_SUBTOTAL IS_JOB_SUBTOTAL     SAL
------ --------- ------------------ --------------- -------
    10 CLERK                      0               0    1300
    10 MANAGER                    0               0    2450
    10 PRESIDENT                  0               0    5000
    20 CLERK                      0               0    1900
    30 CLERK                      0               0     950
    30 SALESMAN                   0               0    5600
    30 MANAGER                    0               0    2850
    20 MANAGER                    0               0    2975
    20 ANALYST                    0               0    6000
    10                            0               1    8750
    20                            0               1    0875
    30                            0               1    9400
       CLERK                      1               0    4150
       ANALYST                    1               0    6000
       MANAGER                    1               0    8275
       PRESIDENT                  1               0    5000
       SALESMAN                   1               0    5600
                                  1               1    9025

最后,使用 CASE 表达式确认每一行的归属,这是基于 GROUPING(JOB)GROUPING(DEPTNO) 的返回值来决定的。

select deptno,
       job,
       case grouping(deptno)||grouping(job)
            when '00' then 'TOTAL BY DEPT AND JOB'
            when '10' then 'TOTAL BY JOB'
            when '01' then 'TOTAL BY DEPT'
            when '11' then 'GRAND TOTAL FOR TABLE'
       end category,
       sum(sal) sal
  from emp
 group by cube(deptno,job)
 order by grouping(job),grouping(deptno)
 
DEPTNO JOB       CATEGORY                  SAL
------ --------- --------------------- -------
    10 CLERK     TOTAL BY DEPT AND JOB    1300
    10 MANAGER   TOTAL BY DEPT AND JOB    2450
    10 PRESIDENT TOTAL BY DEPT AND JOB    5000
    20 CLERK     TOTAL BY DEPT AND JOB    1900
    30 CLERK     TOTAL BY DEPT AND JOB     950
    30 SALESMAN  TOTAL BY DEPT AND JOB    5600
    30 MANAGER   TOTAL BY DEPT AND JOB    2850
    20 MANAGER   TOTAL BY DEPT AND JOB    2975
    20 ANALYST   TOTAL BY DEPT AND JOB    6000
       CLERK     TOTAL BY JOB             4150
       ANALYST   TOTAL BY JOB             6000
       MANAGER   TOTAL BY JOB             8275
       PRESIDENT TOTAL BY JOB             5000
       SALESMAN  TOTAL BY JOB             5600
    10           TOTAL BY DEPT            8750
    30           TOTAL BY DEPT            9400
    20           TOTAL BY DEPT           10875
                 GRAND TOTAL FOR TABLE   29025

上述 Oracle 解决方案在做字符串连接操作时,把两个 GROUPING 函数的返回值隐式地转换成了字符类型。对于 DB2 和 SQL Server 而言,则需要显式地把 GROUPING 函数的返回值转换成 CHAR(1) 数据类型,正如上述解决方案所示。另外,把两个 GROUPING 函数调用的返回值拼接成一个字符串时,SQL Server 用户必须使用 + 操作符,而不是||操作符。
对于 Oracle 和 DB2 而言,GROUP BY 还有一个 GROUPING SETS 语法扩展,该扩展也非常有用。例如,我们可以用 GROUPING SETS 模仿 CUBE 的输出结果,如下所示。(和 CUBE 解决方案一样,DB2 和 SQL Server 需要对 GROUPING 函数的返回值进行显式的数据类型转换。)

select deptno,
       job,
       case grouping(deptno)||grouping(job)
            when '00' then 'TOTAL BY DEPT AND JOB'
            when '10' then 'TOTAL BY JOB'
            when '01' then 'TOTAL BY DEPT'
            when '11' then 'GRAND TOTAL FOR TABLE'
       end category,
       sum(sal) sal
  from emp
 group by grouping sets ((deptno),(job),(deptno,job),())
 
DEPTNO JOB       CATEGORY                  SAL
------ --------- --------------------- -------
    10 CLERK     TOTAL BY DEPT AND JOB    1300
    20 CLERK     TOTAL BY DEPT AND JOB    1900
    30 CLERK     TOTAL BY DEPT AND JOB     950
    20 ANALYST   TOTAL BY DEPT AND JOB    6000
    10 MANAGER   TOTAL BY DEPT AND JOB    2450
    20 MANAGER   TOTAL BY DEPT AND JOB    2975
    30 MANAGER   TOTAL BY DEPT AND JOB    2850
    30 SALESMAN  TOTAL BY DEPT AND JOB    5600
    10 PRESIDENT TOTAL BY DEPT AND JOB    5000
       CLERK     TOTAL BY JOB             4150
       ANALYST   TOTAL BY JOB             6000
       MANAGER   TOTAL BY JOB             8275
       SALESMAN  TOTAL BY JOB             5600
       PRESIDENT TOTAL BY JOB             5000
    10           TOTAL BY DEPT            8750
    20           TOTAL BY DEPT           10875
    30           TOTAL BY DEPT            9400
                 GRAND TOTAL FOR TABLE   29025

GROUPING SETS 的奇妙之处在于它允许我们定义分组。上述查询中的 GROUPING SETS 子句分别指定了按照 DEPTNO 分组,按照 JOB 分组,按照 DEPTNOJOB 的组合分组,以及最后空白括号代表的总计。GROUPING SETS 能够非常灵活地支持不同维度的聚合运算。例如,如果我们希望改一下上面的例子,去掉 GRAND TOTAL,那么只要把 GROUPING SETS 子句的空白括号删除掉就可以了。

/*去掉总计 */
 
select deptno,
       job,
       case grouping(deptno)||grouping(job)
            when '00' then 'TOTAL BY DEPT AND JOB'
            when '10' then 'TOTAL BY JOB'
            when '01' then 'TOTAL BY DEPT'
            when '11' then 'GRAND TOTAL FOR TABLE'
       end category,
       sum(sal) sal
  from emp
 group by grouping sets ((deptno),(job),(deptno,job))
 
DEPTNO JOB       CATEGORY                     SAL
------ --------- --------------------- ----------
    10 CLERK     TOTAL BY DEPT AND JOB       1300
    20 CLERK     TOTAL BY DEPT AND JOB       1900
    30 CLERK     TOTAL BY DEPT AND JOB        950
    20 ANALYST   TOTAL BY DEPT AND JOB       6000
    10 MANAGER   TOTAL BY DEPT AND JOB       2450
    20 MANAGER   TOTAL BY DEPT AND JOB       2975
    30 MANAGER   TOTAL BY DEPT AND JOB       2850
    30 SALESMAN  TOTAL BY DEPT AND JOB       5600
    10 PRESIDENT TOTAL BY DEPT AND JOB       5000
       CLERK     TOTAL BY JOB                4150
       ANALYST   TOTAL BY JOB                6000
       MANAGER   TOTAL BY JOB                8275
       SALESMAN  TOTAL BY JOB                5600
       PRESIDENT TOTAL BY JOB                5000
    10           TOTAL BY DEPT               8750
    20           TOTAL BY DEPT              10875
    30           TOTAL BY DEPT               9400

我们也可以删除一个小计,例如基于 DEPTNO 的小计,只要从 GROUPING SETS 子句中去掉“(DEPTNO)”就可以了。

/* 去掉DEPTNO小计 */
 
select deptno,
       job,
       case grouping(deptno)||grouping(job)
            when '00' then 'TOTAL BY DEPT AND JOB'
            when '10' then 'TOTAL BY JOB'
            when '01' then 'TOTAL BY DEPT'
            when '11' then 'GRAND TOTAL FOR TABLE'
       end category,
       sum(sal) sal
  from emp
 group by grouping sets ((job),(deptno,job),())
 order by 3
 
DEPTNO JOB       CATEGORY                     SAL
------ --------- --------------------- ----------
                 GRAND TOTAL FOR TABLE      29025
    10 CLERK     TOTAL BY DEPT AND JOB       1300
    20 CLERK     TOTAL BY DEPT AND JOB       1900
    30 CLERK     TOTAL BY DEPT AND JOB        950
    20 ANALYST   TOTAL BY DEPT AND JOB       6000
    20 MANAGER   TOTAL BY DEPT AND JOB       2975
    30 MANAGER   TOTAL BY DEPT AND JOB       2850
    30 SALESMAN  TOTAL BY DEPT AND JOB       5600
    10 PRESIDENT TOTAL BY DEPT AND JOB       5000
    10 MANAGER   TOTAL BY DEPT AND JOB       2450
       CLERK     TOTAL BY JOB                4150
       SALESMAN  TOTAL BY JOB                5600
       PRESIDENT TOTAL BY JOB                5000
       MANAGER   TOTAL BY JOB                8275
       ANALYST   TOTAL BY JOB                6000

如上所述,GROUPING SETS 确实能更方便地帮助我们从不同角度获得总计和小计。
PostgreSQLMySQL
首先使用聚合函数 SUM,并按照 DEPTNOJOB 进行分组。

select deptno, job,
       'TOTAL BY DEPT AND JOB' as category,
       sum(sal) as sal
  from emp
 group by deptno, job
 
DEPTNO JOB       CATEGORY                  SAL
------ --------- --------------------- -------
    10 CLERK     TOTAL BY DEPT AND JOB    1300
    10 MANAGER   TOTAL BY DEPT AND JOB    2450
    10 PRESIDENT TOTAL BY DEPT AND JOB    5000
    20 CLERK     TOTAL BY DEPT AND JOB    1900
    20 ANALYST   TOTAL BY DEPT AND JOB    6000
    20 MANAGER   TOTAL BY DEPT AND JOB    2975
    30 CLERK     TOTAL BY DEPT AND JOB     950
    30 MANAGER   TOTAL BY DEPT AND JOB    2850
    30 SALESMAN  TOTAL BY DEPT AND JOB    5600

然后,使用 UNION ALL 把基于 JOB 分组的工资合计值合并进来。

select deptno, job,
       'TOTAL BY DEPT AND JOB' as category,
       sum(sal) as sal
  from emp
 group by deptno, job
 union all
select null, job, 'TOTAL BY JOB', sum(sal)
  from emp
 group by job
 
DEPTNO JOB       CATEGORY                  SAL
------ --------- --------------------- -------
    10 CLERK     TOTAL BY DEPT AND JOB    1300
    10 MANAGER   TOTAL BY DEPT AND JOB    2450
    10 PRESIDENT TOTAL BY DEPT AND JOB    5000
    20 CLERK     TOTAL BY DEPT AND JOB    1900
    20 ANALYST   TOTAL BY DEPT AND JOB    6000
    20 MANAGER   TOTAL BY DEPT AND JOB    2975
    30 CLERK     TOTAL BY DEPT AND JOB     950
    30 MANAGER   TOTAL BY DEPT AND JOB    2850
    30 SALESMAN  TOTAL BY DEPT AND JOB    5600
       ANALYST   TOTAL BY JOB             6000
       CLERK     TOTAL BY JOB             4150
       MANAGER   TOTAL BY JOB             8275
       PRESIDENT TOTAL BY JOB             5000
       SALESMAN  TOTAL BY JOB             5600

再使用 UNION ALL 把基于 DEPTNO 分组的工资合计值合并进来。

select deptno, job,
       'TOTAL BY DEPT AND JOB' as category,
       sum(sal) as sal
  from emp
 group by deptno, job
 union all
select null, job, 'TOTAL BY JOB', sum(sal)
  from emp
 group by job
 union all
select deptno, null, 'TOTAL BY DEPT', sum(sal)
  from emp
 group by deptno
 
DEPTNO JOB       CATEGORY                  SAL
------ --------- --------------------- -------
    10 CLERK     TOTAL BY DEPT AND JOB    1300
    10 MANAGER   TOTAL BY DEPT AND JOB    2450
    10 PRESIDENT TOTAL BY DEPT AND JOB    5000
    20 CLERK     TOTAL BY DEPT AND JOB    1900
    20 ANALYST   TOTAL BY DEPT AND JOB    6000
    20 MANAGER   TOTAL BY DEPT AND JOB    2975
    30 CLERK     TOTAL BY DEPT AND JOB     950
    30 MANAGER   TOTAL BY DEPT AND JOB    2850
    30 SALESMAN  TOTAL BY DEPT AND JOB    5600
       ANALYST   TOTAL BY JOB             6000
       CLERK     TOTAL BY JOB             4150
       MANAGER   TOTAL BY JOB             8275
       PRESIDENT TOTAL BY JOB             5000
       SALESMAN  TOTAL BY JOB             5600
    10           TOTAL BY DEPT            8750
    20           TOTAL BY DEPT           10875
    30           TOTAL BY DEPT            9400

最后,使用 UNION ALLEMP 表的工资总计合并进来。

select deptno, job,
       'TOTAL BY DEPT AND JOB' as category,
       sum(sal) as sal
  from emp
 group by deptno, job
 union all
select null, job, 'TOTAL BY JOB', sum(sal)
  from emp
 group by job
 union all
select deptno, null, 'TOTAL BY DEPT', sum(sal)
  from emp
 group by deptno
 union all
select null,null, 'GRAND TOTAL FOR TABLE', sum(sal)
  from emp
 
DEPTNO JOB       CATEGORY                  SAL
------ --------- --------------------- -------
    10 CLERK     TOTAL BY DEPT AND JOB    1300
    10 MANAGER   TOTAL BY DEPT AND JOB    2450
    10 PRESIDENT TOTAL BY DEPT AND JOB    5000
    20 CLERK     TOTAL BY DEPT AND JOB    1900
    20 ANALYST   TOTAL BY DEPT AND JOB    6000
    20 MANAGER   TOTAL BY DEPT AND JOB    2975
    30 CLERK     TOTAL BY DEPT AND JOB     950
    30 MANAGER   TOTAL BY DEPT AND JOB    2850
    30 SALESMAN  TOTAL BY DEPT AND JOB    5600
       ANALYST   TOTAL BY JOB             6000
       CLERK     TOTAL BY JOB             4150
       MANAGER   TOTAL BY JOB             8275
       PRESIDENT TOTAL BY JOB             5000
       SALESMAN  TOTAL BY JOB             5600
    10           TOTAL BY DEPT            8750
    20           TOTAL BY DEPT           10875
    30           TOTAL BY DEPT            9400
                 GRAND TOTAL FOR TABLE   29025

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程