Oracle高级查询之over(partition by...)

为了方便学习和测试,所有的例子都是在Oracle自带用户Scott下建立的。

[sql] view plain copy

print?

  1. create table EMP
  2. (
  3. empno    NUMBER(4) not null,
  4. ename    VARCHAR2(10),
  5. job      VARCHAR2(9),
  6. mgr      NUMBER(4),
  7. hiredate DATE,
  8. sal      NUMBER(7,2),
  9. comm     NUMBER(7,2),
  10. deptno   NUMBER(2)
  11. )
  12. alter table EMP
  13. add constraint PK_EMP primary key (EMPNO);
  14. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  15. values (7369, ‘SMITH‘, ‘CLERK‘, 7902, to_date(‘17-12-1980‘, ‘dd-mm-yyyy‘), 800, null, 20);
  16. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  17. values (7499, ‘ALLEN‘, ‘SALESMAN‘, 7698, to_date(‘20-02-1981‘, ‘dd-mm-yyyy‘), 1600, 300, 30);
  18. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  19. values (7521, ‘WARD‘, ‘SALESMAN‘, 7698, to_date(‘22-02-1981‘, ‘dd-mm-yyyy‘), 1250, 500, 30);
  20. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  21. values (7566, ‘JONES‘, ‘MANAGER‘, 7839, to_date(‘02-04-1981‘, ‘dd-mm-yyyy‘), 2975, null, 20);
  22. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  23. values (7654, ‘MARTIN‘, ‘SALESMAN‘, 7698, to_date(‘28-09-1981‘, ‘dd-mm-yyyy‘), 1250, 1400, 30);
  24. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  25. values (7698, ‘BLAKE‘, ‘MANAGER‘, 7839, to_date(‘01-05-1981‘, ‘dd-mm-yyyy‘), 2850, null, 30);
  26. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  27. values (7782, ‘CLARK‘, ‘MANAGER‘, 7839, to_date(‘09-06-1981‘, ‘dd-mm-yyyy‘), 2450, null, 10);
  28. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  29. values (7788, ‘SCOTT‘, ‘ANALYST‘, 7566, to_date(‘19-04-1987‘, ‘dd-mm-yyyy‘), 3000, null, 20);
  30. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  31. values (7839, ‘KING‘, ‘PRESIDENT‘, null, to_date(‘17-11-1981‘, ‘dd-mm-yyyy‘), 5000, null, 10);
  32. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  33. values (7844, ‘TURNER‘, ‘SALESMAN‘, 7698, to_date(‘08-09-1981‘, ‘dd-mm-yyyy‘), 1500, 0, 30);
  34. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  35. values (7876, ‘ADAMS‘, ‘CLERK‘, 7788, to_date(‘23-05-1987‘, ‘dd-mm-yyyy‘), 1100, null, 20);
  36. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  37. values (7900, ‘JAMES‘, ‘CLERK‘, 7698, to_date(‘03-12-1981‘, ‘dd-mm-yyyy‘), 950, null, 30);
  38. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  39. values (7902, ‘FORD‘, ‘ANALYST‘, 7566, to_date(‘03-12-1981‘, ‘dd-mm-yyyy‘), 3000, null, 20);
  40. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
  41. values (7934, ‘MILLER‘, ‘CLERK‘, 7782, to_date(‘23-01-1982‘, ‘dd-mm-yyyy‘), 1300, null, 10);

注:标题中的红色order by是说明在使用该方法的时候必须要带上order by

一、rank()/dense_rank() over(partition by ...order by ...)

现在客户有这样一个需求,查询每个部门工资最高的雇员的信息,相信有一定oracle应用知识的同学都能写出下面的SQL语句:

[sql] view plain copy

