89652511 2017-03-15
为了便于讲述,我将把简单 GROUP BY 子句的分组称之为标准分组,把 GROUP BY 子句中出现的列(或表达式)称之为分组列。
在分组查询中,GROUP BY 子句的作用就是按指定的(一或多个)列或表达式的值将选定行集进行分组,并针对每一组返回一行从组中收集到的数据。基本语法:
SELECT expression1, expression2, ... expression_n, aggregate_function (aggregate_expression) FROM tables [WHERE conditions] GROUP BY expression1, expression2, ... expression_n [HAVING having_condition];
简单示例:
SELECT t.dept_code, MAX(t.post_salary) max_salary, -- 部门最高岗位工资 MIN(t.post_salary) min_salary, -- 部门最低岗位工资 AVG(t.post_salary) avg_salary, -- 部门平均岗位工资 SUM(t.post_salary) sum_salary, -- 部门岗位工资之和 COUNT(t.post_salary) cnt_salary -- 部门工资份数 FROM demo.t_staff t GROUP BY t.dept_code HAVING AVG(t.post_salary)>3500 ORDER BY t.dept_code;
注意事项:
我首先要说明的是:“WHERE 和 HAVING 的区别?”这绝对是一个有问题的问题!因为它两根本就没有可比性,实际上问这种问题的人,往往 SQL 基础也不够扎实。
在包含 GROUP BY 子句的查询语句中:WHERE 子句的作用是在对查询结果分组前过滤行数据,将不符合条件的行去掉;而 HAVING 子句的作用是在对查询结果分组后过滤组数据,将不符合条件的组去掉。换句话说,因为聚合函数的作用是提供有关组的信息,还没分组之前当然是无法提供组的信息的,也就是说 WHERE 子句中无法使用聚合函数,这也正是会出现 HAVING 子句的原因。有了 HAVING 子句,我们就可以很方便的在分组之后对组的数据进行过滤了。一般来说,能用 WHERE 的过滤的就不应该用 HAVING 过滤!
在实际业务开发中,只有标准分组可能还不够,往往还需要更多维度的小计、合计。针对这类需求,Oracle 提供了丰富的扩展分组功能;尽管用 UNION ALL 一般也能实现类似效果,但不够灵活且性能比较低。
ROLLUP 是 GROUP BY 子句的一种扩展,它允许计算标准分组及部分维度的小计、合计。语法:
GROUP BY ROLLUP(grouping_column_reference_list)
ROLLUP 的计算结果与括号中指定列的顺序有关,因为 ROLLUP 的分组过程具有方向性,先计算标准分组,然后从右到左递减计算更高一级的小计,直到所有列被计算完,最后计算合计。当 ROLLUP 中指定 n 列时,整个计算过程中分组方式有 n+1 种。如GROUP BY ROLLUP(A,B)
的分组过程相当于是:第 1 步按 GROUP BY(A,B) 分组求小计,第 2 步按 GROUP BY(A,NULL) 分组求小计,第 3 步按 GROUP BY(NULL,NULL) 分组求合计。
示例 1:
SELECT t.dept_code,SUM(t.post_salary) sum_salary,MAX(t.post_salary) max_salary FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY ROLLUP(t.dept_code);
结果:
DEPT_CODE SUM_SALARY MAX_SALARY -------------------------------------------------- ---------- ---------- 010102 13500 7500 010103 7850 5050 21350 7500
示例 2:
SELECT t.dept_code,t.post_code,SUM(t.post_salary) sum_salary,MAX(t.post_salary) max_salary FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY ROLLUP(t.dept_code,t.post_code);
结果:
DEPT_CODE POST_CODE SUM_SALARY MAX_SALARY ---------------------------------------------- -------------------------------------------- ---------- ---------- 010102 P40 7500 7500 010102 P50 6000 6000 010102 13500 7500 010103 P40 5050 5050 010103 P50 2800 2800 010103 7850 5050 21350 7500
示例 3,部分 ROLLUP 分组(不需要某些小计、合计时可用该种写法):
SELECT t.dept_code,t.post_code,SUM(t.post_salary) sum_salary,MAX(t.post_salary) max_salary FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY t.dept_code,ROLLUP(t.post_code);
结果:
DEPT_CODE POST_CODE SUM_SALARY MAX_SALARY --------------------------------------------- -------------------------------------------- ---------- ---------- 010102 P40 7500 7500 010102 P50 6000 6000 010102 13500 7500 010103 P40 5050 5050 010103 P50 2800 2800 010103 7850 5050
CUBE 是 GROUP BY 子句的一种扩展,它允许计算标准分组及所有维度的小计、合计。语法:
GROUP BY CUBE(grouping_column_reference_list)
CUBE 会对所有可能的分组进行统计,从而生成交叉报表。CUBE 比 ROLLUP 的分组更多,且包含了 ROLLUP 的统计结果,且计算结果与分组列的顺序无关,但如果列顺序不同,默认的结果排序会有不同。当 CUBE 中指定 n 列时,整个计算过程中分组方式有 2 的 n 次方种。如GROUP BY CUBE(A,B)
相当于:按 GROUP BY(A,B) 分组的小计,加按 GROUP BY(A,NULL) 分组的小计,加按 GROUP BY(NULL,B) 分组的小计,加按 GROUP BY(NULL,NULL) 分组的合计。
示例 1:
SELECT t.dept_code,SUM(t.post_salary) sum_salary,MAX(t.post_salary) max_salary FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY CUBE(t.dept_code);
结果:
DEPT_CODE SUM_SALARY MAX_SALARY -------------------------------------------------- ---------- ---------- 21350 7500 010102 13500 7500 010103 7850 5050
示例 2:
SELECT t.dept_code,t.post_code,SUM(t.post_salary) sum_salary,MAX(t.post_salary) max_salary FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY CUBE(t.dept_code,t.post_code);
结果:
DEPT_CODE POST_CODE SUM_SALARY MAX_SALARY --------------------------------------------- ------------------------------------------ ---------- ---------- 21350 7500 P40 12550 7500 P50 8800 6000 010102 13500 7500 010102 P40 7500 7500 010102 P50 6000 6000 010103 7850 5050 010103 P40 5050 5050 010103 P50 2800 2800
示例 3,部分 CUBE 分组(不需要某些小计、合计时可用该种写法):
SELECT t.dept_code,t.post_code,SUM(t.post_salary) sum_salary,MAX(t.post_salary) max_salary FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY t.dept_code,CUBE(t.post_code);
结果:
DEPT_CODE POST_CODE SUM_SALARY MAX_SALARY -------------------------------------------- ---------------------------------------- ---------- ---------- 010102 13500 7500 010102 P40 7500 7500 010102 P50 6000 6000 010103 7850 5050 010103 P40 5050 5050 010103 P50 2800 2800
GROUPING SETS 是 GROUP BY 子句的一种扩展,它允许一次计算多个标准分组的小计。语法:
GROUP BY GROUPING SETS(grouping_column_reference_list)
GROUPING SETS 的计算结果和分组列的顺序无关,结果集排序也和分组列的顺序无关。当 GROUPING SETS 中指定 n 列时,整个计算过程中分组方式有 n 种。如GROUPING SETS(A,B,C)
相当于 GROUP BY A、GROUP BY B 和 GROUP BY C 这 3 个分组 UNION ALL 的结果。
示例 1:
SELECT t.dept_code,SUM(t.post_salary) sum_salary,MAX(t.post_salary) max_salary FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY GROUPING SETS(t.dept_code);
结果:
DEPT_CODE SUM_SALARY MAX_SALARY -------------------------------------------------- ---------- ---------- 010102 13500 7500 010103 7850 5050
示例 2:
SELECT t.dept_code,t.post_code,SUM(t.post_salary) sum_salary,MAX(t.post_salary) max_salary FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY GROUPING SETS(t.dept_code,t.post_code);
结果:
DEPT_CODE POST_CODE SUM_SALARY MAX_SALARY --------------------------------------------- ------------------------------------------ ---------- ---------- 010102 13500 7500 010103 7850 5050 P50 8800 6000 P40 12550 7500
示例 3,部分 GROUPING SETS 分组:
SELECT t.dept_code,t.post_code,SUM(t.post_salary) sum_salary,MAX(t.post_salary) max_salary FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY t.dept_code,GROUPING SETS(t.post_code);
结果:
DEPT_CODE POST_CODE SUM_SALARY MAX_SALARY -------------------------------------------- ----------------------------------------- ---------- ---------- 010103 P40 5050 5050 010102 P40 7500 7500 010102 P50 6000 6000 010103 P50 2800 2800
示例 4,GROUPING SETS 能够接受 ROLLUP 和 CUBE 作为它的参数;GROUPING SETS 只对单列进行分组,而不提供合计的功能,如果需要 GROUPING SETS 提供合计,可用 ROLLUP 或 CUBE 作参数来提供合计功能(注意 ROLLUP 和 CUBE 不接受 GROUPING SETS 作参数,ROLLUP 和 CUBE 之间互相作为参数也不可以):
SELECT t.dept_code,t.post_code,SUM(t.post_salary) sum_salary,MAX(t.post_salary) max_salary FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY GROUPING SETS(ROLLUP(t.dept_code),ROLLUP(t.post_code));
结果:
DEPT_CODE POST_CODE SUM_SALARY MAX_SALARY -------------------------------------------- ------------------------------------------ ---------- ---------- 010102 13500 7500 010103 7850 5050 P50 8800 6000 P40 12550 7500 21350 7500 21350 7500
简单来说:组合列分组就是允许 ROLLUP、CUBE 和 GROUPING SETS 中可以有多个列或列组合;重复列分组就是允许 GROUP BY 后面重复出现分组列;连接分组就是允许 GROUP BY 后面有多个 ROLLUP、CUBE 或 GROUPING SETS。
组合列分组有过滤某些小计或计算一些额外的小计的功能。前面的部分 ROLLUP 和 部分 CUBE 都没有合计,使用组合列分组既可以实现部分 ROLLUP 或 部分 CUBE 的功能,还能有合计。如ROLLUP(A,(B,C))
,既能过滤 B、C 的小计,还能计算 ABC 的合计。
连接分组的分组级别是由所有 ROLLUP、CUBE 或 GROUPING SETS 分组的级别组成的笛卡尔积。如ROLLUP(A,B),ROLLUP(C,D,E)
的分组级别是 (2+1)×(3+1)=12,CUBE(A,B),CUBE(C,D,E)
的分组级别是 (4)×(8)=32,CUBE(A,B),CUBE(C,D,E)
的分组级别是 (4)×(8)=32。
按我的理解来说:复杂分组无非也就是对标准扩展分组的综合运用。在实际开发中,可能会遇到一些仅使用标准扩展分组无法实现的需求,这时候就可以考虑灵活的运用标准扩展分组,通过复杂分组来实现。
GROUPING 语法:GROUPING(expr)
。因为原始数据中可能存在 NULL,且小计或合计的值也可能为 NULL,这样一来就显得数据比较混乱了。当该函数出现在 SELECT 子句中时,如果聚集行的 expr 为 NULL,它就返回 1;如果常规行的 expr 为 NULL,它就返回 0。通常将一个分组列作为该函数的参数,然后通过判断它的返回值来区分聚集行与常规行,从而进一步对结果集美化或过滤。示例:
SELECT DECODE(GROUPING(t.dept_code),1,'合计',t.dept_code) dept_code, SUM(t.post_salary) sum_salary,GROUPING(t.dept_code) gd FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY ROLLUP(t.dept_code);
结果:
DEPT_CODE SUM_SALARY GD -------------------------------------------------- ---------- ---------- 010102 13500 0 010103 7850 0 合计 21350 1
GROUPING_ID 语法:GROUPING_ID(expr [, expr ]...)
。当该函数出现在 SELECT 子句中时,它将返回与行相关联的 GROUPING 位向量对应的数值。GROUPING_ID 函数按从左到右的顺序计算,如果此列是分组列,则为 0,如果是小计或合计则为 1,然后按列的顺序将计算结果组成二进制序列(位向量),最后将位向量转化为十进制数。GROUPING_ID 函数在功能上等效于多个 GROUPING 函数的结果,有了 GROUPING_ID 就不必再写多个 GROUPING 了,也使得行过滤条件更容易表达。当查询结果有多个聚合级别时,该函数特别有用,可通过它的返回值来排序和过滤结果集。示例:
SELECT t.dept_code,t.post_code,SUM(t.post_salary) sum_salary, GROUPING_ID(t.dept_code) gd, GROUPING_ID(t.post_code) gp, GROUPING_ID(t.dept_code,t.post_code) gdp, GROUPING_ID(t.post_code,t.dept_code) gpd FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY CUBE(t.dept_code,t.post_code) ORDER BY GROUPING_ID(t.dept_code,t.post_code);
结果(结果集相当于是按 GDP 升序排序):
DEPT_CODE POST_CODE SUM_SALARY GD GP GDP GPD ----------------------------- ------------------------ ---------- ---------- ---------- ---------- ---------- 010102 P40 7500 0 0 0 0 010103 P40 5050 0 0 0 0 010102 P50 6000 0 0 0 0 010103 P50 2800 0 0 0 0 010103 7850 0 1 1 2 010102 13500 0 1 1 2 P50 8800 1 0 2 1 P40 12550 1 0 2 1 21350 1 1 3 3
GROUP_ID 语法:GROUP_ID()
。当该函数出现在 SELECT 子句中时,如果结果集中存在 n 个重复,那么它将返回范围从 0 到 n-1 中的数字,这对于从查询结果中剔除重复分组来说非常有用。示例(一般通过HAVING GROUP_ID()<1
将重复行全部剔除):
SELECT t.dept_code,SUM(t.post_salary) sum_salary,GROUP_ID() group_id FROM demo.t_staff t WHERE t.dept_code IN('010102','010103') GROUP BY ROLLUP(t.dept_code),CUBE(t.dept_code);
结果:
DEPT_CODE SUM_SALARY GROUP_ID -------------------------------------------------- ---------- ---------- 010102 13500 0 010103 7850 0 010102 13500 2 010103 7850 2 010102 13500 1 010103 7850 1 21350 0
本文主要讲述了 Oracle 中分组查询的标准分组、扩展分组、扩展函数等 GROUP BY 相关的知识点。
[blockquote]
本文链接:http://www.cnblogs.com/hanzongze/p/Oracle-Group-By.html 版权声明:本文为安科开发博主 韩宗泽 原创,作者保留署名权!欢迎通过转载、演绎或其它传播方式来使用本文,但必须在明显位置给出作者署名和本文链接!本人初写博客,水平有限,若有不当之处,敬请批评指正,谢谢!
[/blockquote]