Oracle 分页方法研究

  • 1、Oracle 中的三大分页方法

    • 1.1、通过分析函数分页
    • 1.2、通过 ROWNUM 分页
    • 1.3、通过 ROWID 分页
  • 2、Oracle 分页解决方案浅析
    • 2.1、纯后端代码完成分页
    • 2.2、通过存储过程来分页
    • 2.3、两个通用的分页存储过程
  • 3、总结

1、Oracle 中的三大分页方法

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

1.1、通过分析函数分页

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

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

1.2、通过 ROWNUM 分页

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

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

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

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

1.3、通过 ROWID 分页

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

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

2、Oracle 分页解决方案浅析

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

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

2.1、纯后端代码完成分页

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

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

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

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

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

2.2、通过存储过程来分页

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

  • 《.Net程序员学用Oracle系列(7):视图、函数、存储过程、包》:存储过程
  • 《.Net程序员学用Oracle系列(26):PLSQL 之类型、变量和结构》:变量
  • 《.Net程序员学用Oracle系列(27):PLSQL 之游标、异常和事务》:游标
  • 《.Net程序员学用Oracle系列(16):访问数据库(ODP.NET)》:甲骨文提供的驱动

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

/// <summary> /// 调用存储过程,执行分页 /// </summary> /// <param name="tableName">表名</param> /// <param name="queryFields">查询(字段)列表</param> /// <param name="queryWhere">查询条件</param> /// <param name="orderBy">排序子句</param> /// <param name="pageIndex">页索引(页码)</param> /// <param name="pageSize">页大小(每页数据条数)</param> /// <param name="pageCount">总页数</param> /// <param name="rowCount">总行数</param> /// <param name="resultSet">结果集</param> public void ExecutePaging( string tableName, string queryFields, string queryWhere, string orderBy, int pageIndex, int pageSize, ref int pageCount, ref int rowCount, ref DataTable resultSet) {OracleParameter[] ps = { new OracleParameter(":tableName", OracleDbType.Varchar2, 1000), new OracleParameter(":queryFields", OracleDbType.Varchar2, 1000), new OracleParameter(":queryWhere", OracleDbType.Varchar2, 2000), new OracleParameter(":orderBy", OracleDbType.Varchar2, 200), new OracleParameter(":pageIndex", OracleDbType.Int32), new OracleParameter(":pageSize", OracleDbType.Int32), new OracleParameter(":pageCount", OracleDbType.Int32), new OracleParameter(":rowCount", OracleDbType.Int32), new OracleParameter(":resultSet", OracleDbType.RefCursor)};ps[0].Value = tableName;ps[1].Value = queryFields;ps[2].Value = queryWhere;ps[3].Value = orderBy;ps[4].Value = pageIndex;ps[5].Value = pageSize;ps[6].Direction = ParameterDirection.Output;ps[7].Direction = ParameterDirection.Output;ps[8].Direction = ParameterDirection.Output;resultSet = OracleHelper.ProcQuery("sp_dynamic_paging", ps); // 调用存储过程 pageCount = Verifier.VerifyInt(ps[6].Value);rowCount = Verifier.VerifyInt(ps[7].Value);
}

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

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

