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

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

.Net程序员学用Oracle系列(21):分组查询(GROUP BY)的更多相关文章

  1. .Net程序员学用Oracle系列(9):系统函数(上)

    <.Net程序员学用Oracle系列:导航目录> 本文大纲 1.字符函数 1.1.字符函数简介 1.2.语法说明及案例 2.数字函数 2.1.数字函数简介 2.2.语法说明及案例 3.日期 ...

  2. .Net程序员学用Oracle系列(10):系统函数(下)

    <.Net程序员学用Oracle系列:导航目录> 本文大纲 1.转换函数 1.1.TO_CHAR 1.2.TO_NUMBER 1.3.TO_DATE 1.4.CAST 2.近似值函数 2. ...

  3. .Net程序员学用Oracle系列(11):系统函数(下)

    1.聚合函数 1.1.COUNT 函数 1.2.SUM 函数 1.3.MAX 函数 1.4.MIN 函数 1.5.AVG 函数 2.ROWNUM 函数 2.1.ROWNUM 函数简介 2.2.利用 R ...

  4. .Net程序员学用Oracle系列(15):DUAL、ROWID、NULL

    1.DUAL 表 2.ROWID 类型 2.1.利用 ROWID 查询数据 2.2.利用 ROWID 更新数据 3.NULL 值 3.1.NULL 与空字符串 3.2.NULL 与函数 3.3.NUL ...

  5. .Net程序员学用Oracle系列(30):零碎补充、最后总结(The End)

    1.同义词 2.Flashback 技术 3.连接字符串的写法 4.转义字符 & 特殊运算符 5.文件类型 6.查看参数 & 修改参数 7.AWR 工具 8.学习方法 & 学习 ...

  6. .Net程序员学用Oracle系列(1):导航目录

    本人从事基于 Oracle 的 .Net 企业级开发近三年,在此之前学习和使用的都是 (MS)SQL Server.未曾系统的了解过 Oracle,所以长时间感到各种不习惯.不方便.怪异和不解,常会遇 ...

  7. .Net程序员学用Oracle系列(2):准备测试环境

    <.Net程序员学用Oracle系列:导航目录> 本文大纲 1.创建说明 1.1.为什么要创建的测试环境? 1.2.了解 Oracle 实例的默认用户 2.创建环境 2.1.创建基本环境 ...

  8. .Net程序员学用Oracle系列(6):表、字段、注释、约束、索引

    <.Net程序员学用Oracle系列:导航目录> 本文大纲 1.表 1.1.创建表 1.2.修改表 & 删除表 2.字段 2.1.添加字段 2.2.修改字段 & 删除字段 ...

  9. .Net程序员学用Oracle系列(7):视图、函数、过程、包

    <.Net程序员学用Oracle系列:导航目录> 本文大纲 1.视图 1.1.创建视图 2.函数 2.1.创建函数 2.2.调用函数 3.过程 3.1.创建过程 3.2.调用过程 4.包 ...

随机推荐

  1. Flex 开发框架汇总

    1.现有成熟Flex框架   Cairngorm (Adobe Open Source) - MVC framework   PureMVC (Open Source) - MVC framework ...

  2. SVG六基本元素

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. 树莓派VNC搭建相关问题,啦啦啦~

    为了节省money,于是我决定用VNC界面来代替显示器,为后面做C++ Qt以及Python Qt开发打下基础,我别无选择!下面开始进入正题: 1-- 下载VNC-Viewer-6.0.1-Windo ...

  4. Vue.js自定义指令的用法与实例

    市面上大多数关于Vue.js自定义指令的文章都在讲语法,很少讲实际的应用场景和用例,以致于即便明白了怎么写,也不知道怎么用.本文不讲语法,就讲自定义指令的用法. 自定义指令是用来操作DOM的.尽管Vu ...

  5. python属性查找(attribute lookup)

    在Python中,属性查找(attribute lookup)是比较复杂的,特别是涉及到描述符descriptor的时候.     在上一文章末尾,给出了一段代码,就涉及到descriptor与att ...

  6. Javascript中的async await

    async / await是Javascript是ES7的重要特性之一,也是目前社区里公认的优秀异步解决方案.目前,async / await这个特性已经是stage 3的建议,可以看看TC39的进度 ...

  7. BOM数据基础 - Mobox物料编码管理及实现

    1 企业现状 在企业日常经营过程中会产生大量的文档,如设计图纸.变更单.计算书.设计方案等,如果是制造企业还会产生大量的产品.组成产品的零部件等物料,这些数据在进入信息系统前都需要有一个唯一的标识,也 ...

  8. 《JavaScript DOM 编程艺术 》 笔记

    一:这本书由几个案列带入知识点,通俗易懂.最大的收获莫过于作者多次提到的逐渐增强和平稳退化. "渐进增强"指的是给所用用户同等的基本使用体验,再根据用户终端的级别给予更高级的用户更 ...

  9. 关于data-xxx属性大小写不敏感,不识别大写的几点总结

    1.所有标签属性,没有大小写区分,都是小写,如:data-userID,在输出后会变成data-userid,前者只能获取到'undefined'. 2.dataset 自动把 - 转换为驼峰.类似的 ...

  10. 深入浅出ThreadLocal

    前言 ThreadLocal为变量在每个线程中都创建了一个副本,所以每个线程可以访问自己内部的副本变量,不同线程之间不会互相干扰.本文会基于实际场景介绍ThreadLocal如何使用以及内部实现机制. ...