CREATE PROCEDURE GetRecordFromPage
@tblName varchar(255), -- 表名 @fldName varchar(255), -- 字段名 @PageSize int = 10, -- 页尺寸 @PageIndex int = 1, -- 页码 @IsCount bit = 0, -- 返回记录总数, 非 0 值则返回 @OrderType bit = 0, -- 设置排序类型, 非 0 值则降序 @strWhere varchar(4000) = '' -- 查询条件 (注意: 不要加 where) AS declare @strSQL varchar(6000) -- 主语句 declare @strTmp varchar(6000) -- 临时变量 declare @strOrder varchar(6000) -- 排序类型 if @OrderType != 0 begin set @strTmp = "<(select min" set @strOrder = " order by [" + @fldName +"] desc" end else begin set @strTmp = ">(select max" set @strOrder = " order by [" + @fldName +"] asc" end set @strSQL = "select top " + str(@PageSize) + " * from [" + @tblName + "] where [" + @fldName + "]" + @strTmp + "([" + @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " [" + @fldName + "] from [" + @tblName + "]" + @strOrder + ") as tblTmp)" + @strOrder if @strWhere != '' set @strSQL = "select top " + str(@PageSize) + " * from [" + @tblName + "] where [" + @fldName + "]" + @strTmp + "([" + @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " [" + @fldName + "] from [" + @tblName + "] where (" + @strWhere + ") " + @strOrder + ") as tblTmp) and (" + @strWhere + ") " + @strOrder if @PageIndex = 1 begin set @strTmp = "" if @strWhere != '' set @strTmp = " where (" + @strWhere + ")" set @strSQL = "select top " + str(@PageSize) + " * from [" + @tblName + "]" + @strTmp + " " + @strOrder end if @IsCount != 0 set @strSQL = "select count(*) as Total from [" + @tblName + "]" exec (@strSQL) GO 这个分页存储过程我一直用于某些场所,如果大家用过,相信应该比较认可它的性能,可是,从代码中,我们看到,它有一个基本的要求,即是表或视图必定有一个主键,隐含的要求是排序字段必须是一个具备唯一值的字段,这在我上面提到的应用是不可能满足的,所以,这个存储过程虽然有用,可是,在这个场合,却用不上 后来看到CNBLOGS上另一位仁兄谈到的关于ASP.NET FORUM使用的临时表分页法,就写了一个临时表的分页存储过程,也是通用的,实现起来比较上的方法还要容易,如下 CREATE PROCEDURE dbo.GetPageRecord ( @tblName varchar(255), -- 表名 @fldlist varchar(1000)='*', @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 #temp ( RecNo int IDENTITY (1, 1) NOT NULL, oldid int ) --generate record Declare @Sqlstr varchar(6000) set @sqlstr='select from if(@strWhere<>'') begin set @Sqlstr=@sqlstr+' where ( end set @sqlstr=@sqlstr+' order by if(@ordertype=0) begin set @sqlstr=@sqlstr+' asc' end else begin set @sqlstr=@sqlstr+' desc' end set @sqlstr='insert into #temp (oldid) execute(@sqlstr) set @sqlstr='SELECT FROM TableA (nolock), #temp T WHERE T.oldid = AND T.RecNo > '+ cast(@PageLowerBound as varchar)+' AND T.RecNo < '+cast(@PageUpperBound as varchar)+' ORDER BY T.RecNo' execute(@sqlstr) GO第一个秘密是关于SELECT语句的:
SQL中的SELECT语句可以将另外一个SELECT语句作为内容来源,并且按此规律进行嵌套处理,但是,有一个前提,必须对作为来源的SELEC结果集指定别名,下面是一个示例性SQL语句,你可以在查询分析器里证实它: select top 10 * from (select top 100 * from tblproducts where name like '%mp3%' order by id desc) as a order by id desc 请相信你的眼睛,上面的语句确实能执行,可能你曾经试过执行类似的语句但并未成功,那是因为你没有将来源结果集指定别名 第二个秘密是一个规则: 如果你想用select top 20*4的方式在SQL中取前80条记录,你会失败,因为TOP子句有一个限制,就是TOP num中的num不能是一个计算值或表达式而必须是一个既定的常量 第三个秘密足够重大,我想,应该很少有人发现它,否则的话,很多问题便会变得简单起来,简要的说,它就是:SQL允许你用EXECUTE执行一个变量中定义的SQL语句,并且允许你在被执行的SQL语句中,再次嵌套入一个变量定义的语句,并且再次在其中用EXECUTE执行它,如果语言不足以证明,那么下面的代码将证明这是行得通并且是很好的 declare @sqlstr varchar(3000) set @sqlstr='declare @subsqlstr varchar(1000);' set @sqlstr=@sqlstr+'set @subsqlstr=''select * from tblproducts'';' set @sqlstr=@sqlstr+'execute (@subsqlstr)' execute(@sqlstr) 在上面的代码中,@SQL变量中定义的是一组SQL语句,在这组SQL语句中,又定义了一个@SUBSQL变量,在其中存放了SELECT语句, 所以,当系统执行@SQLSTR时,其中定义的变量会被执行,执行的结果是,构造了一组动态执行的SQL语句,将其存入了变量,最后,在EXECUTE中又被执行,这种嵌套的执行,我们就姑且称其为动态执行能力吧 不过,需要相当注意的是: 1,由于在变量中存放语句组,因此,且莫忘记在每一个语句末尾加上分号(分号是SQL一句语句的结束标志) 2,如果在语句中包含变量的赋值,请记住你是在写一个变量赋值语句,因此记住加上单引号将值括起 3,如果你在变量中定义的语句组中要引用字符串常量,请先将单引号替换成两个单引号最后,我用上面的方法写了两种SQL通用分页存储过程:
它们具有以下特点1,支持字段集合选择,2支持任意字段排序, 上面的图,我们以最终结果集倒序为例,第一种方案,我们先取集集合OB,这可以用一个使用SELECT TOP 并使用升级排列的语句完成 然后对OB结果集进行倒序排序,再用"SELECT TOP 每页记录数"倒序 的方式取得目标集合AB,下面是存储过程 create PROCEDURE dbo.GetPagingData ( @tablename varchar(100),--表名或视图表 @fieldlist varchar(4000)='*',--欲选择字段列表 @orderfield varchar(100),--排序字段 @keyfield varchar(100),--主键 @pageindex int,--页号,从0开始 @pagesize int=20,--页尺寸 @strwhere varchar(4000),--条件 @ordertype bit=1--排序,1,降序,0,升序 ) AS /**//* 名称:GetPagingRecord 作用:按任意字段进行排序分页 */ SET NOCOUNT ON declare @sqlstr varchar(6000) --处理SQL中危险字符,并且将条件处理成易嵌入的形式 set @sqlstr='declare @Rcount int;' set @sqlstr=@sqlstr+'set @rcount=(select count() from where set @strwhere=replace(@strwhere,'''','''''') set @strwhere=replace(@strwhere,'--','') set @strwhere=replace(@strwhere,';','') set @sqlstr=@sqlstr+'declare @Rnum int;' set @sqlstr=@sqlstr+'set @rnum=@rcount-'+cast(@pagesize as varchar)+'*'+cast(@pageindex as varchar)+';' set @sqlstr=@sqlstr+'declare @sqlstr varchar(6000);' if @ordertype=1 begin set @sqlstr=@sqlstr+'set @sqlstr=''select top '+cast(@Pagesize as varchar)+' from (select top 100 percent * from (select top ''+cast(@rnum as varchar)+'' * from where order by asc) as b order by paymoney desc) as a order by desc '';' end else begin set @sqlstr=@sqlstr+'set @sqlstr=''select top '+cast(@Pagesize as varchar)+' from (select top 100 percent * from (select top ''+cast(@rnum as varchar)+'' * from where order by desc) as b order by paymoney asc) as a order by asc '';' end set @sqlstr=@sqlstr+'if @Rcount>0 begin execute(@sqlstr) end' --print @sqlstr execute(@sqlstr) 在上面的代码中,还处理了没有符合条件结果的情况 第二种方案的思想是这样的,先用倒序的SELECT TOP (页序号+1)*页尺寸的方法取得AE结果集,再从AE结果集中用NOT IN 的方法排除掉用SELECT TOP 页序号*页尺寸的方法取得的结果集,最后,对目标结果集执行倒序,下面是实现的代码 create PROCEDURE dbo.GetPagingRecord ( @tablename varchar(100),--表名或视图表 @fieldlist varchar(4000)='*',--欲选择字段列表 @orderfield varchar(100),--排序字段 @keyfield varchar(100),--主键 @pageindex int,--页号,从0开始 @pagesize int=20,--页尺寸 @strwhere varchar(4000),--条件 @ordertype bit=1--排序,1,降序,0,升序 ) AS /**//* 名称:GetPagingRecord 作用:按任意字段进行排序分页 作者:菩提树(MARK MA) 时间:2004-12-14 声明:此代码你可以无偿使用及转载,但在转载时,请勿移称本文字声明 */ SET NOCOUNT ON declare @sqlstr varchar(6000) --处理SQL中危险字符,并且将条件处理成易嵌入的形式 set @strwhere=replace(@strwhere,'''','''''') set @strwhere=replace(@strwhere,'--','') set @strwhere=replace(@strwhere,';','') set @sqlstr='declare @CurPageNum int;' set @sqlstr=@sqlstr+'declare @nextpagenum int;' set @sqlstr=@sqlstr+'set @curpagenum='+cast(@PageIndex as varchar)+'*'+cast(@Pagesize as varchar)+';' set @sqlstr=@sqlstr+'set @nextpagenum='+cast(@PageIndex+1 as varchar)+'*'+cast(@Pagesize as varchar)+';' set @sqlstr=@sqlstr+'declare @sqlstr varchar(6000);' if @ordertype=1 begin set @sqlstr=@sqlstr+'set @sqlstr=''select from ( select top ''+cast(@nextpagenum as varchar)+'' * from where order by desc ) as a where not in ( select top ''+cast(@curpagenum as varchar)+'' from where order by desc) order by desc'';' end else begin set @sqlstr=@sqlstr+'set @sqlstr=''select from ( select top ''+cast(@nextpagenum as varchar)+'' * from where order by asc ) as a where not in ( select top ''+cast(@curpagenum as varchar)+'' from where order by asc) order by asc'';' end set @sqlstr=@sqlstr+'execute( @sqlstr)' --print @sqlstr execute(@sqlstr) 需要注意的是,如果要避免SQL注入式***,请注意处理像分号,双减号,单引号这些在SQL中有特殊含义的字符至于上面两个存储过程哪个性能更好,那就取决于是对一个倒序的结果集再进入一次反序排列好呢,还是用NOT IN从一个大的结果集移除一个小的结果集好
学术名叫 derived table
上面这个实现的方法应该知道的人不少吧?
需要唯一主键的原因是可以用in
使用From子句”这个主题也提到关于在From子句中把另一个Select子句作为参数的使用方法。
子查询的的勘套.以及连接查询 子查询里面的连接查询不要使用同样的字段名,不然外面勘套的 select * 要报错。考虑性能最好不要用in 和 not in.
用以上代码修改的兼容网上流行的"通用分页存储过程"
CREATE PROCEDURE pagination ( @tablename varchar(255), --表名或视图表 @fieldlist varchar(1000)='*', --欲选择字段列表 @orderfield varchar(255)='', --排序字段 @pagesize int=10, --页尺寸 @pageindex int=1, --页号 @docount bit=0, --返回记录总数, 非 0 值则返回 @ordertype bit=0, --排序,1,降序,0,升序 @strwhere varchar(1500)='' -- 查询条件 (注意: 不要加 where) ) AS /* 名称:GetPagingRecord 作用:按任意字段进行排序分页 作者:菩提树(MARK MA) 时间:2004-12-14 声明:此代码你可以无偿使用及转载,但在转载时,请勿移称本文字声明 修改:木木(mumuqz) 2005-09-14 */ SET NOCOUNT ON declare @sqlstr varchar(6000) set @sqlstr='declare @rcount int;'if @doCount != 0 -----------------------------------------------统计总记录数
begin if @strwhere !='' begin set @sqlstr = 'select count(*) as Total from ' + @tablename + ' where set @strwhere=replace(@strwhere,'''','''''') ------处理SQL中危险字符,并且将条件处理成易嵌入的形式 set @strwhere=replace(@strwhere,'--','') set @strwhere=replace(@strwhere,';','') end else begin set @sqlstr = 'select count(*) as Total from ' + @tablename end end else -----------------------------------------------------------分页显示记录 begin if @strwhere !='' ----------------------------------------当查询条件不为空 begin set @sqlstr=@sqlstr+'set @rcount=(select count(*) from where set @strwhere=replace(@strwhere,'''','''''') set @strwhere=replace(@strwhere,'--','') set @strwhere=replace(@strwhere,';','') set @sqlstr=@sqlstr+'declare @rnum int;' set @sqlstr=@sqlstr+'set @rnum=@rcount-'+cast(@pagesize as varchar)+'*('+cast(@pageindex as varchar)+'-1);' set @sqlstr=@sqlstr+'declare @sqlstr varchar(6000);' if @ordertype=1 begin set @sqlstr=@sqlstr+'set @sqlstr=''select top '+cast(@Pagesize as varchar)+' from (select top 100 percent * from (select top ''+cast(@rnum as varchar)+'' * from where order by asc) as b order by desc) as a order by desc '';' end else begin set @sqlstr=@sqlstr+'set @sqlstr=''select top '+cast(@Pagesize as varchar)+' from (select top 100 percent * from (select top ''+cast(@rnum as varchar)+'' * from where order by desc) as b order by asc) as a order by asc '';' end set @sqlstr=@sqlstr+'execute(@sqlstr)' end else --------------------------------------------------------------当查询条件为空 begin set @sqlstr=@sqlstr+'set @rcount=(select count(*) from set @sqlstr=@sqlstr+'declare @rnum int;' set @sqlstr=@sqlstr+'set @rnum=@rcount-'+cast(@pagesize as varchar)+'*('+cast(@pageindex as varchar)+'-1);' set @sqlstr=@sqlstr+'declare @sqlstr varchar(6000);' if @ordertype=1 begin set @sqlstr=@sqlstr+'set @sqlstr=''select top '+cast(@Pagesize as varchar)+' from (select top 100 percent * from (select top ''+cast(@rnum as varchar)+'' * from order by asc) as b order by desc) as a order by desc '';' end else begin set @sqlstr=@sqlstr+'set @sqlstr=''select top '+cast(@Pagesize as varchar)+' from (select top 100 percent * from (select top ''+cast(@rnum as varchar)+'' * from order by desc) as b order by asc) as a order by asc '';' end set @sqlstr=@sqlstr+'execute(@sqlstr)' end end --print @sqlstr execute(@sqlstr) GO