Oracle SQL篇(四)group by 分组与分组的加强 rollup

   

分组操作group by 和分组的强化(rollup)

分组操作和分组函数的使用,对于编写SQL语句的人来说,是最基本的概念。
我们来看下面的例子:
在这里我们使用员工表EMP
[email protected]> select * from emp;

EMPNO
ENAME     
JOB             
MGR
HIREDATE                  
SAL      
COMM    
DEPTNO
---------- ---------- --------- ---------- -------------------
---------- ---------- ----------
     
7369
SMITH     
CLERK          
7902 1980-12-17
00:00:00       
800                   
20
     
7499
ALLEN     
SALESMAN       
7698 1981-02-20
00:00:00      
1600       
300        
30
     
7521
WARD      
SALESMAN       
7698 1981-02-22
00:00:00      
1250       
500        
30
     
7566
JONES     
MANAGER        
7839 1981-04-02
00:00:00      
2975                   
20
     
7654
MARTIN    
SALESMAN       
7698 1981-09-28
00:00:00      
1250      
1400        
30
     
7698
BLAKE     
MANAGER        
7839 1981-05-01
00:00:00      
2850                   
30
     
7782
CLARK     
MANAGER        
7839 1981-06-09
00:00:00      
2450                   
10
     
7788
SCOTT     
ANALYST        
7566 1987-04-19
00:00:00      
3000                   
20
     
7839
KING      
PRESIDENT           
1981-11-17
00:00:00      
5000                   
10
     
7844
TURNER    
SALESMAN       
7698 1981-09-08
00:00:00      
1500         
0        
30
     
7876
ADAMS     
CLERK          
7788 1987-05-23
00:00:00      
1100                   
20
     
7900
JAMES     
CLERK          
7698 1981-12-03
00:00:00       
950                   
30
     
7902
FORD      
ANALYST        
7566 1981-12-03
00:00:00      
3000                   
20
     
7934
MILLER    
CLERK          
7782 1982-01-23
00:00:00      
1300                   
10
14 rows selected.

在员工表中有14条记录,即14个员工,我们可以看到,这14个员工分别属于3个部门(10,20,30),我们可以提出求EMP表中,每个部门的员工薪水总和

[email protected]> select deptno,sum(sal) tsal
 
2        
from emp
 
3        
group by deptno;

DEPTNO      
TSAL
---------- ----------
       
30      
9400
       
20     
10875
       
10      
8750

在这里稍微需要注意的是:select 列表里如果出现列的话,那在group
by语句中同样需要列名,并且只能是列名本身,不能是列的别名。group
by语句可以说是oracle语句里最严格的语句,后面只能跟列的真名,别名、位置号、函数、表达式、子查询
都不被允许。当然如果只考虑实现这里已经做到了,如果我们深入了解一点的话,分组对于数据库来说是要消耗资源的,比如cpu、内存
在oracle9i之前
,分组操作内部主要通过排序来实现,10刚开始,采用hash的算法实现,我们看一下10g下,让面语句的执行计划
[email protected]> set autotrace trace exp
[email protected]> /
Execution Plan
----------------------------------------------------------
Plan hash value: 4067220884
---------------------------------------------------------------------------

| Id  |
Operation         
| Name | Rows  | Bytes | Cost (%CPU)|
Time    
|
---------------------------------------------------------------------------

|   0 | SELECT
STATEMENT  
|     
|    14
|   364
|    
4  (25)| 00:00:01 |
|   1 |  HASH
GROUP
BY    
|     
|    14
|   364
|    
4  (25)| 00:00:01 |
|   2
|   TABLE ACCESS FULL|
EMP 
|    14
|   364
|    
3   (0)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
   - dynamic sampling used for
this statement

其实在有些情况下,我们可以避免hash或是sort的发生,也可以实现分组查询的效果,比如说通过索引,当然这需要你有适当的索引存在。

我们来看下面的演示:
[email protected]> set autotrace off
[email protected]> create table  s_test(id
number,name varchar2(10),sal number);
Table created.

[email protected]> begin
 
2        
for  i in 1..20000 loop
 
3             
insert into s_test values(i,i||‘name‘,i*10);
 
4        
end loop;
 
5        
commit;
 
6        
end;
 
7        
/
PL/SQL procedure successfully completed.
[email protected]> /

PL/SQL procedure successfully completed.
[email protected]> /

PL/SQL procedure successfully completed.

[email protected]> select count(*) from s_test;

COUNT(*)
----------
    
60000
我在这里建了一张表s_test,分3次往表里插入数据1-20000,现在我的需求是,找到表里100-120的记录,以及他们出现的次数

