1、Oracle 中的三大分页方法

本人最近总结了一下 Oracle 中的分页写法,从纯粹的 SQL 写法上来看,所谓分页就是嵌套子查询,无非就是不同的分页方法嵌套的子查询层数不同而已。Oracle 中一共有三种分页写法,分别是:嵌套一层子查询的分析函数分页、嵌套两层子查询的 ROWNUM 分页和嵌套三层子查询的 ROWID 分页。

1.1、通过分析函数分页

按员工年龄排序,每页显示 3 个员工,取第 1 页的数据。只嵌套一层子查询,写法简洁,容易理解,但一般没人用这种方法。只需要在子查询中的分析函数内部排序即可实现排序功能。

  1. SELECT t2.staff_name,t2.birthday FROM(
  2. SELECT t1.staff_name,t1.birthday,ROW_NUMBER() OVER(ORDER BY t1.birthday) rn
  3. FROM demo.t_staff t1
  4. ) t2 WHERE t2.rn >= ((1-1)*3+1) AND t2.rn <= (1*3);

1.2、通过 ROWNUM 分页

按员工年龄排序,每页显示 3 个员工,取第 1 页的数据。嵌套两层子查询,写法比较灵活,一般都是用这种方法。只需要在子查询内部排序即可实现排序功能。

  1. SELECT t3.staff_name,t3.birthday FROM(
  2. SELECT t2.*,ROWNUM rn FROM(
  3. SELECT t1.staff_name,t1.birthday FROM demo.t_staff t1 ORDER BY t1.birthday
  4. ) t2 WHERE ROWNUM <= (1*3)
  5. ) t3 WHERE t3.rn >= ((1-1)*3+1);

通过 ROWNUM 分页的一种变通写法(相对来说更好理解):

  1. SELECT t3.staff_name,t3.birthday FROM(
  2. SELECT t2.*,ROWNUM rn FROM(
  3. SELECT t1.staff_name,t1.birthday FROM demo.t_staff t1 ORDER BY t1.birthday
  4. ) t2
  5. ) t3 WHERE t3.rn >= ((1-1)*3+1) AND t3.rn <= (1*3);

1.3、通过 ROWID 分页

按员工年龄排序,每页显示 3 个员工,取第 1 页的数据。写法复杂,不太灵活,不易理解,很少有人用这种方法。必须在最内层子查询和最外层查询中都排序才可实现排序功能。

  1. SELECT t4.staff_name,t4.birthday
  2. FROM demo.t_staff t4
  3. WHERE t4.ROWID IN(
  4. SELECT t3.rid FROM(
  5. SELECT t2.rid,ROWNUM rn FROM(
  6. SELECT t1.ROWID rid FROM demo.t_staff t1 ORDER BY t1.birthday
  7. ) t2 WHERE ROWNUM <= (1*3)
  8. ) t3 WHERE t3.rn >= ((1-1)*3+1)
  9. ) ORDER BY t4.birthday;

2、Oracle 分页解决方案浅析

Oracle 中的三大分页方法应用最广泛的还是第二种,也就是基于 ROWNUM 的分页方法。由于实现分页的语法是固定的,所以一般项目中都是会提供一个公用的分页模版方法,然后其它需要分页的业务方法再调用这个方法来完成分页功能的。

分页的实现过程就是拼接 SQL 语句的过程,但选择在那个地方来完成拼接也是有讲究的。一般来说在服务端拼接是一个比较好的选择,这种方案主要好处就是灵活、简单、易维护。另一种比较常见的做法是通过存储过程来分页,然后在服务端调用存储过程,这种方案理论上分页效率比较高,但实现过程相对复杂,也没有纯服务端代码那么好维护。

2.1、纯后端代码完成分页

纯后端代码完成分页在定义、调用、性能、理解、维护等方面有不少小的技巧值得推敲。前几天我结合自己这些年来的分页经验和一个在公司干了十多年的技术专家交流了这个问题,最终我们一致认为还是传递整个内层子查询的方式最好(主要是可以规避掉一大堆小的坑)。拼接格式如下:

  1. SELECT t3.* FROM(
  2. SELECT t2.*,ROWNUM rn FROM(
  3. :subquery
  4. ) t2 WHERE ROWNUM <= (:pageIndex*:pageSize)
  5. ) t3 WHERE t3.rn >= ((:pageIndex-1)*:pageSize+1)

我们以前都有尝试过将子查询分拆成多个部分,然后分别传递的方式,不过一旦项目深入之后问题总比想象的要多得多。譬如参数过多导致调用难度增加,为了实现分页不得不将写好的整条语句拆成几个部分多余浪费时间,出问题时调试的复杂度也增加了,多表分页也相对难以处理,经验不足的程序员常常没耐心看懂现有代码进而又捏造了一个所谓的改进版(事实上这种情况还很多)……

