ORACLE学习笔记系列(15)使用扩展的 GROUP BY 子句
1、使用 ROLLUP子句
2、使用 CUBE子句
3、使用 GROUPING()函数
4、使用 GROUPING SETS子句
5、使用 GROUPING_ID()函数
6、使用 GROUP_ID()函数
GROUP BY 可选项 什么时候可以无: 非分组查询中,聚合函数实际上等于将表中所有记录作为一个组来运算。 此时在 select列表中指定的列只能是包含聚组函数,不能包含数据表本身的列。 比如求所有员工的总工资: SELECT SUM(SALARY) FROM EMP; 什么时候必须有: 在分组查询中,聚合函数是将数据按分组关键字分组,然后对每一组的函数自变量中的内容进行聚合运算。 Select 子句字段可以是分组关键字(group by 后面字段)和聚合函数。 比如求各个部门员工总工资: SELECT DEPTNAME, SUM(SALARY) FROM EMP GROUP BY EMP.DEPTNAME; 注意: a.如果没有group by 子句,select 列表中不允许出现字段与分组函数混用的情况。 b.在带有group by子句的查询语句中,在select列表中指定的列要么是group by 子句中指定的列,要么包含聚组函数。 出现在select列表中的字段,如果不是包含在分组函数中,那么该字段必须同时出在Group by子句中。 c.group by后面字段的顺序不同分组结果不同。
/* --设置输出格式 SET linesize 1000; SET pagesize 25; COLUMN deptno format A10; COLUMN DEPTNAME format A10; COLUMN job format A6; COLUMN NAME format A25; COLUMN lev format A10; */ -- 建表:EMP create table EMP ( id NUMBER(4), deptno VARCHAR2(50), deptname VARCHAR2(50), job VARCHAR2(50), name VARCHAR2(50), lev VARCHAR2(50), salary NUMBER(16,2) ); -- EMP表数据 SQL> SELECT * FROM EMP; ID DEPTNO DEPTNAME JOB NAME LEV SALARY ---------- ---------- ---------- ------ ------------------------- ---------- ---------- 1 BUS PRE James Smith 2000 2 SAL MGR Ron Johnson 875 3 SAL WOR Fred Hobbs 350 4 SUP MGR Susan Jones 500 5 SAL WOR Rob Green 875 6 SUP WOR Jane Brown 500 7 SUP MGR John Grey 662.5 8 SUP WOR Jean Blue 275 9 SUP WOR Henry Heyson 312.5 10 OPE MGR Kevin Black 562.5 11 OPE MGR Keith Long 412.5 12 OPE WOR Frank Howard 312.5 13 OPE WOR Doreen Penn 362.5 14 BUS MGR Mark Smith 387.5 15 BUS MGR Jill Jones 437.5 16 OPE ENG Megan Craig 612.5 17 SUP TEC Matthew Brant 287.5 18 OPE MGR Tony Clerke 500 19 BUS MGR Tanya Conway 500 20 OPE MGR Terry Cliff 537.5 21 SAL MGR Steve Green 687.5 22 SAL MGR Roy Red 937.5 ID DEPTNO DEPTNAME JOB NAME LEV SALARY ---------- ---------- ---------- ------ ------------------------- ---------- ---------- 23 SAL MGR Sandra Smith 837.5 24 SAL MGR Gail Silver 562.5 25 SAL MGR Gerald Gold 612.5 26 SAL MGR Eileen Lane 587.5 27 SAL MGR Doreen Upton 587.5 28 SAL MGR Jack Ewing 587.5 29 SAL MGR Paul Owens 612.5 30 SAL MGR Melanie York 637.5 31 SAL MGR Tracy Yellow 562.5 32 SAL MGR Sarah White 587.5 33 SAL MGR Terry Iron 562.5 34 SAL MGR Christine Brown 617.5 35 SAL MGR John Brown 622.5 36 SAL MGR Kelvin Trenton 637.5 37 BUS WOR Damon Jones 700 已选择37行。 ---- GROUP BY 的结果 SQL> SELECT DEPTNAME, SUM(SALARY) FROM EMP GROUP BY DEPTNAME; DEPTNAME SUM(SALARY) ---------- ----------- SUP 2537.5 SAL 12340 BUS 4025 OPE 3300 SQL>
1、使用 ROLLUP子句 ROLLUP 是GROUP BY子句的扩展,它是为每一个分组返回一条合计记录,并为全部分组返回总计。 ROLLUP 可以按单列进行分组,与可以向 ROLLUP 传递多列。 ROLLUP 按照多列进行分组时,Rollup 后面跟了n个字段,就将进行n+1次分组,从左到右每次减少一个字段进行分组; 然后进行union操作。 例如 ROLLUP(A, B, C),则执行顺序如下: (1)首先对(A、B、C)进行GROUP BY, (2)然后对(A、B)进行GROUP BY, (3)然后是(A)进行GROUP BY, (4)然后对全表进行GROUP BY, (5)最后将以上四步的结果进行union操作。 示例: -- ROLLUP一个字段: SQL> SELECT DEPTNAME, SUM(SALARY) FROM EMP GROUP BY ROLLUP(DEPTNAME); DEPTNAME SUM(SALARY) ---------- ----------- BUS 4025 OPE 3300 SAL 12340 SUP 2537.5 22202.5 -- 等价于 SQL> SELECT DEPTNAME, SUM(SALARY) FROM EMP GROUP BY DEPTNAME 2 UNION 3 SELECT '', SUM(SALARY) FROM EMP; DEPTNAME SUM(SALARY) ---------- ----------- BUS 4025 OPE 3300 SAL 12340 SUP 2537.5 22202.5 -- ROLLUP两个字段: SQL> SELECT DEPTNAME, JOB, SUM(SALARY) FROM EMP GROUP BY ROLLUP(DEPTNAME, JOB); DEPTNAME JOB SUM(SALARY) ---------- ------ ----------- BUS MGR 1325 BUS PRE 2000 BUS WOR 700 BUS 4025 OPE ENG 612.5 OPE MGR 2012.5 OPE WOR 675 OPE 3300 SAL MGR 11115 SAL WOR 1225 SAL 12340 SUP MGR 1162.5 SUP TEC 287.5 SUP WOR 1087.5 SUP 2537.5 22202.5 已选择16行。 -- 等价于 SQL> SELECT DEPTNAME, JOB, SUM(SALARY) FROM EMP GROUP BY DEPTNAME, JOB 2 UNION 3 SELECT DEPTNAME,'', SUM(SALARY) FROM EMP GROUP BY DEPTNAME 4 UNION 5 SELECT '','', SUM(SALARY) FROM EMP ; DEPTNAME JOB SUM(SALARY) ---------- ------ ----------- BUS MGR 1325 BUS PRE 2000 BUS WOR 700 BUS 4025 OPE ENG 612.5 OPE MGR 2012.5 OPE WOR 675 OPE 3300 SAL MGR 11115 SAL WOR 1225 SAL 12340 SUP MGR 1162.5 SUP TEC 287.5 SUP WOR 1087.5 SUP 2537.5 22202.5 已选择16行。 SQL> --合计行的分组列会显示为空,可进行转换 SQL> SELECT NVL(DEPTNAME, '合计') DEPTNAME, SUM(SALARY) FROM EMP GROUP BY ROLLUP(DEPTNAME); DEPTNAME SUM(SALARY) ---------- ----------- BUS 4025 OPE 3300 SAL 12340 SUP 2537.5 合计 22202.5 SQL> SELECT NVL(DEPTNAME, '合计') DEPTNAME, NVL(JOB, '小计') JOB, SUM(SALARY) FROM EMP GROUP BY ROLLUP(DEPTNAME, JOB); DEPTNAME JOB SUM(SALARY) ---------- ------ ----------- BUS MGR 1325 BUS PRE 2000 BUS WOR 700 BUS 小计 4025 OPE ENG 612.5 OPE MGR 2012.5 OPE WOR 675 OPE 小计 3300 SAL MGR 11115 SAL WOR 1225 SAL 小计 12340 SUP MGR 1162.5 SUP TEC 287.5 SUP WOR 1087.5 SUP 小计 2537.5 合计 小计 22202.5 已选择16行。 SQL>
2、使用 CUBE子句 CUBE子句也是对GROUP BY子句进行扩展,返回CUBE中所有列组合的小计信息,同时在最后显示总计信息。 CUBE这里的使用与ROLLUP基本相同,但CUBE的合计更加详细,它能够显示出分组字段的合计信息。 在Group By 中使用Cube可以产生Rollup结果集+和多维度的交叉表数据源。 Cube 后面跟了n个字段,就将进行2的N次方的分组运算,然后进行union; 例如 GROUP BY CUBE(A, B, C),则执行顺序如下: (1)首先会对 (A、B、C) 进行GROUP BY, (2)然后是对 (A、B) 进行GROUP BY, (3)然后是对 (A、C) 进行GROUP BY, (4)然后是对 (A) 进行GROUP BY, (5)然后是对 (B、C) 进行GROUP BY, (6)然后是对 (B) 进行GROUP BY, (7)然后是对 (C) 进行GROUP BY, (8)然后对全表进行GROUP BY, (9)最后将以上的结果进行union操作。 -- GROUP BY CUBE 一个字段时,与ROLLUP一个字段,是等价的。 SELECT DEPTNAME, SUM(SALARY) FROM EMP GROUP BY ROLLUP(DEPTNAME); SELECT DEPTNAME, SUM(SALARY) FROM EMP GROUP BY CUBE(DEPTNAME) ORDER BY DEPTNAME; --等价于 SELECT DEPTNAME, SUM(SALARY) FROM EMP GROUP BY DEPTNAME UNION SELECT '', SUM(SALARY) FROM EMP; -- GROUP BY CUBE 两个字段时 SELECT DEPTNAME, JOB, SUM(SALARY) FROM EMP GROUP BY CUBE(DEPTNAME, JOB) ORDER BY DEPTNAME; --等价于 SELECT DEPTNAME, JOB, SUM(SALARY) FROM EMP GROUP BY DEPTNAME, JOB UNION SELECT DEPTNAME,'', SUM(SALARY) FROM EMP GROUP BY DEPTNAME UNION SELECT '', JOB, SUM(SALARY) FROM EMP GROUP BY JOB UNION SELECT '','', SUM(SALARY) FROM EMP ; -- GROUP BY CUBE 两个字段时 SQL> SELECT DEPTNAME, JOB, SUM(SALARY) FROM EMP GROUP BY CUBE(DEPTNAME, JOB) ORDER BY DEPTNAME; DEPTNAME JOB SUM(SALARY) ---------- ------ ----------- BUS MGR 1325 BUS PRE 2000 BUS WOR 700 BUS 4025 OPE ENG 612.5 OPE MGR 2012.5 OPE WOR 675 OPE 3300 SAL MGR 11115 SAL WOR 1225 SAL 12340 SUP MGR 1162.5 SUP TEC 287.5 SUP WOR 1087.5 SUP 2537.5 ENG 612.5 MGR 15615 PRE 2000 TEC 287.5 WOR 3687.5 22202.5 已选择21行。 SQL> SELECT NVL(DEPTNAME, '合计') DEPTNAME, NVL(JOB, '小计') JOB, SUM(SALARY) FROM EMP GROUP BY CUBE(DEPTNAME, JOB) ORDER BY DEPTNAME; DEPTNAME JOB SUM(SALARY) ---------- ------ ----------- BUS 小计 4025 BUS MGR 1325 BUS PRE 2000 BUS WOR 700 OPE 小计 3300 OPE MGR 2012.5 OPE WOR 675 OPE ENG 612.5 SAL MGR 11115 SAL WOR 1225 SAL 小计 12340 SUP TEC 287.5 SUP WOR 1087.5 SUP MGR 1162.5 SUP 小计 2537.5 合计 ENG 612.5 合计 小计 22202.5 合计 MGR 15615 合计 PRE 2000 合计 TEC 287.5 合计 WOR 3687.5 已选择21行。 SQL>
3、使用 GROUPING()函数 GROUPING()可接受一个列值,当列值为空时,函数返回1;如果列值非空,则返回0。 (注意:GROUPING()只能在ROLLUP、CUBE查询中使用。还可以结合DECODE()函数来注释使用更好) 示例: --GROUPING()与ROLLUP结合使用 SQL> SELECT GROUPING(DEPTNAME), DEPTNAME, SUM(SALARY) 2 FROM EMP 3 GROUP BY ROLLUP(DEPTNAME); GROUPING(DEPTNAME) DEPTNAME SUM(SALARY) ------------------ ---------- ----------- 0 BUS 4025 0 OPE 3300 0 SAL 12340 0 SUP 2537.5 1 22202.5 --使用DECODE()函数来转换: SQL> SELECT DECODE(GROUPING(DEPTNAME), 1, '合计', DEPTNAME) AS DEPTNAME, 2 DEPTNAME, 3 SUM(SALARY) 4 FROM EMP 5 GROUP BY ROLLUP(DEPTNAME); DEPTNAME DEPTNAME SUM(SALARY) ---------- ---------- ----------- BUS BUS 4025 OPE OPE 3300 SAL SAL 12340 SUP SUP 2537.5 合计 22202.5 --GROUPING()与CUBE结合使用 SQL> SELECT DECODE(GROUPING(DEPTNAME), 1, '合计', DEPTNAME) AS DEPTNAME, 2 DECODE(GROUPING(JOB), 1, '小计', JOB) AS JOB, 3 SUM(SALARY) 4 FROM EMP 5 GROUP BY CUBE(DEPTNAME, JOB) 6 ORDER BY DEPTNAME, JOB; DEPTNAME JOB SUM(SALARY) ---------- ------ ----------- BUS MGR 1325 BUS PRE 2000 BUS WOR 700 BUS 小计 4025 OPE ENG 612.5 OPE MGR 2012.5 OPE WOR 675 OPE 小计 3300 SAL MGR 11115 SAL WOR 1225 SAL 小计 12340 SUP MGR 1162.5 SUP TEC 287.5 SUP WOR 1087.5 SUP 小计 2537.5 合计 ENG 612.5 合计 MGR 15615 合计 PRE 2000 合计 TEC 287.5 合计 WOR 3687.5 合计 小计 22202.5 已选择21行。 SQL>
4、使用 GROUPING SETS子句 下面使用 GROUPING SETS子句来限制只返回小计记录。 也可以使用 GROUPING SETS子句 来代替多次UNION。 GROUPING SETS子句可以按单一字段分组,也可以按组合字段分组。 例如 按单一字段分组 GROUP BY GROUPING SETS(A, B),则执行顺序如下: (1)首先是对 (A) 进行GROUP BY, (2)然后是对 (B) 进行GROUP BY, (3)最后将以上的结果进行union操作。 例如 按组合字段分组 GROUP BY GROUPING SETS((A, B),(C,D)),则执行顺序如下: (1)首先是对 (A,B) 进行GROUP BY, (2)然后是对 (C,D) 进行GROUP BY, (3)最后将以上的结果进行union操作。 -- 按单一字段分组 SQL> SELECT DEPTNAME, JOB, SUM(SALARY) 2 FROM EMP 3 GROUP BY GROUPING SETS(DEPTNAME, JOB) 4 ORDER BY DEPTNAME, JOB; DEPTNAME JOB SUM(SALARY) ---------- ------ ----------- BUS 4025 OPE 3300 SAL 12340 SUP 2537.5 ENG 612.5 MGR 15615 PRE 2000 TEC 287.5 WOR 3687.5 已选择9行。 SQL> --等价于 SQL> SELECT DEPTNAME, '' AS JOB, SUM(SALARY) FROM EMP GROUP BY DEPTNAME 2 UNION 3 SELECT '' AS DEPTNAME, JOB, SUM(SALARY) FROM EMP GROUP BY JOB; DEPTNAME JOB SUM(SALARY) ---------- ------ ----------- BUS 4025 OPE 3300 SAL 12340 SUP 2537.5 ENG 612.5 MGR 15615 PRE 2000 TEC 287.5 WOR 3687.5 已选择9行。 SQL>
5、使用 GROUPING_ID()函数 GROUPING_ID()函数可接受一列或多列,它返回GROUPING位向量的十进制值。 GROUPING位向量的计算方法是按照顺序对每一列调用GROUPING()函数的结果组合起来。 它的作用是借助HAVING子句对记录进行过滤,将不包含小计或总计的记录除去。 GROUPING位向量的十进制值,我们由前面的介绍已知道当GROUPING()的列值为空时它返回1,当非空时返回0; 比如:GROUPING_ID(DIVISION_ID, JOB_ID) division_id,job_id两列都为非空, GROUPING()都返回0。将这两列的值组合起来,形成一个位向量00,十进制为0。 division_id列为非空,job_id列为空,GROUPING()都返回1。将这两列的值组合起来,形成一个位向量01,十进制为1。 division_id列为空,job_id列为非空,GROUPING()都返回2。将这两列的值组合起来,形成一个位向量10,十进制为2。 division_id,job_id两列都为空, GROUPING()都返回3。将这两列的值组合起来,形成一个位向量11,十进制为3。 (这里要特别注意division_id与job_id两列的顺序,如果顺序不一样返回的结果值可能不一样。) 示例: SQL> SELECT DEPTNAME, 2 JOB, 3 GROUPING(DEPTNAME) AS DEP, 4 GROUPING(JOB) AS J, 5 GROUPING_ID(DEPTNAME, JOB) AS GRP_ID, 6 SUM(SALARY) 7 FROM EMP 8 GROUP BY CUBE(DEPTNAME, JOB) 9 ORDER BY DEPTNAME, JOB; DEPTNAME JOB DEP J GRP_ID SUM(SALARY) ---------- ------ ---------- ---------- ---------- ----------- BUS MGR 0 0 0 1325 BUS PRE 0 0 0 2000 BUS WOR 0 0 0 700 BUS 0 1 1 4025 OPE ENG 0 0 0 612.5 OPE MGR 0 0 0 2012.5 OPE WOR 0 0 0 675 OPE 0 1 1 3300 SAL MGR 0 0 0 11115 SAL WOR 0 0 0 1225 SAL 0 1 1 12340 SUP MGR 0 0 0 1162.5 SUP TEC 0 0 0 287.5 SUP WOR 0 0 0 1087.5 SUP 0 1 1 2537.5 ENG 1 0 2 612.5 MGR 1 0 2 15615 PRE 1 0 2 2000 TEC 1 0 2 287.5 WOR 1 0 2 3687.5 1 1 3 22202.5 已选择21行。 --使用GROUPING_ID过滤不包含小计或总计的记录: SQL> SELECT DEPTNAME, 2 JOB, 3 GROUPING(DEPTNAME) AS DEP, 4 GROUPING(JOB) AS J, 5 GROUPING_ID(DEPTNAME, JOB) AS GRP_ID, 6 SUM(SALARY) 7 FROM EMP 8 GROUP BY CUBE(DEPTNAME, JOB) 9 HAVING GROUPING_ID(DEPTNAME, JOB) > 0 10 ORDER BY DEPTNAME, JOB; DEPTNAME JOB DEP J GRP_ID SUM(SALARY) ---------- ------ ---------- ---------- ---------- ----------- BUS 0 1 1 4025 OPE 0 1 1 3300 SAL 0 1 1 12340 SUP 0 1 1 2537.5 ENG 1 0 2 612.5 MGR 1 0 2 15615 PRE 1 0 2 2000 TEC 1 0 2 287.5 WOR 1 0 2 3687.5 1 1 3 22202.5 已选择10行。 SQL> --使用GROUPING_ID来进行合计的注注释 SQL> SELECT DECODE(GROUPING_ID(DEPTNAME, JOB), 3, '总合计', 2, '合计', DEPTNAME) AS DEPTNAME, 2 DECODE(GROUPING_ID(DEPTNAME, JOB), 3, '总合计', 1, '小计', JOB) AS JOB, 3 SUM(SALARY) 4 FROM EMP 5 GROUP BY CUBE(DEPTNAME, JOB) 6 ORDER BY DEPTNAME, JOB; DEPTNAME JOB SUM(SALARY) ---------- ------ ----------- BUS MGR 1325 BUS PRE 2000 BUS WOR 700 BUS 小计 4025 OPE ENG 612.5 OPE MGR 2012.5 OPE WOR 675 OPE 小计 3300 SAL MGR 11115 SAL WOR 1225 SAL 小计 12340 SUP MGR 1162.5 SUP TEC 287.5 SUP WOR 1087.5 SUP 小计 2537.5 合计 ENG 612.5 合计 MGR 15615 合计 PRE 2000 合计 TEC 287.5 合计 WOR 3687.5 总合计 总合计 22202.5 已选择21行。 SQL>
6、使用 GROUP_ID()函数 在 GROUP BY 子句中可以多次使用某个列,这样可以实现对数据的重新组织,或是按照不同的数据分组进行统计。 如下面这个查询中包含一个 GROUP BY 子句,其中使用了两次 DEPTNAME 列,第一次是对 DEPTNAME 进行分组, 第二次是在 ROLLOUP 中使用。从结果可以看到最后4行与前面4行是重复的。 这种现象可以通过使用 GROUP_ID() 函数来消除。 GROUP_ID()函数用于消除 GROUP BY 子句返回的重复记录。 GROUP_ID()函数不接收任何参数。 如果某个特定的分组重复出现 N 次,那么 GROUP_ID() 函数返回从 0 到 N-1 之间的一个整数。 -- GROUP BY 子句返回重复记录 SQL> SELECT DEPTNAME, JOB, GROUP_ID(), SUM(SALARY) 2 FROM EMP 3 GROUP BY DEPTNAME, ROLLUP(DEPTNAME, JOB); DEPTNAME JOB GROUP_ID() SUM(SALARY) ---------- ------ ---------- ----------- BUS MGR 0 1325 BUS PRE 0 2000 BUS WOR 0 700 OPE ENG 0 612.5 OPE MGR 0 2012.5 OPE WOR 0 675 SAL MGR 0 11115 SAL WOR 0 1225 SUP MGR 0 1162.5 SUP TEC 0 287.5 SUP WOR 0 1087.5 BUS 0 4025 OPE 0 3300 SAL 0 12340 SUP 0 2537.5 BUS 1 4025 OPE 1 3300 SAL 1 12340 SUP 1 2537.5 已选择19行。 SQL> 在上面的例子中,除了最后 4 条记录之外,GROUP_ID()函数在其他记录上返回值都是0; 最后4条记录与前面4条记录重复,因此GROUP_ID()函数返回1。 可使用 HAVING 子句消除重复记录,只返回GROUP_ID()函数等于0的记录。 SQL> SELECT DEPTNAME, JOB, GROUP_ID(), SUM(SALARY) 2 FROM EMP 3 GROUP BY DEPTNAME, ROLLUP(DEPTNAME, JOB) 4 HAVING GROUP_ID() = 0; DEPTNAME JOB GROUP_ID() SUM(SALARY) ---------- ------ ---------- ----------- BUS MGR 0 1325 BUS PRE 0 2000 BUS WOR 0 700 OPE ENG 0 612.5 OPE MGR 0 2012.5 OPE WOR 0 675 SAL MGR 0 11115 SAL WOR 0 1225 SUP MGR 0 1162.5 SUP TEC 0 287.5 SUP WOR 0 1087.5 BUS 0 4025 OPE 0 3300 SAL 0 12340 SUP 0 2537.5 已选择15行。 SQL>