[email protected]> select id,name,count(*) from s_test
where id>=100 and id<=120 group by
id,name;

ID
NAME        
COUNT(*)
---------- ---------- ----------
      
115
115name            
3
      
101
101name            
3
      
103
103name            
3
      
106
106name            
3
      
109
109name            
3
      
118
118name            
3
      
105
105name            
3
      
114
114name            
3
      
102
102name            
3
      
104
104name            
3
      
112
112name            
3
      
116
116name            
3
      
100
100name            
3
      
110
110name            
3
      
113
113name            
3
      
117
117name            
3
      
119
119name            
3
      
107
107name            
3
      
108
108name            
3
      
111
111name            
3
      
120
120name            
3
21 rows selected.

我们来看一下语句的执行计划
[email protected]> set autotrace trace exp
[email protected]> /

Execution Plan
----------------------------------------------------------
Plan hash value: 752916570
-----------------------------------------------------------------------------

| Id  |
Operation         
| Name   | Rows 
| Bytes | Cost (%CPU)|
Time    
|
-----------------------------------------------------------------------------

|   0 | SELECT
STATEMENT  
|       
|   163 |  3260
|   
58   (6)| 00:00:01 |
|   1 |  HASH
GROUP
BY    
|       
|   163 |  3260
|   
58   (6)| 00:00:01 |
|*  2 |   TABLE
ACCESS FULL| S_TEST |   163
|  3260
|   
57   (4)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 -
filter("ID">=100 AND "ID"<=120)

Note
-----
   - dynamic sampling used for
this statement

在执行计划中我们发现,成本Cost是58,还有cpu的消耗,在执行计划的第2步,我们发现为了实现分组,oracle做了hash。接下来我们建一个组合索引看看

[email protected]> create index s_id_n_idx on
s_test(id,name);
Index created.

[email protected]> select id,name,count(*) from s_test
where id>=100 and id<=120 group by
id,name;
Execution Plan
----------------------------------------------------------
Plan hash value: 826362002
-----------------------------------------------------------------------------------

| Id  |
Operation           
|
Name      
| Rows  | Bytes | Cost (%CPU)|
Time    
|
-----------------------------------------------------------------------------------

|   0 | SELECT
STATEMENT    
|           
|    63
|  1260
|    
2   (0)| 00:00:01 |
|   1 |  SORT
GROUP BY
NOSORT|           
|    63
|  1260
|    
2   (0)| 00:00:01 |
|*  2 |   INDEX
RANGE SCAN   | S_ID_N_IDX
|    63
|  1260
|    
2   (0)| 00:00:01 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 -
access("ID">=100 AND "ID"<=120)
      
filter("ID">=100 AND "ID"<=120)
Note
-----
   - dynamic sampling used for
this statement

在第一步中,应该做的排序并没有做 SORT GROUP BY NOSORT,这样就节省了cpu。
当然在这个例子当中,我们发现了一个重要的问题,就是语句的成本急剧下降,当然,这是通过索引,改变了数据的访问方法造成的,以后有机会在讨论索引的时候,我们会展开来说。

我们接下来看这样一个需求,根据表里的deptno和job求分组,得到每个job下的薪水综合,然后在部门级别做汇总,求小计,在整张表汇总,求总计

[email protected]> select deptno,job,empno,ename,sal from
emp order by deptno,job;

DEPTNO
JOB           
EMPNO
ENAME            
SAL
---------- --------- ---------- ---------- ----------
       
10
CLERK          
7934
MILLER          
1300
       
10
MANAGER        
7782
CLARK           
2450
       
10
PRESIDENT      
7839
KING            
5000
       
20
ANALYST        
7788
SCOTT           
3000
       
20
ANALYST        
7902
FORD            
3000
       
20
CLERK          
7876
ADAMS           
1100
       
20
CLERK          
7369
SMITH            
800
       
20
MANAGER        
7566
JONES           
2975
       
30
CLERK          
7900
JAMES            
950
       
30
MANAGER        
7698
BLAKE           
2850
       
30
SALESMAN       
7654
MARTIN          
1250
       
30
SALESMAN       
7521
WARD            
1250
       
30
SALESMAN       
7499
ALLEN           
1600
       
30
SALESMAN       
7844
TURNER          
1500

其实需求本身很简单,如果仅仅是为了实现的话,使用集合并运算符union就可以了,不过union的效率在这里是非常的低。
[email protected]> select deptno,job,sum(sal) tsal from emp
group by deptno,job
  2  union
  3  select
