按任意字段排序的分頁存儲過程

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 '+@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 #temp (oldid) '+@sqlstr
execute(@sqlstr)
set @sqlstr='SELECT '+@fldList+' FROM '+@tblname+' TableA (nolock), #temp 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)
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('+@keyfield+') from '+@tablename+' where '+@strWhere+');'     
     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)+' '+@fieldlist+' from (select top 100  

percent * from   (select top   ''+cast(@rnum as varchar)+'' * from '+@tablename+' where '+@strwhere+'  

order by '+@orderfield+' asc) as b order by paymoney desc) as a order by '+@orderfield+' desc '';'
     end
     else
     begin
     set @sqlstr=@sqlstr+'set @sqlstr=''select top '+cast(@Pagesize as varchar)+' '+@fieldlist+' from (select top 100  

percent * from   (select top   ''+cast(@rnum as varchar)+'' * from '+@tablename+' where '+@strwhere+'  

order by '+@orderfield+' desc) as b order by paymoney asc) as a order by '+@orderfield+' 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 '+@fieldlist+' from ( select top ''+cast(@nextpagenum as varchar)+'' * from  

'+@tablename+'   where '+@strwhere+' order by '+@orderfield+' desc ) as a where '+@keyfield+' not in (  

select top ''+cast(@curpagenum as varchar)+'' '+@keyfield+' from '+@tablename+' where '+@strwhere+'  

order by '+@orderfield+' desc) order by '+@orderfield+' desc'';'
     end
     else
     begin
     set @sqlstr=@sqlstr+'set @sqlstr=''select '+@fieldlist+' from ( select top ''+cast(@nextpagenum as varchar)+'' * from  

'+@tablename+'   where '+@strwhere+' order by '+@orderfield+' asc ) as a where '+@keyfield+' not in (  

select top ''+cast(@curpagenum as varchar)+'' '+@keyfield+' from '+@tablename+' where '+@strwhere+'  

order by '+@orderfield+' asc) order by '+@orderfield+' 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 '+@strwhere
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 '+@tablename+' where '+@strwhere+');'
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)+' '+@fieldlist+' from (select top 100 percent * from (select top ''+cast(@rnum as varchar)+'' * from '+@tablename+' where '+@strwhere+' order by '+@orderfield+' asc) as b order by '+@orderfield+' desc) as a order by '+@orderfield+' desc '';'
end
else
begin
set @sqlstr=@sqlstr+'set @sqlstr=''select top '+cast(@Pagesize as varchar)+' '+@fieldlist+' from (select top 100 percent * from (select top ''+cast(@rnum as varchar)+'' * from '+@tablename+' where '+@strwhere+' order by '+@orderfield+' desc) as b order by '+@orderfield+' asc) as a order by '+@orderfield+' asc '';'
end
set @sqlstr=@sqlstr+'execute(@sqlstr)'
end
else --------------------------------------------------------------當查詢條件爲空
begin
set @sqlstr=@sqlstr+'set @rcount=(select count(*) from '+@tablename+');'
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)+' '+@fieldlist+' from (select top 100 percent * from (select top ''+cast(@rnum as varchar)+'' * from '+@tablename+' order by '+@orderfield+' asc) as b order by '+@orderfield+' desc) as a order by '+@orderfield+' desc '';'
end
else
begin
set @sqlstr=@sqlstr+'set @sqlstr=''select top '+cast(@Pagesize as varchar)+' '+@fieldlist+' from (select top 100 percent * from (select top ''+cast(@rnum as varchar)+'' * from '+@tablename+' order by '+@orderfield+' desc) as b order by '+@orderfield+' asc) as a order by '+@orderfield+' asc '';'
end
set @sqlstr=@sqlstr+'execute(@sqlstr)'
end
end
--print @sqlstr
execute(@sqlstr)
GO

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章