SQL优化一(SQL使用技巧)

一个学习性任务:每个人有不同次数的成绩,统计出每个人的最高成绩。

一、Oracle分析函数入门

 

分析函数是什么?
分析函数是Oracle专门用于解决复杂报表统计需求的功能强大的函数,它可以在数据中进行分组然后计算基于组的某种统计值,并且每一组的每一行都可以返回一个统计值。

          

分析函数和聚合函数的不同之处是什么?
普通的聚合函数用group
by分组,每个分组返回一个统计值,而分析函数采用partition
by分组,并且每组每行都可以返回一个统计值。

              

分析函数的形式
分析函数带有一个开窗函数over(),包含三个分析子句:分组(partition by),
排序(order by), 窗口(rows) ,
他们的使用形式如下:over(partition by xxx
order by yyy rows between zzz)。
注:窗口子句在这里我只说rows方式的窗口,range方式和滑动窗口也不提

    

分析函数例子(在scott用户下模拟)

示例目的:显示各部门员工的工资,并附带显示该部分的最高工资。

永利酒店赌场 1

--显示各部门员工的工资,并附带显示该部分的最高工资。
SELECT E.DEPTNO,
       E.EMPNO,
       E.ENAME,
       E.SAL,
       LAST_VALUE(E.SAL) 
       OVER(PARTITION BY E.DEPTNO 
            ORDER BY E.SAL ROWS 
            --unbounded preceding and unbouned following针对当前所有记录的前一条、后一条记录,也就是表中的所有记录
            --unbounded:不受控制的,无限的
            --preceding:在...之前
            --following:在...之后
            BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) MAX_SAL
  FROM EMP E;

永利酒店赌场 2

运行结果:

永利酒店赌场 3

               

示例目的:按照deptno分组,然后计算每组值的总和

SELECT EMPNO,
       ENAME,
       DEPTNO,
       SAL,
       SUM(SAL) OVER(PARTITION BY DEPTNO ORDER BY ENAME) max_sal
  FROM SCOTT.EMP;

运行结果:

永利酒店赌场 4

     

示例目的:对各部门进行分组,并附带显示第一行至当前行的汇总

永利酒店赌场 5

SELECT EMPNO,
       ENAME,
       DEPTNO,
       SAL,
       --注意ROWS BETWEEN unbounded preceding AND current row  是指第一行至当前行的汇总
       SUM(SAL) OVER(PARTITION BY DEPTNO 
                     ORDER BY ENAME 
                     ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) max_sal
  FROM SCOTT.EMP;

永利酒店赌场 6

运行结果:

永利酒店赌场 7

永利酒店赌场,   

示例目标:当前行至最后一行的汇总

永利酒店赌场 8

SELECT EMPNO,
       ENAME,
       DEPTNO,
       SAL,
       --注意ROWS BETWEEN current row AND unbounded following 指当前行到最后一行的汇总
       SUM(SAL) OVER(PARTITION BY DEPTNO 
                     ORDER BY ENAME 
                     ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) max_sal
  FROM SCOTT.EMP;

永利酒店赌场 9

运行结果:

永利酒店赌场 10

   

 示例目标:当前行的上一行(rownum-1)到当前行的汇总

永利酒店赌场 11

SELECT EMPNO,
       ENAME,
       DEPTNO,
       SAL,
       --注意ROWS BETWEEN 1 preceding AND current row 是指当前行的上一行(rownum-1)到当前行的汇总 
       SUM(SAL) OVER(PARTITION BY DEPTNO 
                     ORDER BY ENAME ROWS 
                     BETWEEN 1 PRECEDING AND CURRENT ROW) max_sal
  FROM SCOTT.EMP;

永利酒店赌场 12

运行结果:

永利酒店赌场 13

    

示例目标:   当前行的上一行(rownum-1)到当前行的下辆行(rownum+2)的汇总     

永利酒店赌场 14

SELECT EMPNO,
       ENAME,
       DEPTNO,
       SAL,
       --注意ROWS BETWEEN 1 preceding AND 1 following 是指当前行的上一行(rownum-1)到当前行的下辆行(rownum+2)的汇总
       SUM(SAL) OVER(PARTITION BY DEPTNO 
                     ORDER BY ENAME 
                     ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING) max_sal
  FROM SCOTT.EMP;

永利酒店赌场 15

运行结果:

永利酒店赌场 16

      

 

 

1、行列转换:

这个问题应该还是相对简单,其实就用聚合函数就好了。

二、理解over()函数

1.1、两个order by的执行时机
分析函数(以及与其配合的开窗函数over())是在整个sql查询结束后(sql语句中的order
by的执行比较特殊)再进行的操作, 也就是说sql语句中的order
by也会影响分析函数的执行结果:

a) 两者一致:如果sql语句中的order
by满足与分析函数配合的开窗函数over()分析时要求的排序,即sql语句中的order
by子句里的内容和开窗函数over()中的order by子句里的内容一样,

那么sql语句中的排序将先执行,分析函数在分析时就不必再排序;
b) 两者不一致:如果sql语句中的order
by不满足与分析函数配合的开窗函数over()分析时要求的排序,即sql语句中的order
by子句里的内容和开窗函数over()中的order by子句里的内容不一样,

那么sql语句中的排序将最后在分析函数分析结束后执行排序。

           