print?

  1. select * from (select ename, job, hiredate, e.sal, e.deptno
  2. from emp e,
  3. (select deptno, max(sal) sal from emp group by deptno) t
  4. where e.deptno = t.deptno
  5. and e.sal = t.sal)
  6. order by deptno;
  7. select * from (select ename 姓名, job 职业, hiredate 入职日期, e.sal 工资, e.deptno 部门
  8. from emp e,
  9. (select deptno, max(sal) sal from emp group by deptno) t
  10. where e.deptno = t.deptno
  11. and e.sal = t.sal)
  12. order by 部门;


在满足客户需求的同时,大家应该习惯性的思考一下是否还有别的方法。这个是肯定的,就是使用本小节标题中rank() over(partition by...)或dense_rank() over(partition by...)语法,SQL分别如下:

[sql] view plain copy

print?

  1. select empno, ename, job, hiredate, sal, deptno
  2. from (select empno, ename, job, hiredate, sal, deptno, rank() over(partition by deptno order by sal desc) r from emp)
  3. where r = 1;
  4. select empno, ename, job, hiredate, sal, deptno
  5. from (select empno, ename, job, hiredate, sal, deptno, dense_rank() over(partition by deptno order by sal desc) r from emp)
  6. where r = 1

为什么会得出跟上面的语句一样的结果呢?这里补充讲解一下rank()/dense_rank() over(partition by e.deptno order by e.sal desc)语法。
over:  在什么条件之上。
partition by e.deptno:  按部门编号划分(分区)。
order by e.sal desc:  按工资从高到低排序(使用rank()/dense_rank() 时,必须要带order by否则非法)
rank()/dense_rank():  分级
整个语句的意思就是:在按部门划分的基础上,按工资从高到低对雇员进行分级,“级别”由从小到大的数字表示(最小值一定为1)。

那么rank()和dense_rank()有什么区别呢?
rank():  跳跃排序,如果有两个第一级时,接下来就是第三级。
dense_rank():  连续排序,如果有两个第一级时,接下来仍然是第二级。

小作业:查询部门最低工资的雇员信息。

二、min()/max() over(partition by ...)

现在我们已经查询得到了部门最高/最低工资,客户需求又来了,查询雇员信息的同时算出雇员工资与部门最高/最低工资的差额。这个还是比较简单,在第一节的groupby语句的基础上进行修改如下:

-- 查询每位雇员信息的同时算出雇员工资与所属部门最高/最低员工工资的差额

[sql] view plain copy

print?

  1. select ename 姓名, job 职业, hiredate 入职日期, e.deptno 部门, e.sal 工资, e.sal-me.min_sal 最低差额, me.max_sal-e.sal 最高差额
  2. from emp e, (select deptno, min(sal) min_sal, max(sal) max_sal from emp group by deptno) me
  3. where e.deptno = me.deptno order by e.deptno, e.sal;

上面我们用到了min()和max(),前者求最小值,后者求最大值。如果这两个方法配合over(partition by ...)使用会是什么效果呢?大家看看下面的SQL语句:

[sql] view plain copy

print?

  1. select ename 姓名, job 职业, hiredate 入职日期, deptno 部门,
  2. min(sal) over(partition by deptno) 部门最低工资,
  3. max(sal) over(partition by deptno) 部门最高工资
  4. from emp order by deptno, sal;
  5. select ename 姓名, job 职业, hiredate 入职日期, deptno 部门,
  6. nvl(sal - min(sal) over(partition by deptno), 0) 部门最低工资差额,
  7. nvl(max(sal) over(partition by deptno) - sal, 0) 部门最高工资差额
  8. from emp order by deptno, sal;

这两个语句的查询结果是一样的,大家可以看到min()和max()实际上求的还是最小值和最大值,只不过是在partition by分区基础上的。

小作业:如果在本例中加上order by,会得到什么结果呢?

三、lead()/lag() over(partition by ... order by ...)

中国人爱攀比,好面子,闻名世界。客户更是好这一口,在和最高/最低工资比较完之后还觉得不过瘾,这次就提出了一个比较变态的需求,计算个人工资与比自己高一位/低一位工资的差额。这个需求确实让我很是为难,在groupby语句中不知道应该怎么去实现。不过。。。。现在我们有了over(partition by ...),一切看起来是那么的简单。如下:

