数据库分页方法

80酷酷网    80kuku.com

  分页|数据|数据库方法一:

select top 10 b.* from (select top 20 主键字段,排序字段 from 表名 order by 排序字段 desc) a,表名 b where b.主键字段 = a.主键字段 order by a.排序字段

10 = 每页记录数

20 = (当前页 + 1) * 每页记录数

以上语句即可以实现分页,但是最后取出的结果排序是升序,如果需要结果集为降序(例如时间),则有两种方法可以处理

1.使用以下语句,但效率可能要降低一些

select * from 表名 b, (select top 10 主键字段,排序字段 from (select top 20 主键字段,排序字段 from 表名 order by 排序字段 desc) a order by 排序字段 ) c where b.主键字段 = c.主键字段 order by c.排序字段 desc

2.在ado里处理,将记录集游标移到最后,然后前移

''以下为asp范例

set rsTemp = Server.CreateObject("adodb.recordset")

rsTemp.Open 语句,conn,1,1

rsTemp.MoveLast

for i = 1 to rsTemp.RecordCount

 '取值....

rsTemp.MovePrevious

next

经测试,以上分页方法比使用临时表分页速度还要快,并且简单易用.

方法二:

大数据量下的分页

    对于非常大的数据模型而言,分页检索时,每次都加载整个数据源非常浪费。通常的选择是检索页面大小的块区的数据,而非检索所有的数据,然后单步执行当前行。

    本文演示ASP.net的DataGrid和Sql Server 实现大数据量下的分页,为了便于实现演示,数据表采用了Northwind数据库的Orders表(830条记录)。

    如果数据表中有唯一的自增索引,并且这个字段没有出现断号现象。检索页面大小的块区数据就非常简单了。通过简单的Sql语句就可以实现这个功能:

    select * from orders where orderid between 10248 and 10253

    其中,开始编号为:(CurrentPageIndex - 1) * PageSize  结束编号为:CurrentPageIndex * PageSize

    当然,如果这个字段断号不是很严重,而且允许不是很严格的按照每页条数分页,这样的方法也是可以用的。

    如果这个字段断号,或者需要按照其他条件排序分页,就要复杂些了。首先要获得这个页面需要显示的编号,然后再按照这个编号获得需要的块区数据。根据编号获得块区数据很简单。不过用下面方式获得数据排序并不是按照指定的id列表顺序,这时候还要附加order by 命令。

select * from orders where orderid in (10248,10249,10250,10251,10252,10253) order by orderid desc

    获得这个页面需要显示的编号列表就复杂多了,而且有多种方案:

方案一:维护一个表,这个表记录需要显示的这些编号排序顺序。(这个表可以是临时表,也可以是物理表)。下面演示了利用一个全局临时表。这个全局临时表记录需要显示的编号。注意排序,这里的order by 就是需要显示的排序顺序。

create table ##temptable(iid int IDENTITY (1, 1) NOT NULL,mainid int NOT NULL)insert ##temptable(mainid) select OrderID from orders order by OrderID descselect * from ##temptabledrop table ##temptable -- 实际执行时候,删除全部临时表当然不再这里执行。

这个临时表存在,获得指定分页的分块数据就很简单了。看下面代码:

create table ##temptable(iid int IDENTITY (1, 1) NOT NULL,mainid int NOT NULL)insert ##temptable(mainid) select OrderID from orders order by OrderID descdeclare PageSize int,CurrPage int,strSQL varchar(2000),IDStr varchar(1000)select PageSize = 30select CurrPage = 2select IDStr = ''select IDStr = IDStr + ltrim(rtrim(str(MainID))) + ',' from ##temptable where iid between ((CurrPage-1)*PageSize+1) and CurrPage*PageSizeif IDStr <> '' begin select IDStr = left(IDStr,len(IDStr)-1)endselect strSQL = 'select * from orders where OrderID in ('+IDStr+')  order by OrderID desc 'exec(strSQL)drop table ##temptable

注意:实际使用这个方案的时候,还要考虑何时更新这个全局临时表,一般是放到计划任务中,定时更新这个汇总表。