1.2、开窗函数over()分析函数中的分组/排序/窗口
      开窗函数over()分析函数包含三个分析子句:分组子句(partition
by), 排序子句(order by), 窗口子句(rows)
      窗口就是分析函数分析时要处理的数据范围,就拿sum来说,它是sum窗口中的记录而不是整个分组中的记录,因此我们在想得到某个栏位的累计值时,我们需要把窗口指定到该分组中的第一行数据到当前行,
如果你指定该窗口从该分组中的第一行到最后一行,那么该组中的每一个sum值都会一样,即整个组的总和。

      窗口子句在这里我只说rows方式的窗口,range方式和滑动窗口也不提。

 

     
窗口子句中我们经常用到指定第一行,当前行,最后一行这样的三个属性:
第一行是 unbounded preceding,
当前行是 current row,
最后一行是 unbounded following,

注释:

开窗函数over()出现分组(partition by)子句时,

unbounded
preceding即第一行是指表中一个分组里的第一行, unbounded
following即最后一行是指表中一个分组里的最后一行;

开窗函数over()省略了分组(partition by)子句时, 

unbounded
preceding即第一行是指表中的第一行, unbounded
following即最后一行是指表中的最后一行。

 

窗口子句不能单独出现,必须有order by子句时才能出现

例如:

last_value(sal) over(partition by deptno 
                     order by sal 
                     rows between unbounded preceding and unbounded following)

以上示例指定窗口为整个分组。而出现order
by子句的时候,不一定要有窗口子句,但效果会很不一样,此时的窗口默认是当前组的第一行到当前行!

 

如果省略分组,则把全部记录当成一个组。
a) 如果存在order by则默认窗口是unbounded preceding and current
row   –当前组的第一行到当前行
b) 如果这时省略order by则窗口默认为unbounded preceding and unbounded
following  –整个组
 

 

而无论是否省略分组子句,如下结论都是成立的:

1、窗口子句不能单独出现,必须有order by子句时才能出现

2、当省略窗口子句时:
a) 如果存在order by则默认的窗口是unbounded preceding and current
row  –当前组的第一行到当前行,即在当前组中,第一行到当前行
b) 如果同时省略order by则默认的窗口是unbounded preceding and unbounded
following  –整个组

              
所以,

 

lag(sal) over(order by sal) 解释

over(order by salary)表示意义如下:

首先,我们要知道由于省略分组子句,所以当前组的范围为整个表的数据行,

然后,在当前组(此时为整个表的数据行)这个范围里执行排序(即order by
salary),

最后,我们知道分析函数lag(sal)在当前组(此时为整个表的数据行)这个范围里的窗口范围为当前组的第一行到当前行,即分析函数lag(sal)在这个窗口范围执行。

 

参见:

 

  decode(条件,值1,返回值1,值2,返回值2,…值n,返回值n,缺省值);

select id,name,max(score) from Student group by id,name order by name

Oracle的LAG和LEAD分析函数

 

 

 

 

上边这种情况只适用id 和name是一一对应的,否则查询出来的数据是不正确的。

Oracle分析函数ROW_NUMBER()|RANK()|LAG()使用详解

 

1.3、帮助理解over()的实例

例1:关注点:sql无排序,over()排序子句省略

SELECT DEPTNO, EMPNO, ENAME, SAL, 
       LAST_VALUE(SAL) OVER(PARTITION BY DEPTNO)
FROM EMP;

运行结果:

 

永利酒店赌场 17

        

例2:关注点:sql无排序,over()排序子句有,窗口省略

 

永利酒店赌场 18

SELECT DEPTNO,
       EMPNO,
       ENAME,
       SAL,
       LAST_VALUE(SAL) OVER(PARTITION BY DEPTNO 
                            ORDER BY SAL DESC)
  FROM EMP;

永利酒店赌场 19

运行结果:

 

永利酒店赌场 20

                   
例3:关注点:sql无排序,over()排序子句有,窗口也有,窗口特意强调全组数据

 

永利酒店赌场 21

SELECT DEPTNO,
       EMPNO,
       ENAME,
       SAL,
       LAST_VALUE(SAL) 
       OVER(PARTITION BY DEPTNO 
            ORDER BY SAL 
            ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) MAX_SAL
  FROM EMP;

永利酒店赌场 22

运行结果:

 

永利酒店赌场 23

      
例4:关注点:sql有排序(正序),over()排序子句无,先做sql排序再进行分析函数运算

 

永利酒店赌场 24

SELECT DEPTNO,
       MGR,
       ENAME,
       SAL,
       HIREDATE,
       LAST_VALUE(SAL) OVER(PARTITION BY DEPTNO) LAST_VALUE
  FROM EMP
 WHERE DEPTNO = 30
 ORDER BY DEPTNO, MGR;

永利酒店赌场 25

运行结果:

 

永利酒店赌场 26

 

例5:关注点:sql有排序(倒序),over()排序子句无,先做sql排序再进行分析函数运算

 

永利酒店赌场 27

SELECT DEPTNO,
       MGR,
       ENAME,
       SAL,
       HIREDATE,
       LAST_VALUE(SAL) OVER(PARTITION BY DEPTNO) LAST_VALUE
  FROM EMP
 WHERE DEPTNO = 30
 ORDER BY DEPTNO, MGR DESC;

永利酒店赌场 28

运行结果:

永利酒店赌场 29

                 

例6:关注点:sql有排序(倒序),over()排序子句有,窗口子句无,此时的运算是:sql先选数据但是不排序,而后排序子句先排序并进行分析函数处理(窗口默认为第一行到当前行),最后再进行sql排序

 

 

