分页存储过程

  1. /*通用存储过程
  2. 支持多表查询,需要有索引,和int的ID字段
  3. 2008-1-10
  4. 2009-7-21    支持非主键的单字段排序
  5. 2009-11-20  去掉out total
  6. by emilchan
  7. */
  8. create                    PROCEDURE [dbo].[spCRM_GetPageListByNoTotal]
  9. (
  10.     @strSelect        varchar(2000),            -- 要返回的列  strSelect=
  11.     @strFrom        varchar(2000),            -- 主表名 strFrom=
  12.     @strWhere        varchar(2000)='1=1',      -- 查询条件
  13.     @strPk            varchar(2000)='a.Id',      -- 主键字段名
  14.     @strOrder        varchar(100),            -- 排序字段
  15.     @strDirection    varchar(5)='desc',        -- 排序方向
  16.     @PageSize        int = 10,              -- 单页大小
  17.     @PageIndex        int = 1                -- 第几页 
  18. )
  19. AS
  20. declare @sql nvarchar(4000)
  21. declare @sort                varchar(10)        --desc or asc
  22. declare @operator            varchar(2)        --方向符号
  23. IF LOWER(@strDirection)='desc'            --如果是逆序
  24.     BEGIN
  25.         set @sort=' desc '
  26.         SET @operator = '<='
  27.     END
  28. ELSE                                        --否则正序
  29.     BEGIN
  30.         set @sort=' asc '
  31.         SET @operator = '>='
  32.     END
  33. declare @strOrderSql            varchar(6000)    --没有排序的包含主键、order字段的列表
  34. declare @strSelectSQL                  varchar(6000)    --返回列表
  35. declare @strGetRecordSql        varchar(6000)    --最终输出表
  36. declare @tmpStrPk                varchar(2000)    --把形如a.Id的主键还原为Id
  37. if(@strPk<>@strOrder)
  38.     set @strOrderSql = 'select '+@strPk+','+@strOrder+' from '+ @strFrom+' where '+@strWhere
  39. else
  40.     set @strOrderSql = 'select '+@strPk+' from '+ @strFrom+' where '+@strWhere
  41. set @strSelectSQL = 'select '+@strSelect+' from '+ @strFrom+' where '+@strWhere
  42. declare @tmpStrOrder        varchar(50)
  43. --修改pkstr以适合构造sql,去掉a.Id的a.
  44. select @tmpStrPk=@strPk
  45. select @tmpStrOrder=@strOrder
  46. if(charindex('.',@tmpStrPk)>0)
  47.     select @tmpStrPk=substring(@tmpStrPk,3, len(@tmpStrPk)-2)
  48. if(charindex('.',@tmpStrOrder)>0)
  49.     select @tmpStrOrder=substring(@tmpStrOrder,3, len(@tmpStrOrder)-2)
  50. /*默认当前页*/
  51. /*直接显示第一页*/
  52. IF @PageIndex <= 1
  53. begin
  54.     SET @PageIndex = 1
  55.     if @PageIndex=1
  56.     begin
  57.         set  @strSelectSQL=stuff(@strSelectSQL,7,0,' top '+cast(@PageSize as varchar)+' ')+ ' order by '+@strOrder+@sort
  58.         exec(@strSelectSQL)
  59.     end
  60. end
  61. else
  62. begin
  63.     --主键是int,且排序为主键的情况
  64.     if(@strPk=@strOrder)
  65.     begin
  66.         --设置分页参数
  67.         DECLARE @strPageSize        varchar(50)        --页大小str
  68.         DECLARE @strStartRow        varchar(50)        --开始的行的计数值       
  69.  
  70.         SET @strPageSize = CAST(@PageSize AS varchar(50))
  71.         SET @strStartRow = CAST(((@PageIndex-1)*@PageSize+1) AS varchar(50))
  72.        
  73.         declare @topPostion int        -- select后插入top 语句的位置
  74.         set @topPostion= len('select')+1
  75.         DECLARE @SortColumn varchar(50) --返回边界值,第一条记录的pkid
  76.         --返回@SortColumn值,该值为条件搜索结果的第一条返回记录,通过该记录,由@operator来决定排序,该值就是当前的的上边界值
  77.         set @sql='
  78.         SET ROWCOUNT ' + @strStartRow + '
  79.         Select @SortColumn=m. '+@tmpStrPk+' from (' + @strOrderSql+ ') m order by m.'+@tmpStrOrder+@sort
  80.         --返回一个pkstr值
  81.         exec sp_executesql @sql, N'@SortColumn varchar(50) output',@SortColumn output
  82.        
  83.  
  84.         --符合条件的,还没有按页大小取记录的记录集
  85.         set @strGetRecordSql=@strSelectSQL+' and '+@strPk+ @operator +cast(@SortColumn as varchar)+'order by '+@strOrder+@sort
  86.  
  87.         --获取top n行
  88.         set @strGetRecordSql=stuff(@strGetRecordSql,@topPostion,0, ' top '+cast(@PageSize as varchar)+' ') 
  89.         exec(@strGetRecordSql)
  90.     end
  91. else
  92.     begin
  93.     --非主键排序情况
  94.         declare @maxPage int
  95.         declare @lastPageSize int
  96.         declare @tmpOrderIn    varchar(100)        --内层order带a.xx
  97.         declare @tmpOrderOut    varchar(100)    --外层反向order
  98.         declare @tmpOrder    varchar(100)        --最外层order没有a.xx
  99.         declare @total    int
  100.         if(@strDirection='desc')
  101.         begin
  102.             set @tmpOrderIn= @strOrder+' desc,'+@strPk+' desc'
  103.             set @tmpOrderOut= @tmpStrOrder+' asc,'+@tmpStrPk+' asc'
  104.             set @tmpOrder= @tmpStrOrder+' desc,'+@tmpStrPk+' desc'
  105.         end
  106.         else
  107.         begin
  108.             set @tmpOrderIn= @strOrder+' asc,'+@strPk+' asc'
  109.             set @tmpOrderOut= @tmpStrOrder+' desc,'+@tmpStrPk+' desc'
  110.             set @tmpOrder= @tmpStrOrder+' asc,'+@tmpStrPk+' asc'
  111.         end
  112.         set @lastPageSize=@pageSize
  113.         set @maxPage=@total/@pageSize
  114.         set @sql = 'select @total=count('+@strPk+') from '+@strFrom+' where '+ @strWhere
  115.         exec sp_executesql @sql, N'@total int output',@total output
  116.         if(@total%@pageSize)>0
  117.         begin
  118.             set @maxPage=@maxPage+1
  119.             set @lastPageSize=@pageSize-((@pageSize*@pageIndex)-@total)
  120.         end   
  121.         --最后一页不是满页的情况
  122.         if (@total<@pageSize*@pageIndex and @pageIndex=@maxPage)
  123.             begin
  124.             set @strGetRecordSql='select * from (  select TOP '+cast(@lastPageSize as varchar)+' * FROM
  125.                 ( SELECT TOP '+cast(@total as varchar)+ ' '+@strSelect+' from '+@strFrom+' where '+@strWhere+' ORDER BY '+@tmpOrderIn+' ) as m  ORDER BY '+@tmpOrderOut+') as b  ORDER BY '+@tmpOrder
  126.             end
  127.         else
  128.             begin
  129.             set @strGetRecordSql='select * from (  select TOP '+cast(@pageSize as varchar)+' * FROM
  130.                 ( SELECT TOP '+cast(@pageSize*@pageIndex as varchar)+ ' '+@strSelect+' from '+@strFrom+' where '+@strWhere+' ORDER BY '+@tmpOrderIn+' ) as m  ORDER BY '+@tmpOrderOut+' ) as b  ORDER BY '+@tmpOrder
  131.             end
  132.         exec(@strGetRecordSql)
  133.     end
  134. end
  135. GO
