首先ROWNUM只适用于小于或小于等于,如果进行等于判断,那么只能等于1,不能进行大于的比较。
ROWNUM是oracle系统顺序分配为从查询返回的行的编号,返回的第一行分配的是1,第二行是2,依此类推。
ROWNUM总是从1开始,不管当前的记录是否满足查询结果,ROWNUM返回的值都是1,如果这条记录的值最终满足所有的条件,
那么ROWNUM会递加,下一条记录的ROWNUM会返回2,否则下一条记录的ROWNUM仍然返回1。
理解了这一点,就清楚为什么一般的ROWNUM大于某个值或等于某个不为1的值是无法返回结果的,
因此对于每条记录的ROWNUM都是1,而ROWNUM为1不满足查询的结果,所以下一条记录的ROWNUM不会递增,
仍然是1,因此所有的记录都不满足条件。
DROP TABLE TT3;
CREATE TABLE TT3 AS SELECT * FROM DBA_OBJECTS WHERE ROWNUM <10;
--ROWNUM < N是永远成立的
SELECT OWNER,OBJECT_NAME,ROWNUM FROM TT3 WHERE ROWNUM <10;
OWNER OBJECT_NAME ROWNUM
------------------------------ ---------------------------------------------------
SYS ICOL$ 1
SYS I_USER1 2
SYS CON$ 3
SYS UNDO$ 4
SYS C_COBJ# 5
SYS I_OBJ# 6
SYS PROXY_ROLE_DATA$ 7
SYS I_IND1 8
SYS I_CDEF2 9
--ROWNUM > N是不成立的
SELECT OWNER,OBJECT_NAME,ROWNUM FROM TT3 WHERE ROWNUM > 1;
OWNER OBJECT_NAME ROWNUM
------------------------------ ---------------------------------------------------
--可以看出ROWNUM并不是按照排序后的结果然后分配ROWNUM,而是一开始取10条记录,再排序
SQL> select * from
2 (
3 select to_number(object_id),rownum rn from t2000 order by to_number(object_id)
4 ) where rn <= 10;
TO_NUMBER(OBJECT_ID) RN
-------------------- ----------
3 6
15 4
20 1
25 7
28 3
29 5
40 10
41 8
46 2
54 9
--解决上面的问题,需要采用下面的sql,先排完序,外层再过滤条数
select *
from (select A.*, rownum rn
from (select to_number(object_id)
from t2000
order by to_number(object_id)) A)
where rn <= 10;
--通过执行计划,看分页排序的效率
--以下写法都保证了页面分页记录不重复
--第一种
SQL> SELECT *
2 FROM (SELECT A.*, ROWNUM RN
3 FROM (SELECT TO_NUMBER(OBJECT_ID)
4 FROM T2000
5 ORDER BY TO_NUMBER(OBJECT_ID)) A
6 WHERE ROWNUM <= 10)
7 WHERE RN >= 2;
已选择9行。
执行计划
----------------------------------------------------------
Plan hash value: 1198689731
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 260 | | 1056 (1)| 00:00:13 |
|* 1 | VIEW | | 10 | 260 | | 1056 (1)| 00:00:13 |
|* 2 | COUNT STOPKEY | | | | | | |
| 3 | VIEW | | 109K| 1395K| | 1056 (1)| 00:00:13 |
|* 4 | SORT ORDER BY STOPKEY| | 109K| 1395K| 2168K| 1056 (1)| 00:00:13 |
| 5 | TABLE ACCESS FULL | T2000 | 109K| 1395K| | 528 (1)| 00:00:07 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("RN">=2)
2 - filter(ROWNUM<=10)
4 - filter(ROWNUM<=10)
--第二种
SQL> SELECT *
2 FROM (SELECT A.*, ROWNUM RN
3 FROM (SELECT TO_NUMBER(OBJECT_ID)
4 FROM T2000
5 ORDER BY TO_NUMBER(OBJECT_ID)) A)
6 WHERE RN BETWEEN 1 AND 10;
已选择10行。
执行计划
----------------------------------------------------------
Plan hash value: 1961136492
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 109K| 2791K| | 1056 (1)| 00:00:13 |
|* 1 | VIEW | | 109K| 2791K| | 1056 (1)| 00:00:13 |
| 2 | COUNT | | | | | | |
| 3 | VIEW | | 109K| 1395K| | 1056 (1)| 00:00:13 |
| 4 | SORT ORDER BY | | 109K| 1395K| 2168K| 1056 (1)| 00:00:13 |
| 5 | TABLE ACCESS FULL| T2000 | 109K| 1395K| | 528 (1)| 00:00:07 |
---------------------------------------------------------------------------------------
--排序列不唯一所带来的问题
如果用来排序的列不唯一,也就是存在值相等的行,可能会造成第一次在前10条返回记录中,
某行数据出现了,而第二次在11到第20条记录中,某行数据又出现了。一条数据重复出现两次,
就必然意味着有数据在两次查询中都不会出现。
其实造成这个问题的原因很简单,是由于排序列不唯一造成的。Oracle这里使用的排序算法不具有稳定性,
也就是说,对于键值相等的数据,这种算法完成排序后,不保证这些键值相等的数据保持排序前的顺序。
解决这个问题其实也很简单。有两种方法可以考虑。
1)在使用不唯一的字段排序时,后面跟一个唯一的字段。
一般在排序字段后面跟一个主键就可以了,如果表不存在主键,跟ROWID也可以。这种方法最简单,且对性能的影响最小。
2)另一种方法就是使用前面给出过多次的BETWEEN AND的方法。
这种方式由于采用表数据的全排序,每次只取全排序中的某一部分数据,因此不会出现上面提到的重复数据问题。
但是正是由于使用了全排序,而且ROWNUM信息无法推到查询内部,导致这种写法的执行效率很低
--以下为实验例子
DROP TABLE TT5;
CREATE TABLE TT5 AS SELECT * FROM DBA_OBJECTS WHERE ROWNUM <10;
INSERT INTO TT5 SELECT * FROM TT5;
INSERT INTO TT5 SELECT * FROM TT5;
INSERT INTO TT5 SELECT * FROM TT5;
COMMIT;
--共产生了72条数据
admin@ORCL> SELECT COUNT(*) FROM TT5;
COUNT(*)
----------
72
--查询是否有重复的ROWID,若有则代表重复
SELECT RD FROM
(
SELECT RD FROM
(SELECT ROWNUM RN,ROWID RD, TT.* FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME)TT WHERE ROWNUM <= 10) A
WHERE A.RN >= 1
UNION ALL
SELECT RD FROM
(SELECT ROWNUM RN,ROWID RD, TT.* FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME)TT WHERE ROWNUM <= 20) A
WHERE A.RN >= 11
UNION ALL
SELECT RD FROM
(SELECT ROWNUM RN,ROWID RD, TT.* FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME)TT WHERE ROWNUM <= 30) A
WHERE A.RN >= 21
UNION ALL
SELECT RD FROM
(SELECT ROWNUM RN,ROWID RD, TT.* FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME)TT WHERE ROWNUM <= 40) A
WHERE A.RN >= 31
UNION ALL
SELECT RD FROM
(SELECT ROWNUM RN,ROWID RD, TT.* FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME)TT WHERE ROWNUM <= 50) A
WHERE A.RN >= 41
UNION ALL
SELECT RD FROM
(SELECT ROWNUM RN,ROWID RD, TT.* FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME)TT WHERE ROWNUM <= 60) A
WHERE A.RN >= 51
UNION ALL
SELECT RD FROM
(SELECT ROWNUM RN,ROWID RD, TT.* FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME)TT WHERE ROWNUM <= 71) A
WHERE A.RN >= 61
) A GROUP BY RD HAVING COUNT(*)>1;
RD
------------------
AAAPB2AAEAAAFS1AAt
AAAPB2AAEAAAFS1AAu
AAAPB2AAEAAAFS1AAf
AAAPB2AAEAAAFS1AA1
AAAPB2AAEAAAFS1AAh
--下面的方式不会产生重复值,因为数据的取值顺序不会发生变化
SELECT RD
FROM (SELECT RD
FROM (SELECT ROWNUM RN, ROWID RD, TT.*
FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME) TT) A
WHERE A.RN BETWEEN 1 AND 10
UNION ALL
SELECT RD
FROM (SELECT ROWNUM RN, ROWID RD, TT.*
FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME) TT) A
WHERE A.RN BETWEEN 11 AND 20
UNION ALL
SELECT RD
FROM (SELECT ROWNUM RN, ROWID RD, TT.*
FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME) TT) A
WHERE A.RN BETWEEN 21 AND 30
UNION ALL
SELECT RD
FROM (SELECT ROWNUM RN, ROWID RD, TT.*
FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME) TT) A
WHERE A.RN BETWEEN 31 AND 40
UNION ALL
SELECT RD
FROM (SELECT ROWNUM RN, ROWID RD, TT.*
FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME) TT) A
WHERE A.RN BETWEEN 41 AND 50
UNION ALL
SELECT RD
FROM (SELECT ROWNUM RN, ROWID RD, TT.*
FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME) TT) A
WHERE A.RN BETWEEN 51 AND 60
UNION ALL
SELECT RD
FROM (SELECT ROWNUM RN, ROWID RD, TT.*
FROM (SELECT * FROM TT5 ORDER BY OBJECT_NAME) TT) A
WHERE A.RN BETWEEN 61 AND 70) A
GROUP BY RD
HAVING COUNT(*) > 1;