CREATE OR REPLACE PROCEDURE sp_paging(i_tableName VARCHAR2, -- 表名i_queryFields VARCHAR2, -- 查询(字段)列表i_queryWhere VARCHAR2, -- 查询条件i_orderBy VARCHAR2, -- 排序子句i_pageIndex NUMBER, -- 当前页索引i_pageSize NUMBER, -- 页大小o_rowCount OUT NUMBER, -- 总行数o_pageCount OUT NUMBER, -- 总页数o_resultSet OUT SYS_REFCURSOR -- 结果集
) ISv_count_sql VARCHAR2(2000);v_select_sql VARCHAR2(4000); BEGIN -- 拼接查询总行数的语句v_count_sql := 'SELECT COUNT(1) FROM '||i_tableName;-- 拼接查询条件 IF i_queryWhere IS NOT NULL THENv_count_sql := v_count_sql||' WHERE 1=1 '||i_queryWhere; END IF;-- 计算总行数EXECUTE IMMEDIATE v_count_sql INTO o_rowCount;--DBMS_OUTPUT.PUT_LINE(v_count_sql||';');-- 计算总页数(CEIL 向上取整)o_pageCount := CEIL(o_rowCount / i_pageSize);-- 如果有记录,且当前页索引合法,则继续查询 IF o_rowCount >= 1 AND i_pageIndex >= 1 AND i_pageIndex <= o_pageCount THEN-- 当记录总数小于或等于页大小时,查询所有记录 IF o_rowCount <= i_pageSize THENv_select_sql := 'SELECT '||i_queryFields||' FROM('||i_tableName||')'; IF i_queryWhere IS NOT NULL THENv_select_sql := v_select_sql||' WHERE 1=1 '||i_queryWhere; END IF; IF i_orderBy IS NOT NULL THENv_select_sql := v_select_sql||' order by '||i_orderBy; END IF;-- 查询第一页ELSIF i_pageIndex = 1 THENv_select_sql := 'SELECT '||i_queryFields||' FROM('||i_tableName||')'; IF i_queryWhere IS NOT NULL THENv_select_sql := v_select_sql||' WHERE 1=1 '||i_queryWhere; END IF; IF i_orderBy IS NOT NULL THENv_select_sql := v_select_sql||' order by '||i_orderBy; END IF;v_select_sql := 'SELECT '||i_queryFields||' FROM('||v_select_sql||') WHERE ROWNUM<='||i_pageSize;-- 查询指定页 ELSE v_select_sql := 'SELECT '||i_queryFields||' FROM('||i_tableName||')'; IF i_queryWhere IS NOT NULL THENv_select_sql := v_select_sql||' WHERE 1=1 '||i_queryWhere; END IF; IF i_orderBy IS NOT NULL THENv_select_sql := v_select_sql||' order by '||i_orderBy; END IF;v_select_sql := 'SELECT '||i_queryFields||' FROM(SELECT ROWNUM rn,'||i_queryFields||' FROM('||v_select_sql||')) WHERE rn>'||((i_pageIndex-1)*i_pageSize)||' AND rn<='||(i_pageIndex*i_pageSize); END IF;--DBMS_OUTPUT.PUT_LINE(v_select_sql||';');OPEN o_resultSet FOR v_select_sql; ELSE OPEN o_resultSet FOR 'SELECT * FROM '||i_tableName||' WHERE 1!=1'; END IF; END;

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

CREATE OR REPLACE PROCEDURE sp_dynamic_paging(i_tableName VARCHAR2, -- 表名i_queryFields VARCHAR2, -- 查询列表i_queryWhere VARCHAR2, -- 查询条件i_orderBy VARCHAR2, -- 排序i_pageSize NUMBER, -- 页大小i_pageIndex NUMBER, -- 页索引o_rowCount OUT NUMBER, -- 返回总条数o_pageCount OUT NUMBER, -- 返回总页数o_resultSet OUT SYS_REFCURSOR -- 返回分页结果集
) IS v_startRows INT; -- 开始行v_endRows INT; -- 结束行 v_pageSize INT;v_pageIndex INT;v_queryFields VARCHAR2(2000);v_queryWhere VARCHAR2(2000);v_orderBy VARCHAR2(200);v_count_sql VARCHAR2(1000); -- 接收统计数据条数的 SQL 语句v_select_sql VARCHAR2(4000); -- 接收查询分页数据的 SQL 语句
BEGIN-- 如果没有表名,则直接返回异常消息-- 如果没有字段,则表示查询全部字段 IF i_queryFields IS NOT NULL THEN v_queryFields:=i_queryFields; ELSE v_queryFields:=' * '; END IF;-- 可以没有查询条件 IF i_queryWhere IS NOT NULL THEN v_queryWhere := ' WHERE 1=1 AND'||i_queryWhere||' '; ELSE v_queryWhere := ' WHERE 1=1 '; END IF;-- 可以没有排序条件 IF i_orderBy IS NULL THEN v_orderBy:=' '; ELSE v_orderBy:='ORDER BY '||i_orderBy; END IF;-- 如果未指定查询页,则默认为首页 IF i_pageIndex IS NULL OR i_pageIndex<1 THEN v_pageIndex:=1; ELSE v_pageIndex:=i_pageIndex; END IF;-- 如果未指定每页记录数,则默认为 10 条 IF i_pageSize IS NULL THEN v_pageSize:=10; ELSE v_pageSize:=i_pageSize; END IF;-- 构造查询总条数的语句v_count_sql:='SELECT COUNT(1) FROM '||i_tableName||v_queryWhere; --DBMS_OUTPUT.PUT_LINE(v_count_sql||';'); -- 构造查询数据的语句v_select_sql:='(SELECT '||v_queryFields||' FROM '||i_tableName||v_queryWhere||v_orderBy||') t2'; -- 查询总条数EXECUTE IMMEDIATE v_count_sql INTO o_rowCount;-- 得到总页数 IF MOD(o_rowCount,i_pageSize)=0 THEN o_pageCount:=o_rowCount/i_pageSize; ELSE o_pageCount:=FLOOR(o_rowCount/i_pageSize)+1; END IF;-- 如果当前页大于最大页数,则取最大页数 IF i_pageIndex>o_pageCount THEN v_pageIndex:=o_pageCount; END IF;-- 设置开始结束的记录数v_startRows:=(v_pageIndex-1)*v_pageSize+1;v_endRows:=v_pageIndex*v_pageSize;-- 进行完成的动态 SQL 语句拼接v_select_sql:='SELECT t3.* FROM'||'(SELECT t2.*,ROWNUM rn FROM'||v_select_sql ||' WHERE ROWNUM<='||v_endRows||') t3 WHERE t3.rn>='||v_startRows; --DBMS_OUTPUT.PUT_LINE(v_select_sql||';'); OPEN o_resultSet FOR v_select_sql; END;

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