复制代码

一般的,我们需要一个total来显示在页面,如果直接在sp里面执行,效率在小表里面看不出来,如果在百W级别、千W级别里面,性能差距是相当明显,下面这个SP是自带total返回的,

  1. /*通用存储过程
  2. 支持多表查询,需要有索引,和int的ID字段
  3. 2008-1-10
  4. 2009-7-21    支持非主键的单字段排序
  5. by emilchan
  6. */
  7. CREATE                    PROCEDURE [dbo].[spCRM_GetPageListBy]
  8. (
  9.     @strSelect        varchar(2000),            -- 要返回的列  strSelect=
  10.     @strFrom        varchar(2000),            -- 主表名 strFrom=
  11.     @strWhere        varchar(2000)='1=1',      -- 查询条件
  12.     @strPk            varchar(2000)='a.Id',      -- 主键字段名
  13.     @strOrder        varchar(100),            -- 排序字段
  14.     @strDirection    varchar(5)='desc',        -- 排序方向
  15.     @PageSize        int = 10,              -- 单页大小
  16.     @PageIndex        int = 1,                -- 第几页 
  17.     @total            int  output        --总记录数
  18. )
  19. AS
  20. --return @total
  21. declare @sql nvarchar(4000)
  22. set @sql = 'select @total=count('+@strPk+') from '+@strFrom+' where '+ @strWhere
  23. exec sp_executesql @sql, N'@total int output',@total output
  24. declare @sort                varchar(10)        --desc or asc
  25. declare @operator            varchar(2)        --方向符号
  26. IF LOWER(@strDirection)='desc'            --如果是逆序
  27.     BEGIN
  28.         set @sort=' desc '
  29.         SET @operator = '<='
  30.     END
  31. ELSE                                        --否则正序
  32.     BEGIN
  33.         set @sort=' asc '
  34.         SET @operator = '>='
  35.     END
  36. declare @strOrderSql            varchar(6000)    --没有排序的包含主键、order字段的列表
  37. declare @strSelectSQL                  varchar(6000)    --返回列表
  38. declare @strGetRecordSql        varchar(6000)    --最终输出表
  39. declare @tmpStrPk                varchar(2000)    --把形如a.Id的主键还原为Id
  40. if(@strPk<>@strOrder)
  41.     set @strOrderSql = 'select '+@strPk+','+@strOrder+' from '+ @strFrom+' where '+@strWhere
  42. else
  43.     set @strOrderSql = 'select '+@strPk+' from '+ @strFrom+' where '+@strWhere
  44. set @strSelectSQL = 'select '+@strSelect+' from '+ @strFrom+' where '+@strWhere
  45. declare @tmpStrOrder        varchar(50)
  46. --修改pkstr以适合构造sql,去掉a.Id的a.
  47. select @tmpStrPk=@strPk
  48. select @tmpStrOrder=@strOrder
  49. if(charindex('.',@tmpStrPk)>0)
  50.     select @tmpStrPk=substring(@tmpStrPk,3, len(@tmpStrPk)-2)
  51. if(charindex('.',@tmpStrOrder)>0)
  52.     select @tmpStrOrder=substring(@tmpStrOrder,3, len(@tmpStrOrder)-2)
  53. /*默认当前页*/
  54. /*直接显示第一页*/
  55. IF @PageIndex <= 1
  56. begin
  57.     SET @PageIndex = 1
  58.     if @PageIndex=1
  59.     begin
  60.         set  @strSelectSQL=stuff(@strSelectSQL,7,0,' top '+cast(@PageSize as varchar)+' ')+ ' order by '+@strOrder+@sort
  61.         exec(@strSelectSQL)
  62.     end
  63. end
  64. else
  65. begin
  66.     --主键是int,且排序为主键的情况
  67.     if(@strPk=@strOrder)
  68.     begin
  69.         --设置分页参数
  70.         DECLARE @strPageSize        varchar(50)        --页大小str
  71.         DECLARE @strStartRow        varchar(50)        --开始的行的计数值       
  72.  
  73.         SET @strPageSize = CAST(@PageSize AS varchar(50))
  74.         SET @strStartRow = CAST(((@PageIndex-1)*@PageSize+1) AS varchar(50))
  75.        
  76.         declare @topPostion int        -- select后插入top 语句的位置
  77.         set @topPostion= len('select')+1
  78.         DECLARE @SortColumn varchar(50) --返回边界值,第一条记录的pkid
  79.         --返回@SortColumn值,该值为条件搜索结果的第一条返回记录,通过该记录,由@operator来决定排序,该值就是当前的的上边界值
  80.         set @sql='
  81.         SET ROWCOUNT ' + @strStartRow + '
  82.         Select @SortColumn=m. '+@tmpStrPk+' from (' + @strOrderSql+ ') m order by m.'+@tmpStrOrder+@sort
  83.         --返回一个pkstr值
  84.         exec sp_executesql @sql, N'@SortColumn varchar(50) output',@SortColumn output
  85.        
  86.  
  87.         --符合条件的,还没有按页大小取记录的记录集
  88.         set @strGetRecordSql=@strSelectSQL+' and '+@strPk+ @operator +cast(@SortColumn as varchar)+'order by '+@strOrder+@sort
  89.  
  90.         --获取top n行
  91.         set @strGetRecordSql=stuff(@strGetRecordSql,@topPostion,0, ' top '+cast(@PageSize as varchar)+' ') 
  92.         exec(@strGetRecordSql)
  93.     end
  94. else
  95.     begin
  96.     --非主键排序情况
  97.         declare @maxPage int
  98.         declare @lastPageSize int
  99.         declare @tmpOrderIn    varchar(100)        --内层order带a.xx
  100.         declare @tmpOrderOut    varchar(100)    --外层反向order
  101.         declare @tmpOrder    varchar(100)        --最外层order没有a.xx
  102.         if(@strDirection='desc')
  103.         begin
  104.             set @tmpOrderIn= @strOrder+' desc,'+@strPk+' desc'
  105.             set @tmpOrderOut= @tmpStrOrder+' asc,'+@tmpStrPk+' asc'
  106.             set @tmpOrder= @tmpStrOrder+' desc,'+@tmpStrPk+' desc'
  107.         end
  108.         else
  109.         begin
  110.             set @tmpOrderIn= @strOrder+' asc,'+@strPk+' asc'
  111.             set @tmpOrderOut= @tmpStrOrder+' desc,'+@tmpStrPk+' desc'
  112.             set @tmpOrder= @tmpStrOrder+' asc,'+@tmpStrPk+' asc'
  113.         end
  114.         set @lastPageSize=@pageSize
  115.      
  116.         set @maxPage=@total/@pageSize
  117.         if(@total%@pageSize)>0
  118.         begin
  119.             set @maxPage=@maxPage+1
  120.             set @lastPageSize=@pageSize-((@pageSize*@pageIndex)-@total)
  121.         end   
  122.         --最后一页不是满页的情况
  123.         if (@total<@pageSize*@pageIndex and @pageIndex=@maxPage)
  124.             begin
  125.             set @strGetRecordSql='select * from (  select TOP '+cast(@lastPageSize as varchar)+' * FROM
  126.                 ( SELECT TOP '+cast(@total as varchar)+ ' '+@strSelect+' from '+@strFrom+' where '+@strWhere+' ORDER BY '+@tmpOrderIn+' ) as m  ORDER BY '+@tmpOrderOut+') as b  ORDER BY '+@tmpOrder
  127.             end
  128.         else
  129.             begin
  130.             set @strGetRecordSql='select * from (  select TOP '+cast(@pageSize as varchar)+' * FROM
  131.                 ( SELECT TOP '+cast(@pageSize*@pageIndex as varchar)+ ' '+@strSelect+' from '+@strFrom+' where '+@strWhere+' ORDER BY '+@tmpOrderIn+' ) as m  ORDER BY '+@tmpOrderOut+' ) as b  ORDER BY '+@tmpOrder
  132.             end
  133.         exec(@strGetRecordSql)
  134.     end
  135. end
  136. GO
复制代码

如果你的分页控件需要后台绑定total,则用第二个SP,但这里,我真的向你推荐使用第一种不带TOTAL返回的,最佳做法是在页面前台,使用一个ajax异步查询total,然后绑定到ajax的分页上。这样性能将是最佳的。

在1900w级的测试中,ajax分页和后台绑定total的性能测试中, 其首次加载页面的性能体验差距基本上是10倍计。

因此,分页的快速开发最佳做法是通用sp+ajax分页,至于数据绑定,泛型还是dataset,我的建议是能用泛型的时候还是泛型,不是因为性能好,而是因为从后翻页泛型的效率和首页加载的性能基本一致。而dataset效率慢了4.5倍左右。数据可以绑定到gridview,也可以仍然使用ajax传递,看你项目需要了,这个不是必须的。

posted @ 2012-04-01 14:49  yxf2011  阅读(251)  评论(0编辑  收藏  举报