Oracle 分页方法研究
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 -- 结果集
) IS
v_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 THEN
v_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 THEN
v_select_sql := 'SELECT '||i_queryFields||' FROM('||i_tableName||')';
IF i_queryWhere IS NOT NULL THEN
v_select_sql := v_select_sql||' WHERE 1=1 '||i_queryWhere;
END IF;
IF i_orderBy IS NOT NULL THEN
v_select_sql := v_select_sql||' order by '||i_orderBy;
END IF;
-- 查询第一页
ELSIF i_pageIndex = 1 THEN
v_select_sql := 'SELECT '||i_queryFields||' FROM('||i_tableName||')';
IF i_queryWhere IS NOT NULL THEN
v_select_sql := v_select_sql||' WHERE 1=1 '||i_queryWhere;
END IF;
IF i_orderBy IS NOT NULL THEN
v_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 THEN
v_select_sql := v_select_sql||' WHERE 1=1 '||i_queryWhere;
END IF;
IF i_orderBy IS NOT NULL THEN
v_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;
BEGIN
v_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
版权声明:本文为博客园博主 韩宗泽 原创,作者保留署名权!欢迎通过转载、演绎或其它传播方式来使用本文,但必须在明显位置给出作者署名和本文链接!个人博客,能力有限,若有不当之处,敬请批评指正,谢谢!
Oracle 分页方法研究的更多相关文章
- oracle 分页方法
我分享两种: 1.用rownum select * from (select p.* , rownum rn from t_premium p where rn<= page * 10) a ...
- Oracle、SQL Server、MySQL分页方法
测试用例:查询TEST_TABLE表中TEST_COLUMN列的第10-20条数据 1,Oracle分页方法 SELECT A.* FROM ( SELECT ROWNUM ROWNO, B.* FR ...
- 【SQL】Oracle分页查询的三种方法
[SQL]Oracle分页查询的三种方法 采用伪列 rownum 查询前10条记录 ? 1 2 3 4 5 6 7 8 9 10 11 [sql] select * from t_user t whe ...
- java oracle的2种分页方法
java oracle的2种分页方法 一物理分页: <!-- 分页查询所有的博客信息 --> <select id="findBlogs" resultType= ...
- mysql和oracle 分页查询(转)
最近简单的对oracle,mysql,sqlserver2005的数据分页查询作了研究,把各自的查询的语句贴出来供大家学习..... (一). mysql的分页查询 mysql的分页查询是最简单的,借 ...
- Oracle 分页原理
oracle rownum 及分页处理的使用方法 在实际应用中我们经常碰到这样的问题,比如一张表比较大,我们只要其中的查看其中的前几条数据,或者对分页处理数据.在这些情况下我们都需要用到rownum. ...
- Oracle分页查询语句的写法(转)
Oracle分页查询语句的写法(转) 分页查询是我们在使用数据库系统时经常要使用到的,下文对Oracle数据库系统中的分页查询语句作了详细的介绍,供您参考. Oracle分页查询语句使我们最常用的 ...
- oracle分页与rownum
Oracle分页(limit方式的运用) Oracle不支持类似于 MySQL 中的 limit. 但你还是可以rownum来限制返回的结果集的行数. 第一种 select * from a_matr ...
- oracle分页查询及原理分析(总结)
oracle分页查询及原理分析(总结) oracle分页查询是开发总为常用的语句之一,一般情况下公司框架会提供只需套用,对于增删改查而言,查是其中最为关键也是最为难的一块,其中就有使用率最高的分页查询 ...
随机推荐
- java web (j2ee)学习路线 —— 将青春交给命运
RESON TO DO JAVA WEB:1.JAVA WEB(企业级) 2.Android和iOS过于火爆并且不兼容 一.JAVA WEB开发需要的知识储备 1. 基本的网页设计语言:H ...
- Hive篇之安装
1,安装 hive的版本的选择,是选择内置的数据库保存元数据,还是用外部的mysql之类的数据库保存元数据,同时,如果使用外置的mysql,需要注意对mysql远程访问的配置. 再就是关于文件的配置了 ...
- [笔记]机器学习(Machine Learning) - 03.正则化(Regularization)
欠拟合(Underfitting)与过拟合(Overfitting) 上面两张图分别是回归问题和分类问题的欠拟合和过度拟合的例子.可以看到,如果使用直线(两组图的第一张)来拟合训,并不能很好地适应我们 ...
- [刷题]算法竞赛入门经典(第2版) 5-15/UVa12333 - Revenge of Fibonacci
题意:在前100000个Fibonacci(以下简称F)数字里,能否在这100000个F里找出以某些数字作为开头的F.要求找出下标最小的.没找到输出-1. 代码:(Accepted,0.250s) / ...
- VMTools安装
先启动CentOS并成功登陆如下图,发现底部提示,准备安装 2.选择虚拟机菜单栏--安装VMware tools 3.光驱目录中拷贝VMwareTools-10.0.5-3228253.tar.gz到 ...
- hive、impala集成ldap
1.概要 1.1 环境信息 hadoop:cdh5.10 os:centos6.7 user:root hive.impala已集成sentry 1.2 访问控制权限 这里通过使用openldap来控 ...
- Mybatis中的like查询
今天要做一个模糊查询 用的Mybatis 开始写的是: select id,bookName,author,publisher,donor,status,createDate,lastUpdate f ...
- web 直播&即时聊天------阿里云、融云
随着直播越来越火,所在公司也打算制作自己的直播,所以去了解了这方面,使用后发现还是有些问题需要记录的. 经过分析,制作直播应该是分为两块来做,即直播与实时评论.这里先去制作实时评论,等直播ok后,也会 ...
- Opencv在linux下安装
Opencv in Linux These steps have been tested for Ubuntu 10.04 but should work with other distros as ...
- nginx+tomcat集群负载均衡(实现session复制)
转自:http://talangniao.iteye.com/blog/341512 架构描述 前端一台nginx服务器做负载均衡器,后端放N台tomcat组成集群处理服务,通过nginx转发到后面( ...