不过即便是整个子查询传进来,也仍然会有不同的处理方式。譬如我上文提到的那个专家说他们就曾尝试过把传递进来子查询切分成多个部分再重新组合,但后来发现复杂的子查询极难写对,徒增了团队里新人的挫败感……

外层查询中的那个星号是比较关键的一点,尽管我们都知道查询中出现星号往往是不好的,但分页时依然拘泥这一点的话,必然会到导致复杂的拼接。复杂的拼接往往不好写,调用时也容易出错,时不时还得回头去看内部的实现再推导出该如何调用,这个过程显然是比较浪费时间的。

2.2、通过存储过程来分页

我本人大部分时候还是通过存储过程来实现分页的,不过对很多人来说写存储过程甚至调用存储过程都是比较难的,我觉得主要原因还是因为相关知识点不熟、写的少。下面列出了写分页存储过程和调用存储过程的相关参考连接:

下面是一个调用 Oracle 分页存储过程的 C# 方法:

  1. /// <summary>
  2. /// 调用存储过程,执行分页
  3. /// </summary>
  4. /// <param name="tableName">表名</param>
  5. /// <param name="queryFields">查询(字段)列表</param>
  6. /// <param name="queryWhere">查询条件</param>
  7. /// <param name="orderBy">排序子句</param>
  8. /// <param name="pageIndex">页索引(页码)</param>
  9. /// <param name="pageSize">页大小(每页数据条数)</param>
  10. /// <param name="pageCount">总页数</param>
  11. /// <param name="rowCount">总行数</param>
  12. /// <param name="resultSet">结果集</param>
  13. public void ExecutePaging(
  14. string tableName, string queryFields, string queryWhere, string orderBy,
  15. int pageIndex, int pageSize,
  16. ref int pageCount, ref int rowCount, ref DataTable resultSet)
  17. {
  18. OracleParameter[] ps = {
  19. new OracleParameter(":tableName", OracleDbType.Varchar2, 1000),
  20. new OracleParameter(":queryFields", OracleDbType.Varchar2, 1000),
  21. new OracleParameter(":queryWhere", OracleDbType.Varchar2, 2000),
  22. new OracleParameter(":orderBy", OracleDbType.Varchar2, 200),
  23. new OracleParameter(":pageIndex", OracleDbType.Int32),
  24. new OracleParameter(":pageSize", OracleDbType.Int32),
  25. new OracleParameter(":pageCount", OracleDbType.Int32),
  26. new OracleParameter(":rowCount", OracleDbType.Int32),
  27. new OracleParameter(":resultSet", OracleDbType.RefCursor)
  28. };
  29. ps[0].Value = tableName;
  30. ps[1].Value = queryFields;
  31. ps[2].Value = queryWhere;
  32. ps[3].Value = orderBy;
  33. ps[4].Value = pageIndex;
  34. ps[5].Value = pageSize;
  35. ps[6].Direction = ParameterDirection.Output;
  36. ps[7].Direction = ParameterDirection.Output;
  37. ps[8].Direction = ParameterDirection.Output;
  38. resultSet = OracleHelper.ProcQuery("sp_dynamic_paging", ps); // 调用存储过程
  39. pageCount = Verifier.VerifyInt(ps[6].Value);
  40. rowCount = Verifier.VerifyInt(ps[7].Value);
  41. }

2.3、两个通用的分页存储过程