永利酒店赌场 30

SELECT DEPTNO,
       MGR,
       ENAME,
       SAL,
       HIREDATE,
       MIN(SAL) OVER(PARTITION BY DEPTNO ORDER BY SAL ASC) LAST_VALUE
  FROM EMP
 WHERE DEPTNO = 30
 ORDER BY DEPTNO, MGR DESC;

永利酒店赌场 31

运行结果:

永利酒店赌场 32

 

永利酒店赌场 33

SELECT DEPTNO,
       MGR,
       ENAME,
       SAL,
       HIREDATE,
       MIN(SAL) OVER(PARTITION BY DEPTNO ORDER BY SAL DESC) LAST_VALUE
  FROM EMP
 WHERE DEPTNO = 30
 ORDER BY DEPTNO, MGR DESC;

永利酒店赌场 34

运行结果:

永利酒店赌场 35

              

 

  select decode(sign(变量1-变量2),-1,变量1,变量2) from dual; –取较小值

例如 : 1 张三 100

三、常见分析函数详解

为了方便进行实践,特将演示表和数据罗列如下:

一、创建表

create table t( 
   bill_month varchar2(12) , 
   area_code number, 
   net_type varchar(2), 
   local_fare number 
);

      

二、插入数据

永利酒店赌场 36

insert into t values('200405',5761,'G', 7393344.04); 
insert into t values('200405',5761,'J', 5667089.85); 
insert into t values('200405',5762,'G', 6315075.96); 
insert into t values('200405',5762,'J', 6328716.15); 
insert into t values('200405',5763,'G', 8861742.59); 
insert into t values('200405',5763,'J', 7788036.32); 
insert into t values('200405',5764,'G', 6028670.45); 
insert into t values('200405',5764,'J', 6459121.49); 
insert into t values('200405',5765,'G', 13156065.77); 
insert into t values('200405',5765,'J', 11901671.70); 
insert into t values('200406',5761,'G', 7614587.96); 
insert into t values('200406',5761,'J', 5704343.05); 
insert into t values('200406',5762,'G', 6556992.60); 
insert into t values('200406',5762,'J', 6238068.05); 
insert into t values('200406',5763,'G', 9130055.46); 
insert into t values('200406',5763,'J', 7990460.25); 
insert into t values('200406',5764,'G', 6387706.01); 
insert into t values('200406',5764,'J', 6907481.66); 
insert into t values('200406',5765,'G', 13562968.81); 
insert into t values('200406',5765,'J', 12495492.50); 
insert into t values('200407',5761,'G', 7987050.65); 
insert into t values('200407',5761,'J', 5723215.28); 
insert into t values('200407',5762,'G', 6833096.68); 
insert into t values('200407',5762,'J', 6391201.44); 
insert into t values('200407',5763,'G', 9410815.91); 
insert into t values('200407',5763,'J', 8076677.41); 
insert into t values('200407',5764,'G', 6456433.23); 
insert into t values('200407',5764,'J', 6987660.53); 
insert into t values('200407',5765,'G', 14000101.20); 
insert into t values('200407',5765,'J', 12301780.20); 
insert into t values('200408',5761,'G', 8085170.84); 
insert into t values('200408',5761,'J', 6050611.37); 
insert into t values('200408',5762,'G', 6854584.22); 
insert into t values('200408',5762,'J', 6521884.50); 
insert into t values('200408',5763,'G', 9468707.65); 
insert into t values('200408',5763,'J', 8460049.43); 
insert into t values('200408',5764,'G', 6587559.23); 
insert into t values('200408',5764,'J', 7342135.86); 
insert into t values('200408',5765,'G', 14450586.63); 
insert into t values('200408',5765,'J', 12680052.38); 
commit;

永利酒店赌场 37

            

三、first_value()与last_value():求最值对应的其他属性
问题、取出每月通话费最高和最低的两个地区。

永利酒店赌场 38