deptno,to_char(null),sum(sal) from emp  group by
deptno
  4  union
  5  select
to_number(null),to_char(null),sum(sal) from emp;

DEPTNO
JOB            
TSAL
---------- --------- ----------
       
10
CLERK          
1300
       
10
MANAGER        
2450
       
10
PRESIDENT      
5000
       
10                
8750
       
20
ANALYST        
6000
       
20
CLERK          
1900
       
20
MANAGER        
2975
       
20               
10875
       
30
CLERK           
950
       
30
MANAGER        
2850
       
30
SALESMAN       
5600
       
30                
9400
                         
29025
13 rows selected.

为了得到比较高效的sql,我们可以借助于oracle分组里面的rollup来实现,我们可以得到同样的效果
[email protected]> select deptno,job,sum(sal) tsal from emp
group by rollup(deptno,job);

DEPTNO
JOB            
TSAL
---------- --------- ----------
       
10
CLERK          
1300
       
10
MANAGER        
2450
       
10
PRESIDENT      
5000
       
10                
8750
       
20
CLERK          
1900
       
20
ANALYST        
6000
       
20
MANAGER        
2975
       
20               
10875
       
30
CLERK           
950
       
30
MANAGER        
2850
       
30
SALESMAN       
5600
       
30                
9400
                         
29025
13 rows selected.

第一直观的表现,使用rollup要比使用分组再union的方法语句简单很多,更重要的是,我们只对emp访问了一次。
为了进一步比较,我们来看一下语句的执行计划
[email protected]> set autotrace trace exp

[email protected]> select deptno,job,sum(sal) tsal from emp
group by deptno,job
  2  union
  3  select
deptno,to_char(null),sum(sal) from emp  group by
deptno
  4  union
  5  select
to_number(null),to_char(null),sum(sal) from emp;

Execution Plan
----------------------------------------------------------
Plan hash value: 3412076862
-----------------------------------------------------------------------------

| Id  |
Operation           
| Name | Rows  | Bytes | Cost (%CPU)|
Time    
|
-----------------------------------------------------------------------------

|   0 | SELECT
STATEMENT    
|     
|    29
|   825
|   
14  (79)| 00:00:01 |
|   1 |  SORT
UNIQUE        
|     
|    29
|   825
|   
14  (79)| 00:00:01 |
|   2
|  
UNION-ALL         
|     
|      
|      
|           
|         
|
|   3
|    HASH GROUP
BY    
|     
|    14
|   448
|    
5  (40)| 00:00:01 |
|   4
|    
TABLE ACCESS FULL| EMP 
|    14
|   448
|    
3   (0)| 00:00:01 |
|   5
|    HASH GROUP
BY    
|     
|    14
|   364
|    
5  (40)| 00:00:01 |
|   6
|    
TABLE ACCESS FULL| EMP 
|    14
|   364
|    
3   (0)| 00:00:01 |
|   7
|    SORT
AGGREGATE   
|     
|    
1 |    13
|    
4  (25)| 00:00:01 |
|   8
|    
TABLE ACCESS FULL| EMP 
|    14
|   182
|    
3   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Note
-----
   - dynamic sampling used for
this statement

[email protected]> select deptno,job,sum(sal) tsal from emp
group by rollup(deptno,job);
Execution Plan
----------------------------------------------------------
Plan hash value: 52302870
-----------------------------------------------------------------------------

| Id  |
Operation           
| Name | Rows  | Bytes | Cost (%CPU)|
Time    
|
-----------------------------------------------------------------------------

|   0 | SELECT
STATEMENT    
|     
|    14
|   448
|    
4  (25)| 00:00:01 |
|   1 |  SORT
GROUP BY
ROLLUP|     
|    14
|   448
|    
4  (25)| 00:00:01 |
|   2
|   TABLE ACCESS
FULL  | EMP 
|    14
|   448
|    
3   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Note
-----
   - dynamic sampling used for
this statement

通过比较发现,两个语句的成本cost会差出很多14vs4。所以,如果我们以后有上面类似的需求的话,可以考虑使用rollup。

注:rollup语法
    select
a,b,组函数
    from 表
    group by
rollup(a,b);

这个语法相当于 group by a,b union group a union group by
null的sql语句的组合

时间: 2024-10-11 04:23:58

Oracle SQL篇(四)group by 分组与分组的加强 rollup的相关文章