DECLARE v_tableName VARCHAR2(1000);v_queryFields VARCHAR2(1000);v_queryWhere VARCHAR2(1000);v_orderBy VARCHAR2(200);v_pageSize INT := 3;v_pageIndex INT;v_rowCount INT := 0;v_pageCount INT := 0;v_resultSet SYS_REFCURSOR;
BEGINv_tableName:='t_staff';v_queryFields:='staff_name,birthday';v_orderBy:='birthday';v_pageIndex:=1;sp_dynamic_paging(i_tableName => v_tableName,i_queryFields => v_queryFields,i_queryWhere => v_queryWhere,i_orderBy => v_orderBy,i_pageSize => v_pageSize,i_pageIndex => v_pageIndex,o_rowCount => v_rowCount,o_pageCount => v_pageCount,o_resultSet => v_resultSet);
END;

3、总结

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

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



About Me

.............................................................................................................................................

● 本文整理自网络

● 本文在itpub(http://blog.itpub.net/26736162/abstract/1/)、博客园(http://www.cnblogs.com/lhrbest)和个人微信公众号(xiaomaimiaolhr)上有同步更新

● 本文itpub地址:http://blog.itpub.net/26736162/abstract/1/

● 本文博客园地址:http://www.cnblogs.com/lhrbest

● 本文pdf版、个人简介及小麦苗云盘地址:http://blog.itpub.net/26736162/viewspace-1624453/

● 数据库笔试面试题库及解答:http://blog.itpub.net/26736162/viewspace-2134706/

● DBA宝典今日头条号地址:http://www.toutiao.com/c/user/6401772890/#mid=1564638659405826

.............................................................................................................................................

● QQ群号:230161599(满)、618766405

● 微信群:可加我微信,我拉大家进群,非诚勿扰

● 联系我请加QQ好友(646634621),注明添加缘由

● 于 2017-08-01 09:00 ~ 2017-08-31 22:00 在魔都完成

● 文章内容来源于小麦苗的学习笔记,部分整理自网络,若有侵权或不当之处还请谅解

● 版权所有,欢迎分享本文,转载请保留出处

.............................................................................................................................................

● 小麦苗的微店:https://weidian.com/s/793741433?wfr=c&ifr=shopdetail

● 小麦苗出版的数据库类丛书:http://blog.itpub.net/26736162/viewspace-2142121/

.............................................................................................................................................

使用微信客户端扫描下面的二维码来关注小麦苗的微信公众号(xiaomaimiaolhr)及QQ群(DBA宝典),学习最实用的数据库技术。

小麦苗的微信公众号      小麦苗的DBA宝典QQ群1     小麦苗的DBA宝典QQ群2        小麦苗的微店

.............................................................................................................................................

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26736162/viewspace-2143110/,如需转载,请注明出处,否则将追究法律责任。

Oracle 分页方法研究相关推荐

  1. asp oracle 分页显示,asp + oracle 分页方法(不用存储过程)

    asp 中查询 oracle数据库 的分页程序,没有用存储过程,效率还可以. 代码如下: ''''  塞北的雪  分页利器(oracle)  不用存储过程   -------------------- ...

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

    测试用例:查询TEST_TABLE表中TEST_COLUMN列的第10-20条数据 1,Oracle分页方法 [sql] view plain copy   SELECT A.* FROM ( SEL ...

  3. oracle分页的方法,Oracle数据库分页的集中方法(三种方法)

    在 做项目中用到了分页,下面说一下oracle分页的方法; 采用伪列 rownum 查询前10条记录 [sql] select * from t_user t where ROWNUM <10; ...

  4. java oracle分页查询语句_oracle分页查询语句,java得到分页查询语句的方法

    oracle分页查询语句 select * from ( select a.*, rownum rn from (select * from table_name) a where rownum &l ...

  5. java oracle的2种分页方法

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

  6. Oracle Spatial中上载GIS空间数据方法研究

    Oracle Spatial中上载GIS空间数据方法研究 作者:佚名    文章来源:博客中国    点击数:6873    更新时间:2006-8-24 摘要:采用Oracle Spatial 存储 ...

  7. oracle分页排序查询,Oracle分页查询中排序与效率问题解决方法详解

    本文将结合作者近日工作中,在ORACLE数据库分页查询时,遇到一个小问题,为大家讲解如何解决Oracle分页查询中排序与效率问题. 原始未分页查询Sql代码如下: select ROWNUM rn, ...

  8. JAVA中oracle分页语句,oracle分页查询语句,java得到分页查询语句的方法

    oracle分页查询语句 select * from ( select a.*, rownum rn from (select * from table_name) a where rownum &l ...

  9. Oracle分页的俩种方法

    由于业务场景需要,根据Oracle数据库,有俩种分页查询的方法(本实例主要提供方法). 第一种分页方法: select *   from (select rownum as rn, table_ali ...

  10. oracle分页的方法,oracle分页

    Oracle 的 oracle分页 oracle的分页一共有三种方式 方法一 根据rowid来分 SELECT * FROM EMP WHERE ROWID IN (SELECT RID FROM ( ...

最新文章

  1. linux tcp阻塞socket recv接收数据 未达到指定长度返回问题
  2. 每日一皮:在同一个项目上工作2年的样子...
  3. oracle客户端下载 win8.1,WINDOWS8.1安装ORACLE客户端及配置
  4. windows server 中,Tomcat9 配置
  5. UDO compare ABAP代码的实现
  6. 开发发布npm module包
  7. 从 ThreadLocal 到 AsyncLocal
  8. win10右键一直转圈_Win10总是自动更新?教你如何关掉自动更新
  9. Selenium2+python自动化35-获取元素属性
  10. Chatbot - NLP
  11. 如何访问嵌套母版页中的控件
  12. 中国建筑石材行业产销现状与投资策略分析报告2022-2028年
  13. Android 集成facebook 登录和分享
  14. Ubuntu安装Gcc时,显示“无法解析域名cn.archive.ubuntu.com”,如下方式可解决
  15. Mysql保存emoji表情
  16. 无法访问localhost与127.0.0.1/本地服务器的解决办法
  17. 江苏具有计算机博士点的大学排名,不愧为高教强省, 江苏27所大学拥有博士点, 有你母校么...
  18. nfc充值java_实测北京公交一卡通NFC手机充值
  19. 为云环境开发的 RADIUS 认证服务
  20. mysql查询有什么意义,数据库中的查询到底有什么作用?

热门文章

  1. 修改服务器ssh欢迎界面
  2. 武汉大花岭科目二考试说明
  3. iOS中 断点下载详解
  4. 怎么监控mysql数据变化_mysql数据库数据变化实时监控
  5. echarts标题(title)设置背景图片
  6. FPGA课设实验二:计数器设计与仿真
  7. 2019年高中(高考)数学数列解题技巧整理总结
  8. 【对比Java学Kotlin】代理
  9. Linux格式化逻辑卷的命令,Linux LVM逻辑卷管理
  10. 利用API爬取QQ音乐评论