下面这个存储过程是从我曾负责过的一个项目中抽取出来的,也是我第一次尝试写存储过程分页,100%原创,中间改版过几次,为方便阅读注释内容已被我去掉,现在的这个版本中的i_queryFields参数是不接受星号的:

  1. CREATE OR REPLACE PROCEDURE sp_paging(
  2. i_tableName VARCHAR2, -- 表名
  3. i_queryFields VARCHAR2, -- 查询(字段)列表
  4. i_queryWhere VARCHAR2, -- 查询条件
  5. i_orderBy VARCHAR2, -- 排序子句
  6. i_pageIndex NUMBER, -- 当前页索引
  7. i_pageSize NUMBER, -- 页大小
  8. o_rowCount OUT NUMBER, -- 总行数
  9. o_pageCount OUT NUMBER, -- 总页数
  10. o_resultSet OUT SYS_REFCURSOR -- 结果集
  11. ) IS
  12. v_count_sql VARCHAR2(2000);
  13. v_select_sql VARCHAR2(4000);
  14. BEGIN
  15. -- 拼接查询总行数的语句
  16. v_count_sql := 'SELECT COUNT(1) FROM '||i_tableName;
  17. -- 拼接查询条件
  18. IF i_queryWhere IS NOT NULL THEN
  19. v_count_sql := v_count_sql||' WHERE 1=1 '||i_queryWhere;
  20. END IF;
  21. -- 计算总行数
  22. EXECUTE IMMEDIATE v_count_sql INTO o_rowCount;
  23. --DBMS_OUTPUT.PUT_LINE(v_count_sql||';');
  24. -- 计算总页数(CEIL 向上取整)
  25. o_pageCount := CEIL(o_rowCount / i_pageSize);
  26. -- 如果有记录,且当前页索引合法,则继续查询
  27. IF o_rowCount >= 1 AND i_pageIndex >= 1 AND i_pageIndex <= o_pageCount THEN
  28. -- 当记录总数小于或等于页大小时,查询所有记录
  29. IF o_rowCount <= i_pageSize THEN
  30. v_select_sql := 'SELECT '||i_queryFields||' FROM('||i_tableName||')';
  31. IF i_queryWhere IS NOT NULL THEN
  32. v_select_sql := v_select_sql||' WHERE 1=1 '||i_queryWhere;
  33. END IF;
  34. IF i_orderBy IS NOT NULL THEN
  35. v_select_sql := v_select_sql||' order by '||i_orderBy;
  36. END IF;
  37. -- 查询第一页
  38. ELSIF i_pageIndex = 1 THEN
  39. v_select_sql := 'SELECT '||i_queryFields||' FROM('||i_tableName||')';
  40. IF i_queryWhere IS NOT NULL THEN
  41. v_select_sql := v_select_sql||' WHERE 1=1 '||i_queryWhere;
  42. END IF;
  43. IF i_orderBy IS NOT NULL THEN
  44. v_select_sql := v_select_sql||' order by '||i_orderBy;
  45. END IF;
  46. v_select_sql := 'SELECT '||i_queryFields||' FROM('||v_select_sql||') WHERE ROWNUM<='||i_pageSize;
  47. -- 查询指定页
  48. ELSE
  49. v_select_sql := 'SELECT '||i_queryFields||' FROM('||i_tableName||')';
  50. IF i_queryWhere IS NOT NULL THEN
  51. v_select_sql := v_select_sql||' WHERE 1=1 '||i_queryWhere;
  52. END IF;
  53. IF i_orderBy IS NOT NULL THEN
  54. v_select_sql := v_select_sql||' order by '||i_orderBy;
  55. END IF;
  56. v_select_sql := 'SELECT '||i_queryFields||' FROM(SELECT ROWNUM rn,'||i_queryFields||' FROM('||v_select_sql
  57. ||')) WHERE rn>'||((i_pageIndex-1)*i_pageSize)||' AND rn<='||(i_pageIndex*i_pageSize);
  58. END IF;
  59. --DBMS_OUTPUT.PUT_LINE(v_select_sql||';');
  60. OPEN o_resultSet FOR v_select_sql;
  61. ELSE
  62. OPEN o_resultSet FOR 'SELECT * FROM '||i_tableName||' WHERE 1!=1';
  63. END IF;
  64. END;