SELECT BILL_MONTH, 
       AREA_CODE,
       SUM(LOCAL_FARE) LOCAL_FARE, 
       FIRST_VALUE(AREA_CODE) 
       OVER(PARTITION BY BILL_MONTH 
            ORDER BY SUM(LOCAL_FARE) DESC 
            ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FIRSTVAL, 
       LAST_VALUE(AREA_CODE) 
       OVER(PARTITION BY BILL_MONTH 
            ORDER BY SUM(LOCAL_FARE) DESC 
            ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) LASTVAL 
  FROM T 
 GROUP BY BILL_MONTH, AREA_CODE 
 ORDER BY BILL_MONTH

永利酒店赌场 39

运行结果:

永利酒店赌场 40

   

四、rank(),dense_rank()与row_number():求排序

rank,dense_rank,row_number函数为每条记录产生一个从1开始至n的自然数,n的值可能小于等于记录的总数。这3个函数的唯一区别在于当碰到相同数据时的排名策略。
①row_number: 
row_number函数返回一个唯一的值,当碰到相同数据时,排名按照记录集中记录的顺序依次递增。
②dense_rank: 
dense_rank函数返回一个唯一的值,当碰到相同数据时,此时所有相同数据的排名都是一样的。
③rank: 
rank函数返回一个唯一的值,当碰到相同的数据时,此时所有相同数据的排名是一样的,同时会在最后一条相同记录和下一条不同记录的排名之间空出排名。

          

演示数据在Oracle自带的scott用户下:
1、rank()值相同时排名相同,其后排名跳跃不连续

永利酒店赌场 41

SELECT * 
  FROM (SELECT DEPTNO, 
               RANK() OVER(PARTITION BY DEPTNO ORDER BY SAL DESC) RW, 
               ENAME,
               SAL
          FROM SCOTT.EMP) 
 WHERE RW <= 4;

永利酒店赌场 42

运行结果:

永利酒店赌场 43
2、dense_rank()值相同时排名相同,其后排名连续不跳跃

永利酒店赌场 44

SELECT * 
  FROM (SELECT DEPTNO, 
               DENSE_RANK() OVER(PARTITION BY DEPTNO ORDER BY SAL DESC) RW, 
               ENAME,
               SAL
          FROM SCOTT.EMP) 
 WHERE RW <= 4;

永利酒店赌场 45

运行结果:

永利酒店赌场 46
3、row_number()值相同时排名不相等,其后排名连续不跳跃

永利酒店赌场 47

SELECT * 
  FROM (SELECT DEPTNO, 
               ROW_NUMBER() OVER(PARTITION BY DEPTNO ORDER BY SAL DESC) RW, 
               ENAME,
               SAL
          FROM SCOTT.EMP) 
 WHERE RW <= 4;

永利酒店赌场 48

运行结果:

永利酒店赌场 49

 

五、lag()与lead():求之前或之后的第N行 
lag和lead函数可以在一次查询中取出同一字段的前n行的数据和后n行的值。这种操作可以使用对相同表的表连接来实现,不过使用lag和lead有更高的效率。
lag(arg1,arg2,arg3)
第一个参数是列名,
第二个参数是偏移的offset,
第三个参数是超出记录窗口时的默认值。
   
举例如下:
SQL> select *  from kkk;                                          
                                                                  
        ID NAME                                                   
———- ——————–                                   
         1 1name                                                  
         2 2name                                                  
         3 3name                                                  
         4 4name                                                  
         5 5name                                                  
SQL> select id,name,lag(name,1,0) over(order by id) from kkk; 
                                                                  
        ID NAME                 LAG(NAME,1,0)OVER(ORDERBYID)      
———- ——————– —————————-      
         1 1name                0                                 
         2 2name                1name                             
         3 3name                2name                             
         4 4name                3name                             
         5 5name                4name

SQL> select id,name,lead(name,1,0) over(order by id) from kkk;
                                                                  
        ID NAME                 LEAD(NAME,1,0)OVER(ORDERBYID)     
———- ——————– —————————–     
         1 1name                2name                             
         2 2name                3name                             
         3 3name                4name                             
         4 4name                5name                             
         5 5name                0

SQL> select id,name,lead(name,2,0) over(order by id) from
kkk;                                                                                                              
        ID NAME                 LEAD(NAME,2,0)OVER(ORDERBYID)     
———- ——————– —————————–     
         1 1name                3name                             
         2 2name                4name                             
         3 3name                5name                             
         4 4name                0                                 
         5 5name                0  
SQL> select id,name,lead(name,1,’linjiqin’) over(order by id) from
kkk;                                 
                                                                                 
        ID NAME                
LEAD(NAME,1,’ALSDFJLASDJFSAF’)                   


——————————                   
         1 1name               
2name                                            
         2 2name               
3name                                            
         3 3name               
4name                                            
         4 4name               
5name                                            
         5 5name                linjiqin  


   

六、rollup()与cube():排列组合分组 
1)、group by rollup(a, b, c):
首先会对(a、b、c)进行group by,
然后再对(a、b)进行group by,
其后再对(a)进行group by,
最后对全表进行汇总操作。

     

2)、group by cube(a, b, c):
则首先会对(a、b、c)进行group by,
然后依次是(a、b),(a、c),(a),(b、c),(b),(c),
最后对全表进行汇总操作。

   

1、生成演示数据:
Connected to Oracle Database 10g Enterprise Edition Release
10.2.0.1.0 
Connected as ds_trade
 
SQL> conn system/oracle as sysdba
Connected to Oracle Database 10g Enterprise Edition Release
10.2.0.3.0 
Connected as SYS
 
SQL> create table scott.t as select * from dba_indexes;
 
Table created
 
 
SQL> connect scott/oracle
Connected to Oracle Database 10g Enterprise Edition Release
10.2.0.3.0 
Connected as scott
 
SQL>

    

2、普通group by体验
sql> select owner, index_type, status, count(*) from t where owner
like ‘SY%’ group by owner, index_type, status;

永利酒店赌场 50

3、group by rollup(A,B,C)
GROUP BY ROLLUP(A, B, C):
首先会对(A、B、C)进行GROUP BY,
然后再对(A、B)进行GROUP BY,
其后再对(A)进行GROUP BY,
最后对全表进行汇总操作。
sql> select owner, index_type, status, count(*) from t where owner
like ‘SY%’ group by ROLLUP(owner, index_type, status);

永利酒店赌场 51

4、group by cube(A,B,C)
GROUP BY CUBE(A, B, C):
则首先会对(A、B、C)进行GROUP BY,
然后依次是(A、B),(A、C),(A),(B、C),(B),(C),
最后对全表进行汇总操作。

sql> select owner, index_type, status, count(*) from t where owner
like ‘SY%’ group by cube(owner, index_type, status);

永利酒店赌场 52

  