-- 计算个人工资与比自己高一位/低一位工资的差额

[sql] view plain copy

print?

  1. select ename 姓名, job 职业, sal 工资, deptno 部门,
  2. lead(sal, 1, 0) over(partition by deptno order by sal)  比自己工资高的部门前一个,
  3. lag(sal, 1, 0) over(partition by deptno order by sal)  比自己工资低的部门后一个,
  4. nvl(lead(sal) over(partition by deptno order by sal) - sal, 0)  比自己工资高的部门前一个差额,
  5. nvl(sal - lag(sal) over(partition by deptno order by sal), 0)   比自己工资高的部门后一个差额
  6. from emp;

看了上面的语句后,大家是否也会觉得虚惊一场呢(惊出一身冷汗后突然鸡冻起来,这样容易感冒)?我们还是来讲解一下上面用到的两个新方法吧。
lead(列名,n,m):  当前记录后面第n行记录的<列名>的值,没有则默认值为m;如果不带参数n,m,则查找当前记录后面第一行的记录<列名>的值,没有则默认值为null。
lag(列名,n,m):  当前记录前面第n行记录的<列名>的值,没有则默认值为m;如果不带参数n,m,则查找当前记录前面第一行的记录<列名>的值,没有则默认值为null。

下面再列举一些常用的方法在该语法中的应用(注:带order by子句的方法说明在使用该方法的时候必须要带order by):

[sql] view plain copy

print?

  1. select ename 姓名, job 职业, sal 工资, deptno 部门,
  2. first_value(sal) over(partition by deptno) first_sal,
  3. last_value(sal) over(partition by deptno) last_sal,
  4. sum(sal) over(partition by deptno) 部门总工资,
  5. avg(sal) over(partition by deptno) 部门平均工资,
  6. count(1) over(partition by deptno) 部门总数,
  7. row_number() over(partition by deptno order by sal) 序号
  8. from emp;

重要提示:大家在读完本片文章之后可能会有点误解,就是OVER (PARTITION BY ..)比GROUP BY更好,实际并非如此,前者不可能替代后者,而且在执行效率上前者也没有后者高,只是前者提供了更多的功能而已,所以希望大家在使用中要根据需求情况进行选择。

原文摘自傅老师课堂 Mr傅:http://blog.csdn.NET/fu0208/article/details/7179001

http://blog.csdn.net/huxu981598436/article/details/38129177

row_number()浅析:

row_number() OVER (PARTITION BY COL1 ORDER BY COL2) 表示根据COL1分组,在分组内部根据 COL2排序,而此函数计算的值就表示每组内部排序后的顺序编号(组内连续的唯一的).

  与rownum的区别在于:使用rownum进行排序的时候是先对结果集加入伪列rownum然后再进行排序,而此函数在包含排序从句后是先排序再计算行号码.

  row_number()和rownum差不多,功能更强一点(可以在各个分组内从1开时排序)。

在使用 row_number() over()函数时候,over()里头的分组以及排序的执行晚于 where group by  order by 的执行。

partition by 是数据的分区取数,用于给结果集分组,如果没有指定那么它把整个结果集作为一个分组,它和聚合函数不同的地方在于它能够返回一个分组中的多条记录,而聚合函数一般只有一个反映统计值的记录。

  rank()是跳跃排序,有两个第二名时接下来就是第四名(同样是在各个分组内).

  dense_rank()l是连续排序,有两个第二名时仍然跟着第三名。相比之下row_number是没有重复值的 .

  lag(arg1,arg2,arg3): 
arg1是从其他行返回的表达式 
arg2是希望检索的当前行分区的偏移量。是一个正的偏移量,时一个往回检索以前的行的数目。 
arg3是在arg2表示的数目超出了分组的范围时返回的值。

举例:

SQL> DESC T1;

Name                                           Null?    Type
----------------------------------------- -------- ----------------------------
ID                                                    NUMBER
NAME                                                    VARCHAR2(10)
DATE1                                                    DATE

