1.Oracle 12c R1 (12.1)支持limit语句
从Oracle 12c R1 (12.1)
版本开始,oracle开始支持limit语句,但是跟熟悉的limit(1, 10)
语句还是有区别的
oracle的语句更复杂,但是有更多选项能做更多的事。参考oracle limit完整的语法 (关于oracle内部是如何实现limit的可以参考这个回答)
那么一个oracle limit语句到底应该怎么写了,比如要取21-30行的数据:
SELECT *
FROM sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
上面是一个简单直接的例子,下面是引用自官方说明的更多实例:
1.1 初始化
DROP TABLE rownum_order_test;
CREATE TABLE rownum_order_test (
val NUMBER
);
INSERT ALL
INTO rownum_order_test
INTO rownum_order_test
SELECT level
FROM dual
CONNECT BY level <= 10;
COMMIT;
表rownum_order_test
里的内容:
SELECT val
FROM rownum_order_test
ORDER BY val;
VAL
----------
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
9
10
10
20 rows selected.
1.2 Top-N 查询
SELECT val
FROM rownum_order_test
ORDER BY val DESC
FETCH FIRST 10 ROWS ONLY;
VAL
----------
10
10
9
9
8
5 rows selected.
1.3 WITH TIES
使用WITH TIES
语句,在最后一行有重复值存在时,将会把最后一行的重复值都返回。在这个例子中第五行的值是8,但是根据排序,有两个行数值都为8的,所以这两个都保留
SELECT val
FROM rownum_order_test
ORDER BY val DESC
FETCH FIRST 5 ROWS WITH TIES;
VAL
----------
10
10
9
9
8
8
6 rows selected.
1.4 前百分比数据量限制
SELECT val
FROM rownum_order_test
ORDER BY val
FETCH FIRST 20 PERCENT ROWS ONLY;
VAL
----------
1
1
2
2
4 rows selected.
1.5 分页
ELECT val
FROM rownum_order_test
ORDER BY val
OFFSET 4 ROWS FETCH NEXT 4 ROWS ONLY;
VAL
----------
3
3
4
4
4 rows selected.
代表从OFFSET+1开始取4行
1.6 offset和百分比结合
SELECT val
FROM rownum_order_test
ORDER BY val
OFFSET 4 ROWS FETCH NEXT 20 PERCENT ROWS ONLY;
VAL
----------
3
3
4
4
4 rows selected.
2.旧版本不支持limit语句
旧版本的oracle不支持limit语句,需要通过子查询来实现,原因可参考On ROWNUM and limiting results
2.1 Top-N
select *
from
( select *
from emp
order by sal desc )
where ROWNUM <= 5;
2.2 区间
select * from
( select a.*, ROWNUM rnum from
( <your_query_goes_here, with order by> ) a
where ROWNUM <= :MAX_ROW_TO_FETCH )
where rnum >= :MIN_ROW_TO_FETCH;
2.3 原因
oracle分页的写法显得特别臃肿和多余,但是对于旧版本来说没得选择,这个写法规避了很多排序的问题并且高效
首先要明白一个概念,ROWNUM
并不是跟行绑定的,他是sql执行完from where
后,并在排序和聚合前,每得到一个结果就分配一个ROWNUM
所以像下面这种写法肯定得不到任何结果
SELECT *
FROM
t
WHERE ROWNUM > 1
当from
获取到第一行,因为还没有经过where
判断,所以没有分配ROWNUM
,只能读取到ROWNUM
的默认值1,然而这个where
条件肯定不能成立,也就是说ROWNUM
没有分配到第一行(分配逻辑包含自增),永远到不了2,该查询肯定没有结果
2.3.1 为什么要用子查询
当使用排序时,因为ROWNUM
的分配是在排序之前的,所以是先得到10条数据然后排序,而不是先排序后取10条
SELECT *
FROM
t
WHERE
ROWNUM <= 10
ORDER BY
col DESC
需要封装一个子查询后再限制ROWNUM
SELECT *
FROM
(
SELECT *
FROM
t
ORDER BY
col DESC
)
WHERE
ROWNUM <= 10
2.3.2 对Top-N的优化
使用子查询ROWNUM
的方式,另外一个原因是oracle对他进行了优化
如果直接进行查询,再对结果进行筛选,像下面的例子
SELECT *
FROM
t
ORDER BY
col DESC
当表的数据有百万甚至千万行的时候,这个表的所有数据都要放到内存中进行排序,如果用于排序的内存满了,还需要暂存到磁盘上。 大量io和磁盘读写,耗费时间,并且占用资源,仅仅只是为了获取10行数据
使用子查询ROWNUM
的方式,如下
SELECT *
FROM
(
SELECT *
FROM
t
ORDER BY
col DESC
)
WHERE
ROWNUM <= 10
ORACLE对他进行了优化,像上面的例子,当查询一开始,会将最开始的10个col
值保存到内存中排序,然后获取第11个跟内存中的第10个进行比较
如果在区间外,那么直接忽略这个值。如果在区间内,那么删除原本第10个值,追加第11个,重新进行排序
按照这个逻辑,遍历时只需要对内存中的10个值进行排序,节省大量资源和时间
2.3.3 分页注意事项
与top-n的随机性不一样,分页需要同样条件下每次的查询结果都是一致的,但如果排序列有大量的重复值,一致性无法保证
SELECT *
FROM
(
SELECT a.*,ROWNUM rn
FROM
(
SELECT *
FROM
t
ORDER BY
col DESC
) a
WHERE
ROWNUM <= 10
)
HWERE rn >=6
上面的例子中如果col
列有大量重复值,每次查询的结果都会不一样
解决方案是在order by语句中追加ROWID
,因为ROWID
在表中是唯一的
SELECT *
FROM
(
SELECT a.*,ROWNUM rn
FROM
(
SELECT *
FROM
t
ORDER BY
col DESC ,ROWID
) a
WHERE
ROWNUM <= 10
)
HWERE rn >=6