七、max(),min(),sun()与avg():求移动的最值总和与平均值
问题:计算出各个地区连续3个月的通话费用的平均数(移动平均值)

 

永利酒店赌场 53

SELECT AREA_CODE, 
       BILL_MONTH,
       LOCAL_FARE,
       SUM(LOCAL_FARE) OVER(PARTITION BY AREA_CODE 
                            ORDER BY TO_NUMBER(BILL_MONTH) 
                            RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) "3month_sum", 
       AVG(LOCAL_FARE) OVER(PARTITION BY AREA_CODE 
                            ORDER BY TO_NUMBER(BILL_MONTH) 
                            RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) "3month_avg", 
       MAX(LOCAL_FARE) OVER(PARTITION BY AREA_CODE 
                            ORDER BY TO_NUMBER(BILL_MONTH) 
                            RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) "3month_max", 
       MIN(LOCAL_FARE) OVER(PARTITION BY AREA_CODE 
                            ORDER BY TO_NUMBER(BILL_MONTH) 
                            RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) "3month_min" 
  FROM (SELECT T.AREA_CODE, T.BILL_MONTH, SUM(T.LOCAL_FARE) LOCAL_FARE 
          FROM T 
         GROUP BY T.AREA_CODE, T.BILL_MONTH)

永利酒店赌场 54

运行结果:

永利酒店赌场 55

  

问题:求各地区按月份累加的通话费

永利酒店赌场 56

SELECT AREA_CODE, 
       BILL_MONTH,
       LOCAL_FARE,
       SUM(LOCAL_FARE) OVER(PARTITION BY AREA_CODE 
                            ORDER BY BILL_MONTH ASC) "last_sum_value" 
  FROM (SELECT T.AREA_CODE, T.BILL_MONTH, SUM(T.LOCAL_FARE) LOCAL_FARE 
          FROM T 
         GROUP BY T.AREA_CODE, T.BILL_MONTH) 
 ORDER BY AREA_CODE, BILL_MONTH

永利酒店赌场 57

运行结果:

永利酒店赌场 58

 


Blog:
J2EE、Android、Linux、Oracle QQ交流群:142463980、158560018(满)

另见:《Oracle分析函数ROW_NUMBER()|RANK()|LAG()使用详解》

 

           2 张三 90

  sign()函数根据某个值是0、正数还是负数,分别返回0、1、-1

          查询出来的结果

  例如:

          两条信息都会输出。

  变量1=10,变量2=20

避免这种情况,可以使用开窗函数。

  则sign(变量1-变量2)返回-1,decode解码结果为“变量1”,达到了取较小值的目的。

个人理解就是,开窗函数和聚合函数功能是相反的。

 

聚合函数,将多行数据合并成一行数据;而开窗函数则是将一行数据拆分成多行。

举例:查询emp表中的每个部门的人数?

开窗函数可以满足上述问题,同事也可以满足其他问题。例如:求每个班最高成绩学生的信息。

SELECT sum(decode(deptno,10,1,0)) as 部门10,
            sum(decode(deptno,20,1,0)) as 部门20,
            sum(decode(deptno,30,1,0)) as 部门30 from emp

分析:每个人学号一定是不同的,名字可能有重名,最大复杂的情况是,每个班最高成绩可能不止一个。

永利酒店赌场 59

        如果继续使用开始的方式,那么是不能满足要求的。

      

        使用开窗函数就能很好的解决这个问题。

      做个对比:select deptno,count(deptno) from emp group by deptno

–每个班级的成绩第一的学生
–学生表中信息如下
a 1 80
b 1 78
c 1 95
d 2 74
e 2 92
f 3 99
g 3 99
h 3 45
i 3 55
j 3 78

      永利酒店赌场 60

查询结果如下:
c 1 95 1
e 2 92 1
f 3 99 1
g 3 99 1

 

SQL查询语句如下:
select *
from
(
select name,class,s,rank()over(partition by class order by s desc) mm
from t2

2、递归查询的优化

) as t
where t.mm=1

   lpad/rpad( string, padded_length, [ pad_string ] )

 

   解释:字符不够的时候向左或者向右填充。

心得:
rank()跳跃排序,有两个第二名时后边跟着的是第四名
dense_rank() 连续排序,有两个第二名时仍然跟着第三名

   例如:SQL> select lpad(‘abcde’,10,’x’) from dual;

over()开窗函数: 在使用聚合函数后,会将多行变成一行,
而开窗函数是将一行变成多行;
并且在使用聚合函数后,如果要显示其他的列必须将列加入到group by中,
而使用开窗函数后,可以不使用group by,直接将所有信息显示出来。

      LPAD(‘ABCDE’,10,’X’)

开窗函数适用于在每一行的最后一列添加聚合函数的结果。

      xxxxxabcde

常用开窗函数:
1.为每条数据显示聚合信息.(聚合函数() over())
2.为每条数据提供分组的聚合函数结果(聚合函数() over(partition by 字段) as
别名) –按照字段分组,分组后进行计算
3.与排名函数一起使用(row number() over(order by 字段) as 别名)

 