SQL> SELECT * FROM T1;
        ID NAME                           DATE1
---------- ------------------------------ ------------------
       101 aaa                                  09-SEP-13

101 bbb                                  10-SEP-13

101 ccc                                  11-SEP-13

102 ddd                                  08-SEP-13

102 eee                                  11-SEP-13

SQL> SELECT ID,NAME,DATE1,ROW_NUMBER() OVER(partition by ID order by DATE1 desc) as RN FROM T1;

ID NAME               DATE1               RN
---------- ------------------------------ ------------------ ----------
       101 ccc                  11-SEP-13              1
       101 bbb                  10-SEP-13              2
       101 aaa                  09-SEP-13              3
       102 eee                  11-SEP-13              1
       102 ddd                  08-SEP-13              2

把上面语句作为一个子表语句,嵌入到另一条语句中:

SQL> SELECT ID,NAME,DATE1 FROM (SELECT ID,NAME,DATE1,ROW_NUMBER() OVER(partition by ID order by DATE1 desc) as RN FROM T1)T WHERE T.RN=1;

ID NAME                           DATE1
---------- ------------------------------ ------------------
       101 ccc                                  11-SEP-13
       102 eee                                  11-SEP-13

再看几个SQL语句:

语句一:

select row_number() over(order by sale/cnt desc) as sort, sale/cnt 
from (
select -60 as sale,3 as cnt from dual union 
select 24 as sale,6 as cnt from dual union 
select 50 as sale,5 as cnt from dual union 
select -20 as sale,2 as cnt from dual union 
select 40 as sale,8 as cnt from dual);

执行结果:

SORT       SALE/CNT
---------- ----------
             1             10
             2              5
             3              4
             4            -10
             5            -20

语句二:查询员工的工资,按部门排序

select ename,sal,row_number() over (partition by deptno order by sal desc) as sal_order from scott.emp;

执行结果:

ENAME                           SAL      SAL_ORDER
-------------------- ---------- ----------
KING                           5000              1
CLARK                          2450              2
MILLER                         1300              3
SCOTT                          3000              1
FORD                           3000              2
JONES                          2975              3
ADAMS                          1100              4
SMITH                           800              5
BLAKE                          2850              1
ALLEN                          1600              2
TURNER                         1500              3
WARD                           1250              4
MARTIN                         1250              5
JAMES                           950              6

已选择14行。

语句三:查询每个部门的最高工资

select deptno,ename,sal from 
     (select deptno,ename,sal,row_number() over (partition by deptno order by sal desc) as sal_order 
         from scott.emp) where sal_order <2;

执行结果:

DEPTNO ENAME                          SAL
---------- -------------------- ----------
           10 KING                          5000
           20 SCOTT                         3000
           30 BLAKE                         2850

已选择3行。

语句四:

select deptno,sal,rank() over (partition by deptno order by sal) as rank_order from scott.emp order by deptno;

执行结果:

DEPTNO         SAL RANK_ORDER
---------- ---------- ----------
         10        1300           1
         10        2450           2
         10        5000           3
         20         800           1
         20        1100           2
         20        2975           3
         20        3000           4
         20        3000           4
         30         950           1
         30        1250           2
         30        1250           2
         30        1500           4
         30        1600           5
         30        2850           6

已选择14行。

语句五:

select deptno,sal,dense_rank() over(partition by deptno order by sal) as dense_rank_order from scott.emp order by deptn;

执行结果:

DEPTNO         SAL DENSE_RANK_ORDER
---------- ---------- ----------------
         10        1300                 1
         10        2450                 2
         10        5000                 3
         20         800                 1
         20        1100                 2
         20        2975                 3
         20        3000                 4
         20        3000                 4
         30         950                 1
         30        1250                 2
         30        1250                 2
         30        1500                 3
         30        1600                 4
         30        2850                 5

已选择14行。

语句六:

select deptno,ename,sal,lag(ename,1,null) over(partition by deptno order by ename) as lag_ from scott.emp order by deptno;