下面这个存储过程摘自《剑破冰山——Oracle开发艺术》一书,有删改:

  1. CREATE OR REPLACE PROCEDURE sp_dynamic_paging(
  2. i_tableName VARCHAR2, -- 表名
  3. i_queryFields VARCHAR2, -- 查询列表
  4. i_queryWhere VARCHAR2, -- 查询条件
  5. i_orderBy VARCHAR2, -- 排序
  6. i_pageSize NUMBER, -- 页大小
  7. i_pageIndex NUMBER, -- 页索引
  8. o_rowCount OUT NUMBER, -- 返回总条数
  9. o_pageCount OUT NUMBER, -- 返回总页数
  10. o_resultSet OUT SYS_REFCURSOR -- 返回分页结果集
  11. )
  12. IS
  13. v_startRows INT; -- 开始行
  14. v_endRows INT; -- 结束行
  15. v_pageSize INT;
  16. v_pageIndex INT;
  17. v_queryFields VARCHAR2(2000);
  18. v_queryWhere VARCHAR2(2000);
  19. v_orderBy VARCHAR2(200);
  20. v_count_sql VARCHAR2(1000); -- 接收统计数据条数的 SQL 语句
  21. v_select_sql VARCHAR2(4000); -- 接收查询分页数据的 SQL 语句
  22. BEGIN
  23. -- 如果没有表名,则直接返回异常消息
  24. -- 如果没有字段,则表示查询全部字段
  25. IF i_queryFields IS NOT NULL THEN
  26. v_queryFields:=i_queryFields;
  27. ELSE
  28. v_queryFields:=' * ';
  29. END IF;
  30. -- 可以没有查询条件
  31. IF i_queryWhere IS NOT NULL THEN
  32. v_queryWhere := ' WHERE 1=1 AND'||i_queryWhere||' ';
  33. ELSE
  34. v_queryWhere := ' WHERE 1=1 ';
  35. END IF;
  36. -- 可以没有排序条件
  37. IF i_orderBy IS NULL THEN
  38. v_orderBy:=' ';
  39. ELSE
  40. v_orderBy:='ORDER BY '||i_orderBy;
  41. END IF;
  42. -- 如果未指定查询页,则默认为首页
  43. IF i_pageIndex IS NULL OR i_pageIndex<1 THEN
  44. v_pageIndex:=1;
  45. ELSE
  46. v_pageIndex:=i_pageIndex;
  47. END IF;
  48. -- 如果未指定每页记录数,则默认为 10
  49. IF i_pageSize IS NULL THEN
  50. v_pageSize:=10;
  51. ELSE
  52. v_pageSize:=i_pageSize;
  53. END IF;
  54. -- 构造查询总条数的语句
  55. v_count_sql:='SELECT COUNT(1) FROM '||i_tableName||v_queryWhere;
  56. --DBMS_OUTPUT.PUT_LINE(v_count_sql||';');
  57. -- 构造查询数据的语句
  58. v_select_sql:='(SELECT '||v_queryFields||' FROM '||i_tableName||v_queryWhere||v_orderBy||') t2';
  59. -- 查询总条数
  60. EXECUTE IMMEDIATE v_count_sql INTO o_rowCount;
  61. -- 得到总页数
  62. IF MOD(o_rowCount,i_pageSize)=0 THEN
  63. o_pageCount:=o_rowCount/i_pageSize;
  64. ELSE
  65. o_pageCount:=FLOOR(o_rowCount/i_pageSize)+1;
  66. END IF;
  67. -- 如果当前页大于最大页数,则取最大页数
  68. IF i_pageIndex>o_pageCount THEN
  69. v_pageIndex:=o_pageCount;
  70. END IF;
  71. -- 设置开始结束的记录数
  72. v_startRows:=(v_pageIndex-1)*v_pageSize+1;
  73. v_endRows:=v_pageIndex*v_pageSize;
  74. -- 进行完成的动态 SQL 语句拼接
  75. v_select_sql:='SELECT t3.* FROM'||'(SELECT t2.*,ROWNUM rn FROM'||v_select_sql
  76. ||' WHERE ROWNUM<='||v_endRows||') t3 WHERE t3.rn>='||v_startRows;
  77. --DBMS_OUTPUT.PUT_LINE(v_select_sql||';');
  78. OPEN o_resultSet FOR v_select_sql;
  79. END;

下面这段 PL/SQL 代码用于测试上面两个存储过程:

  1. DECLARE
  2. v_tableName VARCHAR2(1000);
  3. v_queryFields VARCHAR2(1000);
  4. v_queryWhere VARCHAR2(1000);
  5. v_orderBy VARCHAR2(200);
  6. v_pageSize INT := 3;
  7. v_pageIndex INT;
  8. v_rowCount INT := 0;
  9. v_pageCount INT := 0;
  10. v_resultSet SYS_REFCURSOR;
  11. BEGIN
  12. v_tableName:='t_staff';
  13. v_queryFields:='staff_name,birthday';
  14. v_orderBy:='birthday';
  15. v_pageIndex:=1;
  16. sp_dynamic_paging(
  17. i_tableName => v_tableName,
  18. i_queryFields => v_queryFields,
  19. i_queryWhere => v_queryWhere,
  20. i_orderBy => v_orderBy,
  21. i_pageSize => v_pageSize,
  22. i_pageIndex => v_pageIndex,
  23. o_rowCount => v_rowCount,
  24. o_pageCount => v_pageCount,
  25. o_resultSet => v_resultSet
  26. );
  27. END;

3、总结

本文主要讲述了 Oracle 中的三种分页方法和常见的两种分页解决方案,并给出了两个通用的分页存储过程源码。主要是对我个人所掌握的 Oracle 分页方法和技术做了个全面的回顾。

本文链接http://www.cnblogs.com/hanzongze/p/oracle-paging-1.html

版权声明:本文为博客园博主 韩宗泽 原创,作者保留署名权!欢迎通过转载、演绎或其它传播方式来使用本文,但必须在明显位置给出作者署名和本文链接!个人博客,能力有限,若有不当之处,敬请批评指正,谢谢!