常用分析函数:(最常用的应该是1.2.3 的排序)
1、row_number() over(partition by … order by …)
2、rank() over(partition by … order by …)
3、dense_rank() over(partition by … order by …)
4、count() over(partition by … order by …)
5、max() over(partition by … order by …)
6、min() over(partition by … order by …)
7、sum() over(partition by … order by …)
8、avg() over(partition by … order by …)
9、first_value() over(partition by … order by …)
10、last_value() over(partition by … order by …)
11、lag() over(partition by … order by …)
12、lead() over(partition by … order by …)
lag 和lead 可以
获取结果集中,按一定排序所排列的当前行的上下相邻若干offset
的某个行的某个列(不用结果集的自关联);
lag ,lead 分别是向前,向后;
lag 和lead
有三个参数,第一个参数是列名,第二个参数是偏移的offset,第三个参数是
超出记录窗口时的默认值)

  1. SELECT ID, FATHER_ID, NAME, CONNECT_BY_ISLEAF LEAF   
  2. FROM T_TREE   
  3. START WITH FATHER_ID = 0   
  4. CONNECT BY PRIOR ID = FATHER_ID;  

 

解释:CONNECT_BY_ISLEAF
判断该行记录是否为叶子节点,如果是返回1,否则返回0

          START WITH FATHER_ID = 0 规定哪一条记录为根节点

          CONNECT BY PRIOR ID = FATHER_ID
判断此节点的父节点是哪一条记录

 

例子:

 SELECT LPAD(MENU_NAME,

            LENGTHB(MENU_NAME) + LEVEL,

            DECODE(CONNECT_BY_ISLEAF, 1, ‘ | ‘, ‘+’))

  FROM MENU

 START WITH PID = ‘0’

 CONNECT BY PRIOR ID = PID;

 

3、利用分析函数排序和去重

 

分析函数是什么?
分析函数是Oracle专门用于解决复杂报表统计需求的功能强大的函数,它可以在数据中进行分组然后计算基于组的某种统计值,并且每一组的每一行都可以返回一个统计值。

          

分析函数和聚合函数的不同之处是什么?
普通的聚合函数用group
by分组,每个分组返回一个统计值,返回的字段名只能是分组名。而分析函数采用partition
by分组,并且每组每行都可以返回一个统计值,返回的字段名可以是每个字段,因为是对应到记录的,所以没有关系。

              

分析函数的形式
分析函数带有一个开窗函数over(),包含三个分析子句:分组(partition by),
排序(order by), 窗口(rows) ,
他们的使用形式如下:over(partition by xxx
order by yyy rows between zzz)

 

 开窗函数over()包含三个分析子句:分组子句(partition by),
排序子句(order by), 窗口子句(rows)
      窗口就是分析函数分析时要处理的数据范围,就拿sum来说,它是sum窗口中的记录而不是整个分组中的记录,因此我们在想得到某个栏位的累计值时,我们需要把窗口指定到该分组中的第一行数据到当前行,
如果你指定该窗口从该分组中的第一行到最后一行,那么该组中的每一个sum值都会一样,即整个组的总和。

     

OVER(PARTITION BY DEPTNO ORDER BY ENAME ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW):当前组第一行到当前行的汇总

OVER(PARTITION BY DEPTNO ORDER BY ENAME ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING):当前行到最后一行的汇总

OVER(PARTITION BY DEPTNO ORDER BY ENAME ROWS BETWEEN 1 PRECEDING AND CURRENT ROW):当前行的上一行(rownum-1)到当前行的汇总

OVER(PARTITION BY DEPTNO ORDER BY ENAME ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING):当前行的上一行(rownum-1)到下两行(rownum+2)的汇总

 

而无论是否省略分组子句,如下结论都是成立的:

 

 1、窗口子句不能单独出现,必须有order by子句时才能出现。

 2、当省略窗口子句时:
    a) 如果存在order by则默认的窗口是unbounded preceding and current
row  –当前组的第一行到当前行,即在当前组中,第一行到当前行,这里强调一下,如果partition
by字段和order by 字段一样的话,这个order by不生效,相当于省略了order by

    b) 如果同时省略order by则默认的窗口是unbounded preceding and
unbounded following  –整个组

 

两个order by的执行时机

分析函数(以及与其配合的开窗函数over())是在整个sql查询结束后(sql语句中的order
by的执行比较特殊)再进行的操作, 也就是说sql语句中的order
by也会影响分析函数的执行结果:

 

    a) 两者一致:如果sql语句中的order
by满足与分析函数配合的开窗函数over()分析时要求的排序,即sql语句中的order
by子句里的内容和开窗函数over()中的order
by子句里的内容一样,那么sql语句中的排序将先执行,分析函数在分析时就不必再排序;
    b) 两者不一致:如果sql语句中的order
by不满足与分析函数配合的开窗函数over()分析时要求的排序,即sql语句中的order
by子句里的内容和开窗函数over()中的order
by子句里的内容不一样,那么sql语句中的排序将最后在分析函数分析结束后执行排序。

 

 

常用的分析函数:

  1、row_number() over(partition by … order by …) 
为每一条记录返回一个唯一的值。当碰到相同数据时,排名按照记录集中记录的顺序依次递增,现实情景为:个人在分组内的排名
  2、rank() over(partition by … order by …) 
得到每条记录在数据中的排名,排名不跳跃
  3、dense_rank() over(partition by … order by …)
得到每条记录在数据中的排名,排名跳跃
  4、count() over(partition by … order by …)
每个分组中,某个字段的统计
  5、max() over(partition by … order by …)
  6、min() over(partition by … order by …)
  7、sum() over(partition by … order by …)
  8、avg() over(partition by … order by …)
  9、first_value() over(partition by … order by …)
得到第一个记录值
  10、last_value() over(partition by … order by …)