Oracle SQL篇(一)null值之初体验

    从我第一次正式的写sql语句到现在,已经超过10年的时间了.我写报表,做统计分析和财务对账,我一点点的接触oracle数据库,并尝试深入了解.这条路,一走就是10年,从充满热情,到开始厌倦,我不知道我还能坚持多久,未来的路,谁知道呢? 也许是该抓紧时间,做一点什么了,我不知道该开始写些什么,我从来没有在网上写东西的习惯.     先从简单的开始吧,那当然就是SQL,这是我SQL系列的第一篇,希望我能够坚持. 在Oracle数据库中,如果一个表中的列没有值的话,我们可以说是空值,比如IT员

Oracle SQL(四)

5. SQL高级--表优化相关(序列.视图等) 5.1 INDEX(索引) 可以在表中创建索引,以便更加快速高效地查询数据. 用户无法看到索引,它们只能被用来加速搜索/查询. PS:更新一个包含索引的表需要比更新一个没有索引的表花费更多的时间,这是由于索引本身也需要更新.因此,理想的做法是仅仅在常常被搜索的列(以及表)上面创建索引. 5.2 索引新增 --在表上创建一个简单的索引.允许使用重复的值 CREATE INDEX 索引名 ON 表 (列) --在表上创建一个唯一的索引.不允许使用重复的

Oracle SQL篇(二)oracle自连接操作

    oracle 的自连接(self join)操作 对于oracle数据库来说,目前支持两套语法,一套是oracle自己的sql语法,一套是通行标准的SQL99语法,那么对于oracle的连接操作 来说,也完全可以使用这样的两套语法来分别的实现.当然从效率上来说,两者是没有差别的.只不过从我的角度来讲,oracle的语法更加简洁而已. 比如说我们有一张表emp,表里数据如下 [email protected]> conn scott/tiger Connected. [email prot

oracle学习篇五:组函数,分组统计

常用组函数: 1.ccount() 求出全部记录数. 2.max() 求出一组最大值 3.min() 求出一组最小值 4.avg() 求出平均值 5.sum() 求和 --1.统计员工数量: select count(empno) from emp; --2.求出最高薪资 select max(sal) from emp; --3.求出最低薪资 select min(sal) from emp; --4.求出平均薪资 select avg(sal) from emp; --查询各部门员工数量 s

oracle学习篇四:多表查询

-----------------产生笛卡儿积------------------------------------ select * from emp,dept; --不带条件时,记录数为14*4 =56条记录 select * from emp cross join dept;--交叉连接,同上 select count(*)from emp; ---左连接( 返回包括左表中的所有记录和右表中联结字段相等的记录 ) select d.dname,e.ename from emp e,dep

oracle sql 高级编程 历史笔记整理

20130909 周一 oracle sql 开发指南 第7章 高级查询 1.层次化查询select level,ttt.*,sys_connect_by_path(ttt.col1,',') from ttt start with .. Connect by prior -因为先建立树,再进行where过滤的.在where中过滤和在cooonect by中过滤是不一样的. 2.rollup cube高级查询 select grouping(col1) .. From ttt group by

【基于WPF+OneNote+Oracle的中文图片识别系统阶段总结】之篇四:关于OneNote入库处理以及审核

篇一:WPF常用知识以及本项目设计总结:http://www.cnblogs.com/baiboy/p/wpf.html 篇二:基于OneNote难点突破和批量识别:http://www.cnblogs.com/baiboy/p/wpf1.html 篇三:批量处理后的txt文件入库处理:http://www.cnblogs.com/baiboy/p/wpf2.html 篇四:关于OneNote入库处理以及审核:http://www.cnblogs.com/baiboy/p/wpf3.html [

Oracle SQL 经典查询练手第三篇

Oracle SQL 经典查询练手第三篇 本文分享的是Oracle SQL的经典查询第三篇,仅仅是作者自己的见解,如有问题,希望您给出建议或者方法.同时,欢迎广大读者们补充,如果您有经典的查询方式也可以拿出来我们共同分享,共同成长,共同进步. 本计算机上使用的是Oracle 11.2.0版本,使用scott用户登陆.使用的是系统自带的表. 表结构: describe employees; describe departments; describe locations; select *from

oracle sql 基础(二):select 语句

为了从数据库中查询数据,你需要用SQL语言中使用最多的SELECT语句.我们分别介绍SELECT语句的基础语法.子查询.从多表中查询数据,然后再进行实例解析. 一.SELECT语句的基础语法 SELECT语句就像叠加在数据库表上的过滤器,即选择查询用于定位数据库特定的列和行.下面是SELECT语句的基础语法.  SELECT [ALL|DISTINCT SELECT_LIST FROM {table_name|view_name} [WHERE search_condition] [GROUP