报表开发之扩展GROUP BY

时间:2022-03-17 03:00:27

在实际运用中。比方在数据仓库中,常常须要对数据进行多维分析。不仅须要标准分组的结果(相当于

GROUP BY),还须要不同维度的小计(简单 GROUP BY 中取部分列分组)和合计(不分组)。从而

提供多角度的数据分析。对于这样的复杂分组需求,简单 GROUP BY 非常难达到这样的目的,当然。我们能够

使用 UNION 或 UNION ALL 将不同维度的分组结果联合起来,但性能往往不好,此时,我们能够使用扩

展 GROUP BY 来满足实际运用中出现的大部分多维分组问题。

1. 扩展 GROUP BY 概述

扩展 GROUP BY 进行多维数据统计的工作。主要表如今:

a. ROLLUP、CUBE、GROUPING SETS 扩展 GROUP BY 子句提供了丰富的多维分组统计功能。
b. 3个扩展分组函数:GROUPING、GROUPING_ID、GROUP_ID 提供扩展 GROUP BY 的辅助功
能。比如。提供差别结果行属于哪个分组级别、区分 NULL 值、建立有意义的报表、对汇总结果排
序、过滤结果行等功能
c. 对扩展 GROUP BY 同意按反复列分组、组合列分组、部分分组、连接分组等,另外 GROUPING 
SETS 能够接受 CUBE、ROLLUP 操作作为參数,这些功能使扩展 GROUP BY 更加强大。


2. ROLLUP
2.1 UNION ALL 实现 ROLLUP 功能
如果有这种需求:
a. 统计每一个部门每一个职位的薪水和
b. 统计每一个部门全部职位的薪水小计
c. 统计全部部门全部职位的薪水合计
d. 须要显示部门名、职位名和累加后的薪水值

-- 需求一实现
select d.dname,e.job,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by d.dname,e.job
union all
-- 需求二实现
select d.dname,null,sum(e.sal) sum_sal from dept d,emp e 
where d.deptno=e.deptno group by d.dname
union all
-- 需求三实现
select null,null,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno

上面的代码通过运行计划(set autotrace on)能够发现,须要多次訪问EMP、DEPT表的索引,假设
实际运用中表的结构非常复杂,将严重影响性能。

2.2 ROLLUP 分组
从 Oracle 8i 開始,Oracle 使用 ROLLUP 对 GROUP BY 进行扩展,它同意计算标准分组及对应维度
 的小计、合计。
ROLLUP 的语法结构例如以下:
SELECT ... GROUP BY ROLLUP(grouping_column_reference_liist)
ROLLUP 后面指定的列以逗号分隔,ROLLUP 的计算和其后面指定列的顺序有关,由于 ROLLUP 分组
过程具有方向性,先计算标准分组,然后列从右向左递减计算更高一级的小计,一直到列所有被选完,
最后计算合计。
假设 ROLLUP 中指定 n 列,则整个计算过程中的分组方式有n+1种。

-- 使用ROLLUP 实现 2.1 节的需求
select d.dname,e.job,sum(e.sal) from dept d,emp e 
where d.deptno=e.deptno group by rollup(d.dname,e.job);

ROLLUP 分组具有方向性,从上面的结果能够看出,ROLLUP(d.dname,e.job) 分组的过程是:
a. 标准分组:GROUP BY(d.dname,e.job),对每一个部门每一个职位进行分组;
b. 从右到左递减:GROUP BY(d.dname,null)。事实上这个null没有必要使用,这里仅仅是方便分析, 
    这个过程是对上个级别分组的小计,也就是对每一个 dname 值。计算横跨全部 job 的小计。
c. 最后合计:相当于 GROUP BY(null,null)。
再比如 ROLLUP(a,b,c)
报表开发之扩展GROUP BY

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjQ1NjkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">


范例:实现下面需求
a. 计算每一个入职时间(年)、部门、职位的标准分组的薪水和
b. 计算每一个入职时间(年)、部门的全部职位的薪水小计
c. 计算每一个入职时间(年)的全部部门全部职位的薪水小计
d. 最后合计薪水,显示入职时间(年)、部门名、职位名

with t as (

  select to_char(e.hiredate,'yyyy') hireyear,d.dname,e.job,sum(e.sal) sum_sal from emp e,dept d

where e.deptno=d.deptno group by rollup(to_char(e.hiredate,'yyyy'),d.dname,e.job))

select rownum,t.* from t;

接下来分析上述代码的结果:
报表开发之扩展GROUP BY

由于 ROLLUP 分组过程具有方向性,所以通过改变 ROLLUP 中列的顺序就能够达到改变报表结果和含义的目的,
如如今须要查询的是 标准分组、计算每一个 job 的全部部门的小计、最后合计。则代码为:
select e.job,d.dname,sum(e.sal) sum_sal from emp e,dept d
where e.deptno=d.deptno group by rollup(e.job,d.dname);

2.3 部分 ROLLUP 分组
通过将部分列从 ROLLUP 中移出来,放在 GROUP BY 中,这样合计肯定没有了,某些小计也没有了。
需求:不须要每一个入职时间(年)的全部部门全部职位的薪水小计,合计也不须要
select to_char(hiredate,'yyyy'),d.dname,e.job,sum(e.sal) sum_sal from emp e,dept d 
where e.deptno=d.deptno group by to_char(hiredate,'yyyy'),d.dname,rollup(e.job);
<=>
select to_char(hiredate,'yyyy'),d.dname,e.job,sum(e.sal) sum_sal from emp e,dept d
where e.deptno=d.deptno group by to_char(hiredate,'yyyy'),d.dname,e.job
union all
select null,null,null,sum(e.sal) sum_sal from emp e,dept d
where e.deptno=d.deptno group by to_char(hiredate,'yyyy'),d.dname;
注:将 hiredate 和 dname 从 ROLLUP 中移出来。就能够将每一个入职时间(年)的全部部门全部职位的
薪水小计及合计剔除,终于仅仅查询标准分组和每一个入职时间(年)、部门的全部职位的小计。
2.4 ROLLUP 总结
先进行标准分组,在标准分组的基础上通过将列从右向左移动。然后进行更高一级的小计。最后合计。                     

3. CUBE
CUBE 是对不同维度的全部可能分组进行统计。从而生成交叉报表;这样的需求比 ROLLUP更加精细。
包括了 ROLLUP 的统计结果,并且还有其它组合分组结果(小计)。
3.1 CUBE 分组
CUBE语法结构:
SELECT ... GROUP BY CUBE(grouping_column_reference_list)
假设 CUBE 中指定 n 列。则整个计算过程中的分组方式有 power(2,n) 种。

将 2.2 节使用 ROLLUP(dname,job) 替换为 CUBE
select d.dname,e.job,sum(e.sal) from dept d,emp e
where d.deptno=e.deptno group by cube(d.dname,e.job);
下图分析 CUBE(dname,job) 相应分组级别:
报表开发之扩展GROUP BY

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjQ1NjkyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">


报表开发之扩展GROUP BY

3.2 部分 CUBE 分组
和 ROLLUP 一样。也有部分 CUBE 操作,能够去掉合计及某些不须要的小计。比方上面的 GROUP BY
CUBE(d.dname,e.job) 改为 GROUP BY d.dname CUBE(e.job) 则剔除了合计及GROUP BY job。
select d.dname,e.job,sum(e.sal) sum_sal from dept d,emp e 
where d.deptno=e.deptno group by d.dname,cube(e.job);
<=>
select d.dname,e.job,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno
group by d.dname,e.job
union all
select null,null,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by d.dname;

3.3 CUBE总结
先进行合计。然后小计,最后再按标准分组


4. GROUPING SETS 实现小计
前面所说的两种多维数据统计的方法,即 ROLLUP 和 CUBE,它们的输出结果是由相应分组的行伴随
着小计行产生的,它们会产生标准分组、各种小计及总计。可是有时候我们仅仅关心某个单列分组,从而
得到其他维度小计的信息,这样就须要使用 GROUPING SETS扩展分组,它是Oracle9i提供的。
比方 GROUP BY GROUPING SETS(a,b,c) 相当于 GROUP BY a、GROUP BY b、GROUP BY c 这三
个单列分组。从而得到其它维度的小计信息。
n列的 GROUPING SETS 的分组总类有 n 个。
4.1 GROUPING SETS 分组
语法结构:
SELECT ... GROUP BY GROUPING SETS(grouping_column_reference_list)
将2.2节中的 ROLLUP 改为 GROUPING SETS
select to_char(e.hiredate,'yyyy') hireyear,d.dname,e.job,sum(e.sal) sum_sal from emp e,dept d
where e.deptno=d.deptno group by grouping sets(to_char(e.hiredate,'yyyy'),d.dname,e.job);
注:GROUPING SETS 的结果是分别按单列分组后 UNION ALL的结果;
        GROUPING SETS 的结果和列的顺序没有关系。并且结果的顺序也是无序的。

4.2 部分 GROUPING SETS 分组
select d.dname,to_char(e.hiredate,'yyyy') hireyear,e.job,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno
group by d.dname,grouping sets(to_char(e.hiredate,'yyyy'),e.job);
<=>
select d.dname,to_char(e.hiredate,'yyyy') hiredate,null job,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by d.dname,to_char(e.hiredate,'yyyy')
union all
select d.dname,null,e.job,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by d.dname,e.job;
上述语句统计的是:对于每一个部门每一个入职时间(年),对全部职位进行小计及
                             对于每一个部门每一个职位,对每一个入职时间(年)进行小计。

4.3 CUBE、GROUPING 作为 GROUPING SETS 的參数
GROUPING SETS 操作可以接受 ROLLUP 和 CUBE 作为它的參数。GROUPING SETS 操作仅仅对
单列进行分组,而不提供合计的功能。假设须要 GROUPING SETS 提供合计的功能。那么能够使
用 ROLLUP 或 CUBE 作为 GROUPING SETS 的參数。
改写前面的 GROUPING SETS(d.dname,e.job)。提供合计功能。
select d.dname,e.job,sum(e.sal) from dept d,emp e
where d.deptno=e.deptno
group by grouping sets(rollup(d.dname),rollup(e.job));
<=>
select d.dname,null job,sum(e.sal) sum_sal from dept d,emp e 
where d.deptno=e.deptno
group by rollup(d.dname)
union all
select null dname,e.job,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno
group by rollup(e.job);
注:上述语句会产生两个合计行,由于 ROLLUP 或 CUBE 作为 GROUPING SETES 的參数,相当
       于对每一个 ROLLUP 或 CUBE 操作的 UNION ALL;
       可使用 DISTINCT 剔除反复行。
       ROLLUP 和 CUBE 不能接受 GROUPING SETS 作为參数。ROLLUP 和 CUBE 之间相互作为
       參数也不能够。

4.4 GROUPING SETS 总结
GROUPING SETS 的结果和列的顺序没有关系,并且结果的顺序也是无序的。


5. 组合列分组、连接分组、反复列分组
组合列分组、连接分组、反复列分组都是Oracle  9i 中才有的特性。组合列也就是将多个列用括号括
起来,从而将多个列当做总体对待。比方 GROUP BY ROLLUP((a,b),c) 相当于 GROUP BY ROLLUP(x,c)
。当中 x 相当于 (a,b) 这个组合列。

组合列一般在 in 条件中比較常见,比方:

-- where in 中使用组合列
select empno,ename,job from emp where (empno,ename) in ((7369,'SMITH'),(7499,'ALLEN'));
下图是普通列 ROLLUP 和组合列 ROLLUP 的对照(CUBE、GROUPING SETS类似)
报表开发之扩展GROUP BY

上图的组合列分组达到了剔除某些小计的功能。且保证了终于结果又合计行。

连接分组同意在 GROUP BY 之后出现多个 ROLLUP、CUBE、GROUPING SETS 操作,这样分组级别
很多其它,报表更加精细。
报表开发之扩展GROUP BY

实际上无论是同类型的连接分组还是不同类型的连接分组之间,最后的分组级别种类都是每一个扩展
分组级别种类的乘积。分组级别是笛卡尔积。比方同类型连接分组 ROLLUP(a,b)。ROLLUP(c) 终于
结果有 3*2=6 种分组级别,不同类型连接分组 ROLLUP(a,b),GROUPING SETS(c) 有3*1=3 种分
组级别。

反复列分组就是 GROUP BY 中同意反复列。比方在 ROLLUP 中使用复杂的复合列分组可能会用到,
比方 GROUP BY ROLLUP(a,(a,b))、GROUP BY a,ROLLUP(a,b) 都属于反复列。

5.1 组合列分组
组合列分组有过滤某些小计或计算一些额外的小计等功能。
前面的部分 ROLLUP、部分CUBE 都没有合计,使用组合列能够实现部分 ROLLUP、部分 CUBE的
功能,还能有合计。
需求:
a. 对部门、入职时间(年)、职位进行标准分组
b. 对每一个部门计算横跨入职时间(年)和职位的小计
c. 最后合计
select d.dname,to_char(e.hiredate,'yyyy') hireyear,e.job,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by rollup(d.dname,(to_char(e.hiredate,'yyyy'),e.job));
CUBE 和 ROLLUP 操作都能够用组合列分组转为相应的 GROUPING SETS。 比如,
ROLLUP(a,b,c) 转为等价的 GROUPING SETS 是 GROUPING SETS((a,b,c),(a,b),(a),NULL);
CUBE(a,b,c) 转为等价的 GROUPING SETS 是 GROUPING SETS((a,b,c),(a,b),(a,c),(b,c),(a),(b),(c),NULL);

5.2 连接分组
连接分组是Oracle 9i 才有的功能,它同意 GROUP BY后面有多个 ROLLUP、CUBE、GROUPING SETS,
连接分组的分组级别是由每一个 ROLLUP、CUBE、GROUPING SETS 分组组成的笛卡尔积。
比方 ROLLUP(a,b),ROLLUP(c,d,e) 共同拥有分组统计级别为 3*4=12 种。
select d.dname,e.job,to_char(e.hiredate,'yyyy') hireyear,sum(sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by rollup(d.dname,e.job),rollup(to_char(e.hiredate,'yyyy'));
GROUP BY ROLLUP(d.dname,e.job),ROLLUP(to_char(e.hiredate),'yyyy') 实现了 6 种分组结果,
相当于两个 ROLLUP 的笛卡尔积。例如以下表:
报表开发之扩展GROUP BY

CUBE、GROUPING SETS 都类似,利用连接分组,CUBE 能够用 ROLLUP转换:
a. 当仅仅有一列的时候。比方 ROLLUP(a) 与 CUBE(a) 是一样的,都有两种统计方式;
b. 当有 n 列的时候,比方 CUBE(a,b,c) 能够转为 ROLLUP(a),ROLLUP(b),ROLLUP(c) 的连接分组表示。
也就是有 n 列的 CUBE 转为 ROLLUP 则须要拆开。转为单列 ROLLUP的连接分组就可以。
select d.dname,e.job,to_char(e.hiredate,'yyyy') hireyear,sum(e.sal) sum_sal from dept d,emp  e
where d.deptno=e.deptno group by rollup(d.dname),rollup(e.job),rollup(to_char(e.hiredate,'yyyy'));
<=> group by cube(d.dname,e.job,to_char(hiredate,'yyyy'));
注:连接分组通常是同类型的连接分组,不同类型的连接分组比方 GROUP BY ROLLUP...CUBE... 等是不经常使用的,
        除非有复杂需求。

5.3 反复列分组
反复列分组也是Oracle 9i 才有的,也就是 GROUP BY 后面同意反复列。
select d.dname,e.job,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by d.dname,rollup(d.dname,e.job);
<=>
select d.dname,e.job,sum(e.sal) sum_sal from dept d,emp e 
where d.deptno=e.deptno group by d.dname,e.job
union all
select null,null,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by d.dname
union all
select null,null,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by d.dname;

5.4 组合列分组、连接分组、反复列分组总结
a. 组合列主要实现剔除某些不必要的小计保留合计;
b. 连接分组按每一个扩展分组的分组级别的笛卡尔积形式进行操作。分组类型很多其它更细。
    比方 ROLLUP 连接分组就实现了类似 CUBE 的功能。



6. 3个扩展分组函数:GROUPING、GROUPING_ID、GROUP_ID 
主要内容有:
a. 使用 GROUPING 函数制作有意义的报表,以及对结果进行过滤;
b. 使用 GROUPING_ID 函数对结果进行过滤及排序。
c. 使用 GROUP_ID 函数剔除反复行。

6.1 GROUPING 函数
对扩展 GROUP BY 子句来说,比方 ROLLUP、CUBE 会生成标准分组、一系列小计及合计。这样查询结果中,
有些行的列值就会存在 NULL。NULL 在扩展 GROUP BY 中有特殊的意义。结果行中的列值为 NULL,一般
就意味着是此列的小计或合计,可是 NULL 也有可能是原始数据存在的 NULL(如 emp.mgr=NULL),所以引入
了 GROUPING 函数专门处理扩展GROUP BY 分组结果中 NULL 的问题:
a. 它仅仅接受一个參数,此參数来自 ROLLUP、CUBE、GROUPING SETS 中的列;
b. GROUPING 函数对于是小计或合计的列返回 1,否则返回 0。

假设小计或合计列的值是 NULL,可是原始

     数据可能也存在 NULL,则常使用 GROUPING 函数来区分终于结果行中的 NULL 是原始数据中存在的,
     还是小计或合计列的值,常和 DECODE 函数配合使用。
6.1.1 用于格式化报表,生成有意义的报表
select d.dname,e.mgr,sum(e.sal) from dept d,emp e 
where d.deptno=e.deptno group by rollup(d.dname,e.mgr);   <== 对于每一个dname,计算横跨 mgr 列的小计
上述结果中第 9 行和第 11 行的 mgr 列都为 NULL,无法区分哪个列是小计。此时就能够使用 GROUPING
函数进行区分。
select d.dname,e.mgr,sum(e.sal),grouping(mgr) from dept d,emp e 
where d.deptno=e.deptno group by rollup(d.dname,e.mgr);  
上述结果中第 8 行的 GROUPING(mgr)=0,第 11 行为 1,全部第 8 行的 mgr 列不是小计列,11 行才是。
以下使用DECODE + GROUPING 来制作有意义的报表:
select decode(grouping(d.dname),1,'TOTAL_DEPT',d.dname) dname,
            decode(grouping(e.mgr),1,'SUBTOTAL_DEPT',nvl(to_char(e.mgr),'BOSS')) mgr,
            sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by rollup(d.dname,e.mgr);

假设要将全部的 mgr 列小计一起放在后面显示,位置在合计之间,这样的需求怎样解决?(详见GROUPING_ID)

6.1.2 过滤某些分组结果
一般使用 GROUPING_ID 取代
需求:对 group by rollup(d.dname,e.mgr,e.job) 的结果保留合计和标准分组
select d.dname,e.mgr,e.job,sum(e.sal) sum_sal  from dept d,emp e 
where d.deptno=e.deptno group by rollup(d.dname,e.mgr,e.job)
having grouping(d.dname)=1 or grouping(e.job)=0;

6.2 GROUPING_ID 函数
GROUPING 函数用来生成有意义的报表及过滤一些分组级别;
GROUPING_ID 函数主要用来过滤分组级别和排序结果(显示排序)。
无论 ROLLUP、CUBE、GROUPING SETS 的结果是否有默认顺序,都是不可靠的。
GROUPING_ID 函数能够接受对个參数。这些參数来自于 ROLLUP、CUBE、GROUPING SETS中的
列(參数来源和 GROUPING 函数一致),按列从左到右顺序计算,假设此列是分组列则为 0 ,假设是
对此列的小计或合计则为 1,然后按列顺序将计算结果组成二进制序列(位向量),最后将位向量转为
十进制数。如CUBE(a,b),则GROUPING_ID(a,b) 的结果例如以下图所看到的:
报表开发之扩展GROUP BY

GROUPING_ID的优点就是能够对多列进行计算,从而得到此列的分组级别。
从上图能够看出。GROUPING_ID(column_list) 中的 colum_list 和扩展分组保持一致,那么 GROUPING_ID
值得种类必须与相应扩展分组数目保持一致:
比方 种,
种。

GROUPING_ID 的取值范围都一样,和列的数目有关,比方有 n 列,则 GROUPING_ID的取值范围在[ 0-2^n-1 ]

6.2.1 GROUPING_ID 函数过滤某些分组结果
需求:改写6.1 节 GROUPING 过滤结果的样例,用 GROUPING_ID 实现同等功能。
先分析对于 ROLLUP(d.dname,e.mgr,e.job) 使用 GROUPING_ID 函数的结果,注意的是,一般使用
GROUPING_ID函数,列的顺序要与 ROLLUP、CUBE、GROUPING SETS 中的顺序保持一致。
报表开发之扩展GROUP BY

从表中就能够清楚的看出,实现这个需求仅仅要 GROUPING_ID(d.dname,e.mgr,e.job) 取 0 和 7 就可以。
select d.dname,e.mgr,e.job,sum(e.sal) sum_sal from dept d,emp e 
where d.deptno=e.deptno group by rollup(d.dname,e.mgr,e.job)
having grouping_id(d.dname,e.mgr,e.job) in (0,7)
6.2.2 使用 GEOUPING_ID 对结果进行排序
对扩展分组,须要手动进行有意义的排序。
需求:将全部的 mgr 列小计一起放在后面显示,位置在合计之前。
select decode(grouping_id(d.dname),1,'TOTAL_EPT',d.dname) dname,
            decode(grouping_id(e.mgr),1,'SUBTOAL_DEPT',nvl(to_char(e.mgr),'BOSS')) mgr,
            sum(e.sal) sum_sal
from dept d,emp e where d.deptno=e.deptno
group by rollup(d.dname,e.mgr) order by grouping_id(d.dname,e.mgr);
注:排序还有非常多灵活性,比方改变 GROUPING_ID 中列的属顺序,就能够达到不同的排序效果:
        order by grouping_id(d.dname,e.job,e.mgr);
        order by grouping_id(d.dname,e.mgr,e.job);
注:GROUPING  函数也能用来排序。甚至能够将 GROUPING 函数与 GROUPING_ID 函数 排序结合起来。
select d.dname,to_char(e.hiredate,'yyyy') hireyear,e.job,sum(e.sal) sum_sal,
grouping(d.dname)+grouping(to_char(e.hiredate,'yyyy'))+grouping(e.job) ord1,
grouping_id(d.dname,to_char(e.hiredate,'yyyy'),e.job) ord2
from dept d,emp e where d.deptno=e.deptno
group by cube(d.dname,to_char(e.hiredate,'yyyy'),e.job)
order by grouping(d.dname)+grouping(to_char(e.hiredate,'yyyy'))+grouping(e.job) desc,
grouping_id(d.dname,to_char(e.hiredate,'yyyy'),e.job);

6.3 GROUP_ID 函数
GROUP_ID 函数无參数,能够区分反复分组结果。第一次出现为 0。以后每次出现增 1。
须要注意的是。DISTINCT是剔除反复行,而不是按反复分组级别剔除,GROUP_ID 函数则是
按反复分组级别来剔除。
-- 为了获得非反复统计,仅仅须要 GROUP_ID()=0 就可以
select d.dname,e.job,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by grouping sets(rollup(d.dname),rollup(e.job))
having group_id()=0;

6.4 扩展 GROUP BY 函数总结
a. GROUPING 函数用于制作有意义的报表。以及对结果进行过滤(对于小计或合计的列返回1,否则返回0);
b. GROUPING_ID 函数对结果行进行过滤(此列为分组列则为0,此列是小计或合计则为1)和排序;
c. GROUP_ID 函数无參数。用于剔除反复行(按反复分组级别来剔除,第一次出现为0,以后出现每次增1);
d. GROUPING 函数和 GROUPING_ID 函数是没有组合列的,也就是里面不能加括号,比方
    GROUPING_ID((a,b),c) 是错的,并且它们要来自于分组列。


7. 扩展分组综合实例 
7.1 需求:依照规则生成报表,而且排序
select decode(grouping_id(order_date,order_no,order_book),6,'SUM('||order_book||')',
                     7,'SUM',order_date)/*order_date1*/,
          order_no,
          decode(grouping_id(order_date,order_no,order_book),6,NULL,order_book) /*order_book1*/,
          sum(order_fee) order_fee,
          sum(order_num) order_num
from t_order
group by rollup(order_book,(order_date,order_no))
order by order_book,order_date;
7.2 实现类似 SQL*PLUS 的 BREAK 报表功能
BREAK 的功能就是可以替换反复出现的单元格值为空格,即在做报表的时候,对同一个大类,
仅仅须要第一次出现的时候单元格值保留就可以。
需求:将反复的部门名替换为空格
SCOTT>BREAK ON dname
SCOTT>select d.dname,e.job,sum(e.sal) sum_sal from dept d,emp e
                where d.deptno=e.deptno group by rollup(d.dname,e.job)
                order by 1,2;
这样报表的可读性就更强了。但这是利用 SQL*PLUS 中的BREAK指令实现的。
7.3 利用分析函数实现类似 BREAK 的功能
select decode(lag(d.dname,1)over(partition by d.dname order by d.dname,e.job),
                           d.dname,NULL,d.dname) dname,
            e.job,sum(e.sal) sum_sal from dept d,emp e
where d.deptno=e.deptno group by rollup(d.dname,e.job)
order by d.dname,e.job;


8. 分组求和后的行列转换实例
需求:生成全部员工薪水等级分布情况的报表。格式例如以下
报表开发之扩展GROUP BY

分析:
a. 须要求出每一个 deptno,横跨全部 grade 列的小计;
b. 须要求出每一个 grade。横跨全部 deptno 列的小计。
所以能够选择 CUBE(deptno,grade)
c. 使用 grouping_id(deptno,grade) 能够推断哪些列是小计、合计,及分组列;
d. 使用 decode 进行行转列。
with t as (
select deptno,grade,count(*) cnt,grouping_id(deptno,grade) gg_id
from emp,salgrade where sal between losal and hisal 
group by cube(deptno,grade) order by deptno,grade),
u as (select cast(decode(gg_id,2,'TOTAL',3,'TOTAL',deptno) as varchar2(10)) deptno,
decode(gg_id,1,cnt,3,cnt) subtotal,decode(grade,1,cnt) grade1,decode(grade,2,cnt) grade2,
decode(grade,3,cnt) grade3,decode(grade,4,cnt) grade4,decode(grade,5,cnt) grade5 from t)
select deptno,max(subtotal) subtotal,max(grade1) grade1,max(grade2) grade2, max(grade3) grade3,
max(grade4) grade4, max(grade5) grade5 from u group by deptno order by deptno;




说明:文中部分内容是从《剑破冰山 — Oracle开发艺术》中总结出来的,须要具体了解的读者能够
            去參考这本书,当然。假设文章中有不正确的地方,还望指出来,我好及时改正。
谢谢!