得到最后一个记录值
  11、lag() over(partition by … order by …)
lag函数可以在一次查询中取出同一字段的前n行的数据

  12、lead() over(partition by … order by …)
lead函数可以在一次查询中取出同一字段的后n行的值

 

 

lag(arg1,arg2,arg3)
第一个参数是列名,
第二个参数是偏移的offset,
第三个参数是超出记录窗口时的默认值。

select id,name,lag(name,1,0) over(order by id) from kkk; 

 

 例子:select ename,deptno,sal,sum(sal) over(partition by deptno order
by ename) as A from emp

永利酒店赌场 61

 

去重:

  1、利用rowid的唯一性查询或删除重复数据

  
select ROWNUM,ROWID,d1.* from dept2 d1 where d1.rowid=(select min(d2.rowid) from dept2 d2 where d2.deptno=d1.deptno);

  2、给定重复行序号并去重

SELECT FWZL

    FROM (SELECT FWZL,

                 ROW_NUMBER() OVER(PARTITION BY FWZL ORDER BY ID DESC)
RN

            FROM T_FWXX) F

   WHERE F.RN = 1

 

 4、求占比、小计和总计

      分析函数RATIO_TO_REPORT
用来计算当前记录的指标expr占开窗函数over中包含记录的所有同一指标的百分比.
这里如果开窗函数的统计结果为null或者为0,就是说占用比率的被除数为0或者为null,
则得到的结果也为0. 开窗条件query_partition_clause决定被除数的值,
如果用户忽略了这个条件, 则计算查询结果中所有记录的汇总值.
用户不能使用其他分析函数或者ratio_to_report作为分析函数ratio_to_report的参数expr,
也就是说这个函数

 百分比(求这个字段值占整组的百分比):select deptno,ename,empno,round(RATIO_TO_REPORT(sal) OVER(PARTITION BY deptno)*100,1) 百分比 from emp

永利酒店赌场 62

 

rollup()与cube():排列组合分组 

1)、group by rollup(a, b, c):
首先会对(a、b、c)进行group by,
然后再对(a、b)进行group by,
其后再对(a)进行group by,
最后对全表进行汇总操作。

 

2)、group by cube(a, b, c):
则首先会对(a、b、c)进行group by,
然后依次是(a、b),(a、c),(a),(b、c),(b),(c),
最后对全表进行汇总操作。

 

 

ROLLUP,是GROUP
BY子句的一种扩展,可以为每个分组返回小计记录以及为所有分组返回总计记录。

CUBE,也是GROUP
BY子句的一种扩展,可以返回每一个列组合的小计记录,同时在末尾加上总计记录。

 

求每个分组的小计和总计:select deptno,sal,sum(sal) from emp group by
rollup(deptno,sal)

永利酒店赌场 63

 

求每一个列的组合的小计记录:select deptno,sum(sal) from emp group by
cube(deptno,sal)

永利酒店赌场 64

 

聚合函数(within group语法):

 

select rank(1500) within group (order by sal desc) “rank of 1500” from
emp;

 

   
解释:如果存在一条记录,这条记录的salary字段值为1500。那么将该条记录插入emp表中后,按照sal字段降序排列后,该条记录的序号为多少?

   可以使用within
group关键字的函数有rank,dense_rank,PERCENT_RANK,PERCENTILE_CONT,PERCENTILE_DISC等

*   永利酒店赌场 65*

 

 

 5、单条记录插入多表

      原理:利用一个insert all 语法:insert all when .. then

     

INSERT ALL WHEN LOCALE = 1 THEN INTO EMPLOYEE1(ID, LOCALE, NAME, AGE,
GENDER, CODE) VALUES (ID, LOCALE, NAME, AGE, GENDER, CODE)

                  WHEN LOCALE = 32 THEN INTO EMPLOYEE2(ID, LOCALE, NAME,
AGE, GENDER, CODE) VALUES(ID, LOCALE, NAME, AGE, GENDER, CODE)

 

 

insert all into table values()

              into  table values()

 

 

 6、Merge的使用

 

 解释:DML语句,适用于批量处理

   MERGE INTO table_name alias1 
  USING (table|view|sub_query) alias2 
  ON (join condition) 
  WHEN MATCHED THEN 
  UPDATE table_name
  SET col1 = col_val1, 
  col2 = col2_val where 条件
  WHEN NOT MATCHED THEN 
  INSERT (column_list) VALUES (column_values) where 条件;

 

MERGE INTO EMPLOYEE E

USING (SELECT * FROM EMPLOYEE1) E1

ON (E.NAME = E1.NAME)

WHEN MATCHED THEN

    UPDATE SET E.CODE = E1.CODE, E.AGE = E1.AGE

WHEN NOT MATCHED THEN

   INSERT  (E.ID, E.LOCALE, E.NAME, E.AGE, E.GENDER, E.CODE)
VALUES(E1.ID, E1.LOCALE, E1.NAME, E1.AGE, E1.GENDER, E1.CODE);

 

备注:对两张表的两个字段相匹配,如果匹配上了就做更新操作,否则就做插入操作。

 

 7、KEEP的使用

    
keep是Oracle下的另一个分析函数,他的用法不同于通过over关键字指定的分析函数,可以用于这样一种场合下:取同一个分组下以某个字段排序后,对指定字段取最小或最大的那个值。

   

  一般写法是 MIN [ MAX ] (A) KEEP(DENSE_RANK FIRST [ LAST ] ORDER
BY B),这里引用别人说的明的解释一下:

DENSE_RANK

功能描述:根据ORDER
BY子句中表达式的值,从查询返回的每一行,计算它们与其它行的相对位置。组内的数据按ORDER
BY子句排序,然后给每一行赋一个号,从而形成一个序列,该序列从1开始,往后累加。每次ORDER
BY表达式的值发生变化时,该序列也随之增加。有同样值的行得到同样的数字序号(认为null时相等的)。密集的序列返回的时没有间隔的数。

 

FIRST

功能描述:从DENSE_RANK返回的集合中取出排在最前面的一个值的行(可能多行,因为值可能相等),因此完整的语法需要在开始处加上一个集合函数以从中取出记录。

LAST

功能描述:从DENSE_RANK返回的集合中取出排在最后面的一个值的行(可能多行,因为值可能相等),因此完整的语法需要在开始处加上一个集合函数以从中取出记录。

所以默认排序下,FIRST可以理解是取小值,LAST取大值。而前面的MIN或者MAX则是在KEEP的结果集中取某一字段的最大值或最小值。

 

 
keep和普通分析函数的区别:普通的分析函数只是列出分组后的记录,而对每一个组的记录进行统计分析。

                            keep对分组内的函数通过order
by和max(),min()选取某个字段的值。可以理解成这里的keep就是sum() groud by
deptno 前的sum()那样

 

  实例:取出各个部门薪资最高的员工编号

 
1、select deptno,empno,sal,max(empno) keep(dense_rank first order by sal desc) over(partition by deptno) from emp2

  2、select deptno,max(empno) keep(dense_rank first order by sal desc) from emp2 group by deptno 

        解释:按deptno分组,再对分组中的sal降序,取出第一个sal的员工号

永利酒店赌场 66

 

 

8、SQL查询正则表达式的使用

 

  ORACLE中的支持正则表达式的函数主要有下面四个:
1,REGEXP_LIKE :与LIKE的功能相似

    select * from emp where regexp_like(empno,’7[0-9]{2}9′)
2,REGEXP_INSTR :与INSTR的功能相似

 

REGEXP_INSTR

6个参数

第一个是输入的字符串

第二个是正则表达式

第三个是标识从第几个字符开始正则表达式匹配。(默认为1)

第四个是标识第几个匹配组。(默认为1)

第五个是指定返回值的类型,如果该参数为0,则返回值为匹配位置的第一个字符,如果该值为非0则返回匹配值的最后一个位置。

第六个是是取值范围:

i:大小写不敏感;

c:大小写敏感;

n:点号 . 不匹配换行符号;

m:多行模式;

x:扩展模式,忽略正则表达式中的空白字符。

    SELECT REGEXP_INSTR(a,'[0-9]+’) AS A FROM test_reg_substr;

 

3,REGEXP_SUBSTR :与SUBSTR的功能相似

   

REGEXP_SUBSTR函数格式如下:

   function REGEXP_SUBSTR(String, pattern, position, occurrence,
modifier)
__srcstr     :需要进行正则处理的字符串
__pattern    :进行匹配的正则表达式
__position   :起始位置,从第几个字符开始正则表达式匹配(默认为1)
__occurrence :标识第几个匹配组,默认为1
__modifier  
:模式(’i’不区分大小写进行检索;’c’区分大小写进行检索。默认为’c’。)

  1. –1、查询使用正则分割后的第一个值,也就是34  
  2. SELECT REGEXP_SUBSTR(‘34,56,-23′,'[^,]+’,1,1,’i’) AS STR FROM DUAL;  
  3. –结果是:34   
  4. –2、查询使用正则分割后的最后一个值,也就是-23  
  5. SELECT REGEXP_SUBSTR(‘34,56,-23′,'[^,]+’,1,3,’i’) AS STR FROM DUAL;  
  6. –结果是:-23  

4,REGEXP_REPLACE :与REPLACE的功能相似

 

 

 

 

    

9、常见函数

TRUNC:截取函数

EXTRACT:用于从一个date或者interval类型中截取到特定的部分

NVL

DECODE

length:字符长度

lengthb:字节长度

ASCII

INITCAP:首字母大写

 

SOUNDEX:返回由四个字符组成的代码 (SOUNDEX) 以评估两个字符串的相似性

MONTHS_BETWEEN

ADD_MONTHS

NEXT_DAY

LAST_DAY

ROUND:函数用于把数值字段舍入为指定的小数位数

 

10、分页函数

 

–普通写法

 

SELECT AA.FWZL, AA.FWTYBH

  FROM (SELECT A.FWZL, A.FWTYBH, ROWNUM RN

          FROM (SELECT F.FWZL, F.FWTYBH FROM FW F ORDER BY F.FWTYBH
DESC) A

         WHERE ROWNUM <= 120020) AA

 WHERE AA.RN > 120000;

 

–rowid写法

SELECT /*+ ROWID(FW) */ FW.FWZL, FW.FWTYBH

   FROM FW FW,

        (SELECT AA.RID, AA.RN

           FROM (SELECT A.RID, ROWNUM RN

                  FROM (SELECT /*+ index(F IDX_FW_FWTYBH) */

                         ROWID RID

                          FROM FW F

                          ORDER BY F.FWTYBH DESC) A

                 WHERE ROWNUM <= 120020) AA

          WHERE AA.RN > 120000) B

  WHERE FW.ROWID = B.RID;

网站地图xml地图