.Net程序员学用Oracle系列(21):分组查询(GROUP BY)

1、GROUP BY 标准分组

为了便于讲述,我将把简单 GROUP BY 子句的分组称之为标准分组,把 GROUP BY 子句中出现的列(或表达式)称之为分组列。

1.1、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;

注意事项

  • 1、SELECT 子句中只能出现分组列或聚合函数或常量。
  • 2、HAVING 子句中只能出现分组列或聚合函数或常量。
  • 3、GROUP BY 子句中只能出现标量基元类型(如 VARCHAR2、NUMBER、DATE 等)的列或常量,不能出现 BLOB、CLOB 等类型的列。
  • 4、实际上分组列可以不出现在 SELECT 子句中,但这可能会让查询结果含义不明确。
  • 5、在 SELECT、HAVING、GROUP BY 子句中还可以出现某些特殊的函数,如 SYSDATE(感觉没啥意义)。

1.2、WHERE 和 HAVING 的区别?

我首先要说明的是:“WHERE 和 HAVING 的区别?”这绝对是一个有问题的问题!因为它两根本就没有可比性,实际上问这种问题的人,往往 SQL 基础也不够扎实。

在包含 GROUP BY 子句的查询语句中:WHERE 子句的作用是在对查询结果分组前过滤行数据,将不符合条件的行去掉;而 HAVING 子句的作用是在对查询结果分组后过滤组数据,将不符合条件的组去掉。换句话说,因为聚合函数的作用是提供有关组的信息,还没分组之前当然是无法提供组的信息的,也就是说 WHERE 子句中无法使用聚合函数,这也正是会出现 HAVING 子句的原因。有了 HAVING 子句,我们就可以很方便的在分组之后对组的数据进行过滤了。一般来说,能用 WHERE 的过滤的就不应该用 HAVING 过滤!

2、GROUP BY 扩展分组

在实际业务开发中,只有标准分组可能还不够,往往还需要更多维度的小计、合计。针对这类需求,Oracle 提供了丰富的扩展分组功能;尽管用 UNION ALL 一般也能实现类似效果,但不够灵活且性能比较低。

2.1、ROLLUP 分组

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

2.2、CUBE 分组

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

2.3、GROUPING SETS 分组

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

2.4、复杂分组(组合列分组、重复列分组、连接分组)

简单来说:组合列分组就是允许 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。

按我的理解来说:复杂分组无非也就是对标准扩展分组的综合运用。在实际开发中,可能会遇到一些仅使用标准扩展分组无法实现的需求,这时候就可以考虑灵活的运用标准扩展分组,通过复杂分组来实现。

3、GROUP BY 扩展函数

3.1、GROUPING 函数

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

3.2、GROUPING_ID 函数

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

3.3、GROUP_ID 函数

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

4、总结

本文主要讲述了 Oracle 中分组查询的标准分组、扩展分组、扩展函数等 GROUP BY 相关的知识点。

本文链接http://www.cnblogs.com/hanzongze/p/oracle-group-by.html
版权声明:本文为博客园博主 韩宗泽 原创,作者保留署名权!欢迎通过转载、演绎或其它传播方式来使用本文,但必须在明显位置给出作者署名和本文链接!本人初写博客,水平有限,若有不当之处,敬请批评指正,谢谢!

posted @ 2017-03-15 08:47  韩宗泽  阅读(1754)  评论(0编辑  收藏  举报
回到顶部