Royman.Chen

My DBA + Programmer 成长路 ...

导航

SQL Server 存储过程的分页

 

SQL Server 存储过程的分页方案比拼 
SQL Server 存储过程的分页,这个问题已经讨论过几年了,很多朋友在问我,所以在此发表一下我的观点
建立表:

CREATE TABLE [TestTable] (
 
[ID] [int] IDENTITY (11NOT NULL ,
 
[FirstName] [nvarchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
 
[LastName] [nvarchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
 
[Country] [nvarchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
 
[Note] [nvarchar] (2000) COLLATE Chinese_PRC_CI_AS NULL 
ON [PRIMARY]
GO

 

插入数据:(2万条,用更多的数据测试会明显一些)
SET IDENTITY_INSERT TestTable ON

declare @i int
set @i=1
while @i<=20000
begin
    
insert into TestTable([id], FirstName, LastName, Country,Note) values(@i'FirstName_XXX','LastName_XXX','Country_XXX','Note_XXX')
    
set @i=@i+1
end

SET IDENTITY_INSERT TestTable OFF

 

-------------------------------------

分页方案一:(利用Not In和SELECT TOP分页)
语句形式:
SELECT TOP 10 *
FROM TestTable
WHERE (ID NOT IN
          (
SELECT TOP 20 id
         
FROM TestTable
         
ORDER BY id))
ORDER BY ID


SELECT TOP 页大小 *
FROM TestTable
WHERE (ID NOT IN
          (
SELECT TOP 页大小*页数 id
         
FROM 表
         
ORDER BY id))
ORDER BY ID

-------------------------------------

分页方案二:(利用ID大于多少和SELECT TOP分页)
语句形式:
SELECT TOP 10 *
FROM TestTable
WHERE (ID >
          (
SELECT MAX(id)
         
FROM (SELECT TOP 20 id
                 
FROM TestTable
                 
ORDER BY id) AS T))
ORDER BY ID


SELECT TOP 页大小 *
FROM TestTable
WHERE (ID >
          (
SELECT MAX(id)
         
FROM (SELECT TOP 页大小*页数 id
                 
FROM 表
                 
ORDER BY id) AS T))
ORDER BY ID


-------------------------------------

分页方案三:(利用SQL的游标存储过程分页)
create  procedure XiaoZhengGe
@sqlstr nvarchar(4000), --查询字符串
@currentpage int--第N页
@pagesize int --每页行数
as
set nocount on
declare @P1 int--P1是游标的id
 @rowcount int
exec sp_cursoropen @P1 output,@sqlstr,@scrollopt=1,@ccopt=1,@rowcount=@rowcount output
select ceiling(1.0*@rowcount/@pagesizeas 总页数--,@rowcount as 总行数,@currentpage as 当前页 
set @currentpage=(@currentpage-1)*@pagesize+1
exec sp_cursorfetch @P1,16,@currentpage,@pagesize 
exec sp_cursorclose @P1
set nocount off

其它的方案:如果没有主键,可以用临时表,也可以用方案三做,但是效率会低。
建议优化的时候,加上主键和索引,查询效率会提高。

通过SQL 查询分析器,显示比较:我的结论是:
分页方案二:(利用ID大于多少和SELECT TOP分页)效率最高,需要拼接SQL语句
分页方案一:(利用Not In和SELECT TOP分页)   效率次之,需要拼接SQL语句
分页方案三:(利用SQL的游标存储过程分页)    效率最差,但是最为通用

在实际情况中,要具体分析。


更多的讨论见:
http:
//community.csdn.net/Expert/topic/3292/3292678.xml?temp=.1621515




Trackback: http:
//tb.blog.csdn.net/TrackBack.aspx?PostId=103511

[点击此处收藏本文]   发表于 2004年09月14日 1:05 AM 


  
squirrel_sc 发表于2004
-09-28 8:38 PM  IP: 61.49.220.*
能否给出具体的测试结果数据呢?谢谢
~

  
QQ
'Richer 发表于2004-10-29 10:12 AM  IP: 219.239.44.*
好,总算找到这篇文章了,谢谢洪根兄

  
SUN 发表于2004-11-09 10:49 AM  IP: 61.233.144.*
declare @pagesize int 
set @pagesize = 10 
select top @pagesize * from album 
order by picid 

这样怎么不对啊? 
老大! 
select top 10 * from album 
order by picid 
又可以 为什么呢? 
那我怎么写存储过程啊!

  
SUN 发表于2004-11-09 11:06 AM  IP: 61.233.144.*
那我 不是 不能写成 存储过程? 
只有写成 SQL语句放在页面上? 
这样不是会降低性能?

  
hubinasm 发表于2004-11-21 11:51 PM  IP: 
TrackBack来自《关于数据库分页》:

Ping Back来自:blog.csdn.net

  
sunny 发表于2004-12-02 10:25 PM  IP: 61.149.250.*
第二种办法,如果按照主键排序可以。 

不然的话不行。 


  
菩提树 发表于2004-12-08 6:06 PM  IP: 219.133.133.*
很多情况下,我们可能不能按主键来排序并分页,比如,你可能需要按工资高低给20万条记录分页及排序,那你肯定只能用临时表或者游标了,游标我不熟,临时表倒是可以达到目的,可是效率不好呀 
ALTER PROCEDURE dbo.GetPageRecord 

@tblName varchar(255), -- 表名 
@fldName varchar(255), --排序字段 
@KeyField varchar(255), --主键 
@PageSize int = 10, -- 页尺寸 
@PageIndex int = 1, -- 页码 
@IsCount bit = 0, -- 返回记录总数, 非 0 值则返回 
@OrderType bit = 1, -- 设置排序类型, 非 0 值则降序 
@strWhere varchar(4000) = 
'' -- 查询条件 (注意: 不要加 where) 

AS 
SET NOCOUNT ON 
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, 
oldid int 

--generate record 
Declare @Sqlstr varchar(6000) 
set @sqlstr=
'select '+@Keyfield+' from '+@tblname 
if(@strWhere<>
''
begin 
set @Sqlstr=@sqlstr+
' where ('+@strWhere+')' 
end 
set @sqlstr=@sqlstr+
' order by '+@fldName 
if(@ordertype=0) 
begin 
set @sqlstr=@sqlstr+
' asc' 
end 
else 
begin 
set @sqlstr=@sqlstr+
' desc' 
end 
set @sqlstr=
'insert into #tmp (oldid) '+@sqlstr 
execute(@sqlstr) 
set @sqlstr=
'SELECT TableA.* FROM '+@tblname+' TableA (nolock), #tmp T WHERE T.oldid = TableA.'+@keyfield+' AND T.RecNo > '+ cast(@PageLowerBound as varchar)+' AND T.RecNo < '+cast(@PageUpperBound as varchar)+' ORDER BY T.RecNo' 
execute(@sqlstr)

  
菩提树 发表于2004-12-08 6:06 PM  IP: 219.133.133.*
ALTER PROCEDURE dbo.GetPageRecord 

@tblName varchar(255), -- 表名 
@fldName varchar(255), --排序字段 
@KeyField varchar(255), --主键 
@PageSize int = 10, -- 页尺寸 
@PageIndex int = 1, -- 页码 
@IsCount bit = 0, -- 返回记录总数, 非 0 值则返回 
@OrderType bit = 1, -- 设置排序类型, 非 0 值则降序 
@strWhere varchar(4000) = 
'' -- 查询条件 (注意: 不要加 where) 

AS 
SET NOCOUNT ON 
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, 
oldid int 

--generate record 
Declare @Sqlstr varchar(6000) 
set @sqlstr=
'select '+@Keyfield+' from '+@tblname 
if(@strWhere<>
''
begin 
set @Sqlstr=@sqlstr+
' where ('+@strWhere+')' 
end 
set @sqlstr=@sqlstr+
' order by '+@fldName 
if(@ordertype=0) 
begin 
set @sqlstr=@sqlstr+
' asc' 
end 
else 
begin 
set @sqlstr=@sqlstr+
' desc' 
end 
set @sqlstr=
'insert into #tmp (oldid) '+@sqlstr 
execute(@sqlstr) 
set @sqlstr=
'SELECT TableA.* FROM '+@tblname+' TableA (nolock), #tmp T WHERE T.oldid = TableA.'+@keyfield+' AND T.RecNo > '+ cast(@PageLowerBound as varchar)+' AND T.RecNo < '+cast(@PageUpperBound as varchar)+' ORDER BY T.RecNo' 
execute(@sqlstr)

  
3doyang 发表于2005-02-15 7:14 PM  IP: 
TrackBack来自《ASP.NET分页的处理方式》:

Ping Back来自:blog.csdn.net

  
Nill 发表于2005-03-05 1:44 AM  IP: 219.128.247.*
我改了下方案2 

declare @pagesize int 
declare @pagenum int 
set @pagenum=3---第几页 
set @pagesize=20 ----每页显示条目数 

declare @t0 varchar(10) 
declare @t1 varchar(10) 

set @t0=cast(@pagesize as varchar(10)) 
set @t1=cast(@pagesize*(@pagenum-1) as varchar(10)) 
--加了个判断是否是第一页。 
if @pagenum<>1 
exec(
'SELECT TOP '+@t0+ ' * FROM DV_board WHERE (boardid >(SELECT MAX(boardid) FROM (SELECT TOP '+@t1+ ' boardid FROM DV_board ORDER BY boardid) AS T)) ORDER BY boardID'
else 
exec(
'SELECT TOP '+@t0+ ' * FROM DV_board')

  
曾登高 发表于2005-03-28 1:11 AM  IP: 
TrackBack来自《近日关注:系统性能提升之道--内存镜像表》:

Ping Back来自:blog.csdn.net

  
我是好猫 发表于2005-03-29 8:49 PM  IP: 218.58.242.*
什么时候,盖子能推出个 适合分页(类似 mysql 的 LIMIT )的 新mssql版本啊 。。。。 

  
九命鸟 发表于2005-04-20 5:10 PM  IP: 210.22.100.*
这样分页的方例都不是很通用,可以考虑用Rowcount的方法,参数@table, @fields, @filter, @orderby, @groupby, @pagenumber, @pagesize,在SP中形成SQL语句。

  
随身影子 发表于2005-04-22 12:48 PM  IP: 219.137.167.*
如果输出按字段AA排序,并且 AA 是索引的话,用游标是最高效率的。用游标分两次分别取到Begin和End点的AA的值就可以搞定。 


  
王者归来 发表于2005-06-01 10:54 PM  IP: 61.186.252.*
两个问题:1.用方法2,如何选择第一页的数据,pagesize和pagecount分别怎么传? 2.如何实现主从表数据的分页?

  
王者归来 发表于2005-06-01 10:57 PM  IP: 61.186.252.*
To 九命鸟:老兄说的这种方法哪里可以找到参考?

  
ξσ Dicky σξ 发表于2005-06-18 10:36 AM  IP: 
TrackBack来自《SQL Server 存储过程的分页方案比拼》:

Ping Back来自:blog.csdn.net

  
leeight 发表于2005-07-12 10:38 AM  IP: 
TrackBack来自《关于存储过程实现分页的技术》:

Ping Back来自:blog.csdn.net

  
小灰 发表于2005-07-21 8:59 PM  IP: 61.186.252.*
看看这个效率如何?怎么改进? 
CREATE proc page 
@RecordCount int output, 
@QueryStr nvarchar(100)=
'table1',--表名、视图名、查询语句 
@PageSize int=20, --每页的大小(行数) 
@PageCurrent int=2, --要显示的页 从0开始 
@FdShow nvarchar (1000)=
'*', --要显示的字段列表 
@IdentityStr nvarchar (100)=
'id', --主键 
@WhereStr nvarchar (200)=
'1=1'
@FdOrder nvarchar(100)=
'desc' --排序 只能取desc或者asc 
as 
declare 
@sql nvarchar(2000) 
set @sql = 
'' 
if @WhereStr = 
'' 
set @WhereStr = 
'1=1' 

if @PageCurrent = 0 begin 
set @sql = 
'select top ' + cast(@PageSize as nvarchar(3)) + ' ' + @FdShow + ' from ' + @QueryStr + ' where ' + @WhereStr + ' order by ' + @IdentityStr + ' ' + @FdOrder 
end 

else begin 
if upper(@FdOrder) = 
'DESC' begin 
set @sql = 
'select top ' + cast(@PageSize as nvarchar(3)) + ' ' + @FdShow + ' from ' + @QueryStr + ' where ' + @WhereStr + ' and ' + @IdentityStr + '< ( select min(' + @IdentityStr + 'from (select top ' + cast(@PageSize*@PageCurrent as nvarchar(10)) + ' ' + @IdentityStr + ' from ' + @QueryStr + ' where ' + @WhereStr + ' order by ' + @IdentityStr + ' descas t) order by ' + @IdentityStr + ' desc' 
end 
else begin 
set @sql = 
'select top ' + cast(@PageSize as nvarchar(3)) + ' ' + @FdShow + ' from ' + @QueryStr + ' where ' + @WhereStr + ' and ' + @IdentityStr + '> ( select max(' + @IdentityStr + 'from (select top ' + cast(@PageSize*@PageCurrent as nvarchar(10)) + ' ' + @IdentityStr + ' from ' + @QueryStr + ' where ' + @WhereStr + ' order by ' + @IdentityStr + ' ascas t) order by ' + @IdentityStr + ' asc' 
end 
end 
--print @sql 
execute(@sql) 


if(@RecordCount is null or @RecordCount<=0)begin 
declare @tsql nvarchar(200) 
set @tsql=N
'select @RecordCount = count(*from ' + @QueryStr + ' where ' + @WhereStr 
exec sp_executesql @tsql,N
'@RecordCount int output',@RecordCount output 
select @Recordcount 
end 
GO 


  
.., 发表于2005-08-15 3:27 AM  IP: 61.186.252.*
愚以为前两种方法只对主键字段的排序有效。 
ID NAME TYPEID 
1 aaa 1 
2 bbb 2 
3 ccc 3 
4 ddd 3 
5 eee 4 
6 fff 5 

例如有如上6条数据,ID为主键,TYPEID为外部键(索引)。 
如果使用ID排序,则可得正确结果。 
但是如果使用TYPEID排序的话,每页显示3条数据,要显示第2页。则两种方法都只能显示2条记录,而实际应该是3条。 

第3种方法,由于在下对sql server的cursor不熟悉,所以不清楚结果。

  
cnatang 发表于2005-09-13 3:04 PM  IP: 211.100.21.*
是啊。要实现一个完美的分页好象不可能?? 
用方法2 ,我现在就碰到了 order by 非主键里的问题! 
搞了一晚上,始终没有搞掂。楼主有什么好的解决办法?

  
zxub 发表于2005-10-13 10:21 PM  IP: 211.100.21.*
  order by非主键的话,就用主键字段+排序字段,我就不信不能排,我做C#分页插件的时候就是这么做的,没问题的。

  
网教中国 发表于2005-12-08 10:09 AM  IP: 222.79.23.*
不错

  
oracle 发表于2006-01-05 10:31 AM  IP: 61.154.121.*
都不好,大多数情况下不会用主键来排序,再说拼接sql这样索引就失效了,可能从效率最高掉到效率最低

posted on 2006-01-15 10:24  Royman.Chen  阅读(595)  评论(1编辑  收藏  举报