今天一位朋友,提了一个很智慧的问题,Oracle查order之后的前多少条,只能用这种写法?
select * from
印象当中,像SQL Server支持top操作,MySQL支持limit,Oracle中一般情况下,我们按顺序取前几条的操作就是利用order by和rownum,还会有其他方式?另外以上这种写法能不能不要子查询?
这些问题的答案,好像有些含糊。通过实验,论证一下。
1. 顺序取前几条的操作
需求就是得到order by cdate desc排序之后的前9条数据。
为了方便说明,创建一张测试表,存入15条数据,
SQL> desc t_order
如果不带order by,查询返回的结果集顺序就是如下,
P.S. 关于数据检索顺序的问题,可以参考《Oracle数据顺序问题》。
SQL> select * from t_order;
按照cdate降序排列,结果集如下所示,注意这两种select操作中的id=16的位置是不同的,
SQL> select * from t_order order by cdate desc;
首先,我们看下原始SQL,因为需求是order by cdate desc排序之后的前9条数据,所以返回这9条数据是正确的,
SQL> select * from (select * from t_order order by cdate desc) where rownum<10;
第一种替代方式,使用窗口函数row_number(),
select id, cdate from (select id, cdate, row_number() over (order by cdate desc) rn from t_order) where rn<10;
如果用的rownum,外层循环select的是*,结果是对的,
SQL> select * from (select id, cdate, row_number() over (order by cdate desc) from t_order) where rownum<10;
我们从执行计划看下,SORT关键字说明这个是经过排序的,语义上正确,
select * from (select id, cdate, row_number() over (order by cdate desc) from t_order) where rownum<10;
但是,如果select的是字段名称且未带着row_number(),结果就可能错,
SQL> select id, cdate from (select id, cdate, row_number() over (order by cdate desc) from t_order) where rownum<10;
如果是字段,带着row_number(),就是正确的,
SQL> select id, cdate, c from (select id, cdate, row_number() over (order by cdate desc) c from t_order) where rownum<10;
第二种替代方式,12c以上,可以使用fetch,
SQL> select * from t_order order by cdate desc fetch first 9 rows only;
但是我们看他的执行计划,应该是将fetch自动转成了row_number()操作,
----------------------------------------------------------------------------------------------------------------------
以上是从功能角度的尝试,如果从性能,每次都是全表扫,效率注定不高,因为测试SQL,无其他检索条件,只是有个order by,因此为cdate创建索引,同时将表数据量,扩为10万,再执行SQL,发现还是全表扫,
create index idx_to_01 on t_order(cdate);
此时其实忽略了一个问题,就是cdate字段非空属性的问题,因为cdate,默认允许为空,索引不存储空值,因此即使是order by,都是不会使用这个索引的,避免漏选数据。
设置cdate非空,
SQL> alter table t_order modify cdate not null;
此时执行SQL,索引全扫描,因为他是单块读索引,避免了排序,从Cost和Buffer可以看到,都降下来了,
select * from (select * from t_order order by cdate desc) where rownum<10;
2. 原始的写法能不能不要子查询?
即如下这两条SQL,是否是等价的?
select * from (select * from t_order order by cdate desc) where rownum<10;
从执行结果看,这两个SQL,结果集是不同的,带子查询,id=1到10,
SQL> select * from (select * from t_order order by cdate desc) where rownum<10;
不带子查询,缺少id=10,多了id=16,
SQL> select * from t_order where rownum<10 order by cdate desc;
需求是得到order by cdate desc排序之后的前9条数据,明显地,第一条SQL,才是正确的,
SQL> select * from t_order order by cdate desc;
而第二条SQL,很明显,是从以下结果集中过滤出来的,
SQL> select * from t_order;
两者的区别,就在于是先排序再得到前9条,还是先得到前9条再排序。
我们从执行计划,能看到他的执行路径是什么,第一条SQL,先全表扫描得到所有的数据,然后排序,再执行rownum,
select * from (select * from t_order order by cdate desc) where rownum<10;
第二条SQL,虽然显示TABLE ACCESS FULL,但是rows就是9,根据rownum直接得到9条(而且是无序的),然后执行order by排序,
select * from t_order where rownum<10 order by cdate desc;
因此,为了得到order by排序后的前几条,需要用到子查询。
当我们碰到这种不知道谁的语义正确的时候,从执行计划,会给我们些提示,判断究竟谁是正确的。
近期更新的文章:
《日常工作中碰到的几个技术问题》
《了解一下sqlhc》
《Oracle的MD5函数介绍》
《Oracle 19c的examples静默安装》
《sqlplus登录缓慢的解决》
《VMWare 11安装RedHat Linux 7过程中碰到的坑》
《COST值相同?是真是假?》
《Oracle 11g的examples静默安装》
《同名的同义词和视图解惑》
《v和v\_的一些玄机》
文章分类和索引:
《公众号700篇文章分类和索引》