方案二:每次都去查询,每次获得最新的编号顺序。由于这时候不存在这个临时表,书写获得需要显示页面的编号的字符串就需要点技巧,看下面的代码:ASP.net 的 DataGrid 提供了使用这种分区的数据的方法。 DataGrid 通过 AllowCustomPaging 和 VirtualItemCount 属性支持块区操作。如果 AllowCustomPaging 为 true,则 DataGrid 不会根据 CurrentPageIndex 计算数据模型中的起始显示位置。DataGrid 将显示数据模型中的所有数据,而页导航栏将当前位置报告为 (VirtualItemCount+PageSize-1)/PageSize 之 CurrentPageIndex 页。下面的示例说明此功能。

declare PageSize int,CurrPage int,topnum int,previous intselect PageSize = 30select CurrPage = 2select topnum = CurrPage * PageSizeselect previous = (CurrPage - 1) * PageSizedeclare i int,IDStr nvarchar(500),strSQL nvarchar(1000)select i = 0select strSQL = N''select strSQL = strSQL + N' select top '+str(topnum)+ ' i = i + 1 'select strSQL = strSQL + N',  IdStr = 'select strSQL = strSQL + N'case when i > '+str(previous)+' then  IdStr + ltrim(rtrim(str(OrderID))) + '','' 'select strSQL = strSQL + N'else N''''end 'select strSQL = strSQL + N'from Orders 'select strSQL = ltrim(rtrim(strSQL)) + N' order by OrderID desc 'Select IdStr = N''exec sp_executesql strSQL,N'i int,IdStr varchar(500) output',i,IdStr outputif len(rtrim(ltrim(IdStr))) > 0begin select IdStr = left(IdStr,len(IdStr)-1)endselect strSQL = 'select * from orders where OrderID in ('+IDStr+')'exec(strSQL)

     protected void BindDataGrid(int currpage) {  string strConn = "Data Source=(local);Integrated Security=SSPI;database=Northwind";  // 请确认 机器名/ASPNET 用户可以访问Northwind数据库  SqlCommand cmd = new SqlCommand();  SqlConnection conn = new SqlConnection(strConn);  SqlParameter[]  parms = new SqlParameter[] {   new SqlParameter("PageSize",SqlDbType.Int),   new SqlParameter("CurrPage",SqlDbType.Int),   new SqlParameter("SearchSql",SqlDbType.NVarChar,128),   new SqlParameter("Count",SqlDbType.Int),  };  parms[0].Value = DataGrid1.PageSize;  parms[1].Value = (currpage+1);   //  数据库的分页算法第一页是1  DataGrid的第一页是0  parms[2].Value = DBNull.Value;  parms[3].Direction = ParameterDirection.Output;  parms[3].Value = DBNull.Value;  DataSet DS = new DataSet();  try   {   if (conn.State != ConnectionState.Open) conn.Open();   cmd.Connection = conn;   cmd.CommandText = "Selected_Page_List";   cmd.CommandType = CommandType.StoredProcedure;   if (parms != null)    {    foreach (SqlParameter parm in parms)     cmd.Parameters.Add(parm);   }   SqlDataAdapter DA = new SqlDataAdapter(cmd);   DA.Fill(DS);   int aa = Convert.ToInt32(parms[3].Value.ToString());   cmd.Parameters.Clear();   if (currpage == 0)   {    DataGrid1.VirtualItemCount = aa;   }   DataGrid1.CurrentPageIndex = currpage;   DataGrid1.DataSource = DS;   DataGrid1.DataBind();  }  catch(Exception ewx)  {   conn.Close();   Response.Write (ewx.Message.ToString());   Response.End();  } }    void Page_Load(Object sender, EventArgs E ) {  if (!IsPostBack)   {   BindDataGrid(0);   // 第一次打开这个页面,访问分页的第一页  }    }    void MyDataGrid_Page(Object sender, DataGridPageChangedEventArgs e) {  BindDataGrid(e.NewPageIndex);    }

如果你有更多数据量的表稍加修改,也可以使用本演示程序。其下是演示代码下载,演示代码使用的是方案二。使用方法看readme.txt文件。

整个演示代码

这里演示了利用DataGrid 的这个功能(没有本文中讨论的利用存储过程获得分区数据)。如对DataGrid的这个功能不太熟悉,请先看这里。

方法三:

虽然 DataGrid 控件自己带了一个分页处理机制,但它是将符合查询条件的所有记录读入内存,然后进行分页显示的。随着符合条件的记录数目增多,就会出现运行效率问题,或者至少是资源的利用率下降。

下面的代码示例都以下面的表结构为准:

 

 Articles 表SQL Server 类型Oracle 类型
PKIdint (自增)number(9) (插入时在当前最大值上加1)
 Authornvarchar(10)nvarchar2(10)
 Titlenvarchar(50)nvarchar2(50)
 PubTimedatetimedate

SQL Server / Access 等微软产品中,我们通常的自定义分页有两种思路:

一种是以 ASP.NET Forum 为代表的、“临时表”方法:即在存储过程中建立一个临时表,该临时表包含一个序号字段(1,2,3,....)以及表的主键(其他能够唯一确定一行记录的字段也是可以的)字段。存储过程可能如下:(编号 SS1)

CREATE Procedure GetAllArticles_Paged
(
     PageIndex int,
     PageSize int,
     TotalRecords out int,
     TotalPages out int
)
AS

DECLARE PageLowerBound int
DECLARE PageUpperBound int

-- Set the page bounds
SET PageLowerBound = PageSize * PageIndex
SET PageUpperBound = PageLowerBound + PageSize + 1

-- Create a temp table to store the select results
CREATE TABLE #tmp
(
     RecNo int IDENTITY (1, 1) NOT NULL,
     ArticleID int
)

INSERT INTO #tmp
     SELECT [ID]
     FROM Articles
     ORDER BY PubTime DESC

SELECT A.*
FROM Articles A (nolock), #tmp T
WHERE A.ID = T.ArticleID AND
     T.RecNo > PageLowerBound AND
     T.RecNo < PageUpperBound
ORDER BY T.RecNo

GO

另一种可能更适合程序中“拼凑” SQL 语句:用两次 TOP 命令取得我们所要的分页数据,例如:(编号 SS2)

SELECT * FROM
     (
     SELECT TOP(PageSize) * FROM
     (
          SELECT TOP (PageSize * PageIndex) *
          FROM Articles
          ORDER BY PubTime DESC
     )
     ORDER BY PubTime ASC
)
ORDER BY PubTime DESC

这个的想法就是“掐头去尾”,还有不少分页的方法,这里就不一一列出了。

对于 Oracle 数据库,有几处不同严重妨碍了上面几个方法的实施,比如,Oracle 不支持 TOP 关键字:不过这个好像并不十分严重,因为它提供了 rownum 这个隐式游标,可以实现与 TOP 类似的功能,如:

SELECT TOP 10 ... FROM WHERE ...

要写成

SELECT ... FROM ... WHERE ... AND rownum <= 10

rownum 是记录序号(1,2,3...),但有一个比较麻烦的事情是:如果 SQL 语句中有 ORDER BY ... 排序的时候,rownum 居然是先“标号”后排序!这样,这个序号如果不加处理是不合乎使用需求的。

至于临时表,Oracle 的临时表和 SQL Server 的有很大不同,我还没搞懂这个东西,就不妄加揣测了。

国内网站中介绍 Oracle 分页的资料很少,我找到了一个国外站点(www.faqts.com)的,根据这篇文章的介绍,可以如下分页:(编号 Ora1)

SELECT * FROM
     (
     SELECT A.*, rownum r
     FROM
          (
          SELECT *
          FROM Articles
          ORDER BY PubTime DESC

          ) A
     WHERE rownum <= PageUpperBound
     ) B
WHERE r > PageLowerBound;

其中蓝色部分可以改为任意的、需要的 SQL SELECT 语句,这点倒是挺方便的。

方法四:

今天突然发现,Oracle原来可以这样实现分页功能:

select * from (select rownum rdd,field1,field2 from t_table where rownum<=400) where  rdd>200

上述语句实现了从第201条记录开始处取200条记录



分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: