日期:2014-05-16  浏览次数:20617 次

Oracle TOPN分析及 rownum相关知识

ORACLE中经常会进行TOP N的查询,即列出按照一定排序的数据记录。这种查询操作分两种:


1.排序,列出所有记录或前N条记录


2.排序,列出指定区间的记录

?

在ORACLE中是通过对表中的一个伪列 rownum进行操作的。也就是因为这一点,使得在ORACLE中进行TOP N的查询,尤其是指定区间的排序查询比较复杂和难以理解。

?

现在我们先看看第一种,根据第一种的定义我们很容易写出以下语句:

?

select 字段列表 from
? (select 字段列表 from 表名 order by 排序字段)
where rownum<=5

?

以scott账户为例,要求:查出emp表中以工资降序的前5名员工的所有信息,查询语句如下:

?

select * from
? (select * from emp order by sal desc)
where rownum<=5

?

有的朋友可能笑了,这还用得着子查询?我写以下语句不也能搞定吗?

?

a.select * from emp where rownum<=5 order by sal desc

b.select * from emp order by sal desc? where rownum<=5

?

以上两种查询语句都是不对的,先说b,大家要明白ORACLE的查询语句也有其固定语法的,如下:

?

select 字段列表 from 表名 +where 子句? +group by 子句? +having 子句? +order by 子句,所以b不符合语法直接否定,这样的语句运行后会报语句没有正确结束的错误。

?

而a查询语句执行后,返回的结果并不是我们预期的。这是因为虽然a查询语句虽然符合语法规则,但是逻辑上却不合理。它的逻辑意义是先取出前五条记录然后再排序,显然将我们所要做的TOP N分析的步骤搞反了。

?

接下来我们继续看看第二种情况,有的朋友可能会从第一种情况中推理出以下语句:

?

select 字段列表 from
? (select 字段列表 from 表名 order by 排序字段)
where rownum>=11 and rownum<=15

?

毫无疑问,这句查询语句执行后不会有任何结果。要讲明白此查询语句的错误,我们就不得不把rownum拿出来研究研究了。

?

首先大家要知道rownum是ORACLE在我们查询时自动生成的一个从1开始计数的伪列(就是一个虚假的列,看起来不存在,但是却实实在在存在,呵呵。。。比较难理解吧?要不我怎么说因为这点所以才造成ORACLE TOP N查询的困难呢?大家继续往下看,会慢慢理解的。我也是花了很多时间去理解的。)

?

这个伪列的产生机理是这样:当我们进行查询操作时,数据库的记录一条一条拿出,并给词条记录自动生成伪列rownum,这里我再强调一下:rownum是从1开始计数的。

?

那么让我们回头看看上边的查询语句为什么会什么都没有查到。当执行了查询操作后,数据库先拿出一条和查询条件比较。这里的查询条件有两个:rownum>=11 和rownum<=15。查询出的rownum是从1开始计数的,也就是拿出的记录rownum=1.它满足rownum<=15但是不满足rownum>=11。所以被无情的抛弃了。紧接着再拿出一条数据,当然这条数据还会走刚才那条数据的老路:自己的rownum的值被赋予了1,然后与rownum>=11 和rownum<=15的条件比较,结果当然还是被抛弃。这个时候大家也许就会恍然大悟了,如果数据库是这样运行的。那么就永远不会第一次取出rownum>=2的值了,更不用谈>=15了。所以以上的查询语句就不会有查询结果。

?

要实现TOP N的查询第二种的实现我们不得不发挥我们的聪明才智了。于是有了以下的查询语句:

?

select * from(select rownum myno,a.* from (select * from emp order by sal desc) a) b where myno>=5 and myno<=10;

?

以上的语句由内到外划分可分为3部分:

1.select * from emp order by sal desc

?? 这条查询语句是为其查询结果排序的,这个相当容易理解,再次不多讲解。

?

2.select rownum myno,a.* from (select * from emp order by sal desc) a

?? 这条查询语句将第一条查询语句作为子查询,可以将第一条语句的查询结果作为一个临时表,并且别名为a。这张临时表中的记录与原来的emp表相同,只是全部排序过。

?? 本查询语句的查询结果就是 rownum myno?? 和 a.*.

?? rownum myno 就是每条记录系统自动给赋予的rownum,读到这里有的朋友会问:为什么要给他起个别名呢?? 答案是:不得不起,不然后面的业务没法开展。而且我们之所以能实现TOP N的查询第二种的实现最大的功臣就是这个别名了,继续往下看你就会明白。

?? a.*就好理解了,指的就是a表的所有内容

?

3.select * from(select rownum myno,a.* from (select * from emp order by sal desc) a)? b where myno>=5 and myno<=10;

这条语句就是把第二条的查询结果作为子查询,作为一张临时表进行操作,别名为b.
b表中不仅有排序好的emp表的所有记录,此时它还多了一列实体列myno.通过对myno的操作进行查询就没有什么问题了。因为它是本来就存在的,并不是系统在查询时才临时生成的。你可以把myno理解为b表的固有列。如果在第三次查询时你用的是rowno而不是myno进行操作,那么你还会进入系统临时产生rowno的错误当中。

?

总结一下其核心思想:就是将伪列rowno想办法实例为一个可操作的固有列,通过这个固有列来达到TOP N分析的第二种实现。