执行结果:

DEPTNO ENAME                        SAL LAG_
---------- -------------------- ---------- --------------------
         10 CLARK                       2450
         10 KING                        5000 CLARK
         10 MILLER                      1300 KING
         20 ADAMS                       1100
         20 FORD                        3000 ADAMS
         20 JONES                       2975 FORD
         20 SCOTT                       3000 JONES
         20 SMITH                        800 SCOTT
         30 ALLEN                       1600
         30 BLAKE                       2850 ALLEN
         30 JAMES                        950 BLAKE
         30 MARTIN                      1250 JAMES
         30 TURNER                      1500 MARTIN
         30 WARD                        1250 TURNER

已选择14行。

很多年以后,还有很多程序员不知道SQL Server2005有了更方便的分页方法,这就是ROW_NUMBER()函数。我们知道SQL2000时代的分页方式是TOP加NOT IN截取中间数据,效率也是很不错的,但这两种效率到底如何呢,我们这次以一万、十万和百万数据量的数据做演示,比较这两种分页方式的效率。另外为何使用 TOP+NOT IN来和ROW_NUMBER()比较,是因为和游标方式及ISNULL方式分页来说,TOP+NOT IN方式效率更高。前人已有证明,可参考这篇文章:http://www.cnblogs.com/morningwang/archive/2009/01/02/1367277.html ,或者自行搜索更权威文章。

准备工作

准备工具:电脑(当然了o(∩_∩)o )和程序员一名。

同一测试环境,电脑配置如下,数据如有不实,请找周鸿祎~

建立数据表,插入相应数据。表结构如下,Id为自增长主键:

插入100万条测试数据:

?

user table3

go

declare @index int

set @index=0

while @index<1000000

    begin

        insert into Users(Name) values(‘walkingp‘)

        set @index[email protected]index+1

    end

接下来先扫盲一下ROW_NUMBER()函数。

ROW_NUMBER()函数

ROW_NUMBER()函数是根据参数传递过来的order by子句的值,返回一个不断递增的整数值,也就是它会从1一直不断自增1,直到条件不再满足。例如表Users(Id,Name),使用以下sql语句进行查询:

?

select id,name,row_number() over(order by Id desc) as rowNum from users where id<10

select id,name,row_number() over(order by Id) as rowNum from users where id<10

两条语句order by排序相反,执行结果如下:

孰优孰劣

以下两种情况,同样取500000到500100中间的数据。

1、使用ROW_NUMBER()函数。

SQL语句如下:

?

declare @time datetime

declare @ms int

set @time= getdate()

select Id,Name from (select row_number() over(order by Id) as rowNum,* from users) ast where rowNum between 500000 and 500100

set @ms=datediff(ms,@time,getdate())

print @ms--毫秒数

测试了几次,平均在250毫秒:

2、使用TOP加NOT IN方法。

SQL语句如下:

?

select top 100 * from users where Id not in

    (select top 500000 id from users order by id)

    order by id

平均在236毫秒:

好吧,一起执行看看结果:

或者你认为SQL存在缓存的问题,把两部分顺序对掉一下:

以上是百万数据量的对比,再看看1万条数据下的对比:

结论

通过以上对比可以我们可以得到这样的结论:在小数据量下(一般应该认为是10万以下,TOP+NOT IN分页方式效率要比ROW_NUMBER()高;在大数据量下(百万级)ROW_NUMBER()分页方式效率要更高一些。

时间: 2024-10-22 22:53:49

Oracle高级查询之over(partition by...)的相关文章

oracle高级查询(实例基于scott用户四张表)

oracle高级查询(实例基于scott用户四张表) 分组查询 多表查询 子查询 综合实例 ======================================================================= scott用户的四张表(emp,dept,bonus,salgrade) 没有这四张表的可参考http://blog.csdn.net/love_legain/article/details/54311040进行创建 -----------------------

Oracle 高级查询1 关联查询 分组查询

高级查询 1.关联查询作用:可以跨越多表查询 --查询出员工的名字和他所在部门的的名字 语法:select 列,列,列 from 表1 join 表2on 表1外键=表2主键 2.外联接 左外联[left outer join] 以关联的左边为准,即使右边没有与之匹配的记录,则左边的记录也要 出现在结果集中,右边全部以NULL值显示. 右外联[right outer join] 以关联的右边为准,即使左边没有与之匹配的记录,则右边的记录也要 出现在结果集中,左边全部以NULL值显示. 3分组查询

Oracle 高级查询

Oracle SQL 一些函数用法 以下sql环境都是在 Oracle 11g/scott完成 Group by 与GROUP BY一起使用的关建字 GROUPING,GROUP SET,ROLLUP,CUBE结合这些特性的函数可以实现SQL的明细+分组统计 GROUPING GROUPING 记录是对哪个字段进行统计,其值只能是 0 & 1 Group(column) =0 表示此字段参与了分组统计 =1表示字段未参与分组统计 select deptno,job,sum(sal),groupi

Oracle高级查询之CONNECT BY

为了方便大家学习和测试,所有的例子都是在Oracle自带用户Scott下建立的. Oracle中的select语句可以用start with ... connect by prior ...子句实现递归查询,connect by 是结构化查询中用到的,其基本语法是: select ... from where <过滤条件,用于对返回的所有记录进行过滤>start with <根结点的限定语句,当然可以放宽限定条件,以取得多个根结点,实际就是多棵树>connect by [prior

Oracle高级查询

本章将学习的内容有: 如何使用集合操作符,集合操作符用于将两个或多个查询返回的行组合起来 使用TRANSLATE函数 1.使用集合操作符 操作符 说明 UNION ALL 返回各个查询检索出的所有行,包括重复行 UNION 返回各个查询检索出的所有行,不包括重复行 INTERSECT 返回两个检索锁的共有行 MINUS 返回将第二个查询检索出的行从第一个查询检索的行中去除之后的行 注意:当使用集合操作符的时候,必须牢记这样一条限制:所有查询返回的列数以及列的类型必须匹配,但列名可以不同. 使用I

Oracle高级查询之GROUP BY

对于group by 的方式不再累赘,高级方式如下案例. 参考老师文章:http://blog.csdn.net/fu0208/article/details/7183258,向老师致敬,辛苦了 为了方便大家学习和测试,所有的例子都是在Oracle自带用户Scott下建立的.所用emp表, 如果没有此表请参考文章:http://blog.csdn.net/xiaokui_wingfly/article/details/43957003中拷贝 现在客户的需求是统计部门中每种工作的工资总额,最后还需

oracle数据库的高级查询方法 多表联合查询

oracle查询 一,简单查询 order by 1,2 select t.sno, t.sname, t.ssex from STUDENT t order by 1,2,3 group by 增强版 SELECT p.toma, p.ptype, SUM(p.lastcou) FROM product p GROUP BY rollup(p.toma, p.ptype) 二,高级查询(多表连接查询) 笛卡尔积的概念: 所谓笛卡尔积,通俗点说就是指包含两个集合中任意取出两个元素构成的组合的集合

Oracle高级队列介绍

原始链接:http://www.oracle-developer.net/display.php?id=411 oracle高级队列介绍 高级队列Advanced Queuing(AQ)在oracle多个版本都可得到.他是oracle原生消息软件并且在每一个版本都在加强. 这篇文章提供了一个AQ的高级概览.尤其是我们将看到如何启动一个队列并进行入列--出列操作,还有通过通知创建异步出列. 注意AQ支持数据库意外的消息监听(例如JMS消息队列).本文仅涉及数据库内部消息通信. 要求: 本例要求指定

SQL Server T-SQL高级查询(转)

高级查询在数据库中用得是最频繁的,也是应用最广泛的.   ? 基本常用查询   --select select * from student; --all 查询所有 select all sex from student; --distinct 过滤重复 select distinct sex from student; --count 统计 select count(*) from student; select count(sex) from student; select count(di