Oracle 分页方法研究的更多相关文章

  1. oracle 分页方法

    我分享两种: 1.用rownum select * from (select p.* , rownum rn  from t_premium p where rn<= page * 10) a ...

  2. Oracle、SQL Server、MySQL分页方法

    测试用例:查询TEST_TABLE表中TEST_COLUMN列的第10-20条数据 1,Oracle分页方法 SELECT A.* FROM ( SELECT ROWNUM ROWNO, B.* FR ...

  3. 【SQL】Oracle分页查询的三种方法

    [SQL]Oracle分页查询的三种方法 采用伪列 rownum 查询前10条记录 ? 1 2 3 4 5 6 7 8 9 10 11 [sql] select * from t_user t whe ...

  4. java oracle的2种分页方法

    java oracle的2种分页方法 一物理分页: <!-- 分页查询所有的博客信息 --> <select id="findBlogs" resultType= ...

  5. mysql和oracle 分页查询(转)

    最近简单的对oracle,mysql,sqlserver2005的数据分页查询作了研究,把各自的查询的语句贴出来供大家学习..... (一). mysql的分页查询 mysql的分页查询是最简单的,借 ...

  6. Oracle 分页原理

    oracle rownum 及分页处理的使用方法 在实际应用中我们经常碰到这样的问题,比如一张表比较大,我们只要其中的查看其中的前几条数据,或者对分页处理数据.在这些情况下我们都需要用到rownum. ...

  7. Oracle分页查询语句的写法(转)

    Oracle分页查询语句的写法(转)   分页查询是我们在使用数据库系统时经常要使用到的,下文对Oracle数据库系统中的分页查询语句作了详细的介绍,供您参考. Oracle分页查询语句使我们最常用的 ...

  8. oracle分页与rownum

    Oracle分页(limit方式的运用) Oracle不支持类似于 MySQL 中的 limit. 但你还是可以rownum来限制返回的结果集的行数. 第一种 select * from a_matr ...

  9. oracle分页查询及原理分析(总结)

    oracle分页查询及原理分析(总结) oracle分页查询是开发总为常用的语句之一,一般情况下公司框架会提供只需套用,对于增删改查而言,查是其中最为关键也是最为难的一块,其中就有使用率最高的分页查询 ...

随机推荐

  1. 分布式开放消息系统(RocketMQ)的原理与实践(转)

    转自:http://www.jianshu.com/p/453c6e7ff81c 分布式消息系统作为实现分布式系统可扩展.可伸缩性的关键组件,需要具有高吞吐量.高可用等特点.而谈到消息系统的设计,就回 ...

  2. 从点击到呈现 — 详解一次HTTP请求

    一般来说,很多的参考资料上面都会说,http 是一个基于请求/响应的工作模式,然后画出一张浏览器和服务器的 b/s 结构图,再画上两个箭头,表示请求和响应,应该说这么解释是易懂的,一般也是够清楚的,但 ...

  3. OC-不可变数组NSArray

  4. redis安装学习

    Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理.它支持字符串.哈希表.列表.集合.有序集合,位图,hyperloglogs等数据类型.内置复制.Lu ...

  5. Response.AddHeader

    Response.AddHeader使用实例 1.文件下载,指定默认名 Response.AddHeader("content-type","application/x- ...

  6. ES6入门2

    for-of循环: 新语法如下: for (var value of myArray) { console.log(value); } 它的优点是: 这是目前遍历数组最简洁和直接的语法: 它避免了fo ...

  7. cassandra.yaml 配置 (非原创,侵删)

    Copy from: http://blog.csdn.net/y_h_t/article/details/11917531 Cassandra中所有的运行配置都是在配置文件cassandra.yam ...

  8. iOS安全攻防之代码混淆

    iOS 代码安全之代码混淆实践: 前言: 在8月份的时候写了个关于 class-dump 反编译的文章(使用 Class-dump 反编译),利用 class-dump 工具可以反编译出工程的头文件, ...

  9. 时间序列分析算法【R详解】

    简介 在商业应用中,时间是最重要的因素,能够提升成功率.然而绝大多数公司很难跟上时间的脚步.但是随着技术的发展,出现了很多有效的方法,能够让我们预测未来.不要担心,本文并不会讨论时间机器,讨论的都是很 ...

  10. JavaSE教程-01初识Java-思维导图

    图片看不清楚时: 1)可以将图片另存为图片,保存在本地来查看 2)右击在新标签中打开放大查看. 分解: 1.计算机基本概念的普及 硬件 cpu.内存.硬盘等 软件 系统级软件 Windows.Linu ...