1. 程式人生 > >SQLSERVER 儲存過程實現分頁查詢 C#後臺獲取查詢結果集

SQLSERVER 儲存過程實現分頁查詢 C#後臺獲取查詢結果集

一、為什麼要用分頁查詢
        在列表查詢時由於資料量非常多,一次性查出來非常慢,也不能一次顯示給客戶端,特別是在使用ExtJS的GridPanel時候,顯示資料量達到200條時對效能影響難以容忍,所以需要考慮將資料分批次查詢出來,每頁顯示一定量的資料,這就是資料要分頁,即需要分頁查詢(paging query)。
二、怎樣用分頁查詢
       分頁查詢思路有兩個,分別是在前臺和後臺實現分頁查詢。前臺了肯定不如後臺高效理想。所以在此,只對後臺實現做總結和歸納。在後臺實現分頁查詢也有不同的實現方法(方法肯定是多種多樣的),一個是在後臺獲取整個資料表,然後進行條件篩選和查詢,再就是直接在資料庫中實現分頁查詢,使用儲存過程將查詢的結果集返回(也可以不使用儲存過程,直接用SQL語句,儲存過程也是平時編寫的sql查詢語句,只不過是經過預先編譯存放在後臺,一次編譯多次使用,提升效能);當然後者即使用儲存過程是最理想的選擇。ok,來整理一下思路:1.資料庫系統中編寫儲存過程,2.後臺呼叫儲存過程(語言平臺)。知道做什麼 不知道怎麼做並不可怕,只要它是肯學習都可以逐個擊破。
三、儲存過程編寫
      儲存過程編寫,以例項講解
USE [Exam]
GO
/****** Object:  StoredProcedure [dbo].[spPager]    Script Date: 2014/12/28 23:42:02 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spPager]
	@Table		VARCHAR(50), --表名
	@pageIndex  INT,		--當前頁碼
	@PageSize	INT,		--每頁記錄數
	@Field		VARCHAR(1000)='*',	--篩選列即輸出欄位
	@Sort		VARCHAR(300)=NULL,   --排序欄位,不帶ORDER BY
	@Filter		VARCHAR(200)=NULL,   -- where過濾條件,不帶where
	@MaxPage	SMALLINT OUTPUT, -- 總頁數
	@TotalCount	INT OUTPUT,			--總記錄數
	@Descript	VARCHAR(100) OUTPUT	--查詢結果描述

AS
BEGIN
	DECLARE @PrimaryKey VARCHAR(50) -- 主鍵名	
  
	SET @PrimaryKey = 'id' --主鍵名稱	
	
	IF @Sort IS NULL OR @Sort = ''
		SET @Sort = @PrimaryKey
	
	--計算總記錄數
	DECLARE @TotalCountSql NVARCHAR(1000)
	SET @TotalCountSql = N'SELECT @TotalCount=COUNT(*)'+N' FROM '
[email protected]
+' WHERE '[email protected] -- 構造獲取滿足條件的總記錄數sql語句 EXEC sys.sp_executesql @TotalCountSql,N'@TotalCount INT OUTPUT',@TotalCount OUTPUT --執行 SET @MaxPage =CEILING(CAST(@TotalCount AS FLOAT)/CAST(@PageSize AS FLOAT)) --賦值分頁最大頁數 IF @pageIndex < 1 SET @pageIndex = 1 IF @pageIndex>
[email protected]
SET @pageIndex = @MaxPage --執行查詢語句 DECLARE @querySql VARCHAR(1000) IF @pageIndex<1 SET @pageIndex = 1 IF @pageIndex > @MaxPage SET @pageIndex = @MaxPage IF @pageIndex = 1 BEGIN SET @querySql = 'SELECT TOP '+STR(@PageSize)+' '[email protected]+' FROM '[email protected]+ ' WHERE '[email protected] +' ORDER BY '[email protected] END ELSE BEGIN SET @querySql = 'SELECT TOP '+STR(@PageSize)+' '[email protected]+' FROM '[email protected]+ ' WHERE '[email protected]+' AND '[email protected]+' NOT IN (SELECT TOP '+ STR((@pageIndex-1)* @PageSize)+' '+ @PrimaryKey +' FROM '+ @Table+' WHERE '[email protected]+' ORDER BY '[email protected] +') ORDER BY '[email protected] END EXEC(@querySql) --執行sql語句得到結果集,在後臺下載獲取 SET @Descript= 'succeed' END
四、後臺獲取結果集
再次僅以C#程式碼為例
using (DataTable tb = new DataTable())
            {
                using (SqlConnection conn = new SqlConnection(DA.STR_CONNECTION(database)))
                {
                    using (SqlCommand cmd = new SqlCommand("spPager", conn))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;
                        cmd.Parameters.AddWithValue("@pageIndex", page_index);
                        cmd.Parameters.AddWithValue("@PageSize", size);
                        cmd.Parameters.AddWithValue("@Table", table);
                        cmd.Parameters.AddWithValue("@Field", fields);
                        cmd.Parameters.AddWithValue("@Sort", order);
                        cmd.Parameters.AddWithValue("@Filter", condition);
                        cmd.Parameters.Add("@MaxPage", SqlDbType.SmallInt).Direction = ParameterDirection.Output;
                        cmd.Parameters.Add("@TotalCount", SqlDbType.Int).Direction = ParameterDirection.Output;
                        cmd.Parameters.Add("@Descript", SqlDbType.VarChar, 100).Direction = ParameterDirection.Output;
                        conn.Open();
                        tb.Load(cmd.ExecuteReader());--獲取結果集

                        result = cmd.Parameters["@Descript"].Value.ToString();
                        if (result.IndexOf("Error") >= 0)
                            throw (new Exception(result));
                        count = Int32.Parse(cmd.Parameters["@TotalCount"].Value.ToString());
                        pages = Int32.Parse(cmd.Parameters["@MaxPage"].Value.ToString());
                        return tb;
                    }
                }
            }     

五、常用分頁查詢方法

    我們經常會碰到要取n到m條記錄,就是有分頁思想,下面羅列一下一般的方法。
我本地的一張表 tbl_FlightsDetail,有300多W記錄,主鍵 FlightsDetailID(Guid),要求按照FlightsDetailID排序 取 3000001 到3000010 之間的10條記錄,也是百萬級。

方法1 定位法 (利用ID大於多少)

select top 10 * from tbl_FlightsDetail where FlightsDetailID>(
       select max(FlightsDetailID) from ( 
              select top 3000000 FlightsDetailID from tbl_FlightsDetail order by FlightsDetailID
       ) as t
) order by FlightsDetailID

執行計劃:

先查出 top 300000,再聚合取這個集合中最大的Id1,再過濾 id大於id1的集合(上圖中使用到索引),再取top 10 條。

方法2 (利用Not In)


語句形式:

select top 10* from tbl_FlightsDetail where FlightsDetailID not in (
       select top 3000000 FlightsDetailID from tbl_FlightsDetail order by FlightsDetailID
) order by FlightsDetailID

執行計劃:

和方法一類似,只是過濾where條件不一樣,這裡用到的是not in,上圖中沒有用到索引,耗時8秒。如果 FlightsDetailID不是索引的話,方法1和該方法將差不多。

方法3 (利用顛顛倒倒top)

語句形式:

select top 10* from (
       select top 3000010* from tbl_FlightsDetail order by FlightsDetailID
) as t  order by t.FlightsDetailID desc

執行計劃:

先取 前面3000010條記錄,再倒序,這時再取前面10條即是300001 到300010條記錄,沒有用到索引,耗時11秒

方法4 (ROW_NUMBER()函式)

語句形式:

select * from (
       select *,ROW_NUMBER() OVER (ORDER BY FlightsDetailID) as rank from tbl_FlightsDetail
)  as t where t.rank between 3000001 and 3000010

 執行計劃:

Sql 2005版本或以上支援,也沒用到索引,耗時2秒,速度還不錯。

方法5 (利用IN)

此方法是由 金色海洋(jyk)陽光男孩 回覆的,飛常感謝,語句形式:

select top 10 * from tbl_FlightsDetail  where FlightsDetailID in( 
       select top 10 FlightsDetailID from(  
              select top 3000010 FlightsDetailID from tbl_FlightsDetail order by FlightsDetailID
       ) as t order by t.FlightsDetailID desc 
) order by FlightsDetailID

執行計劃:

多次執行之後一般維持在4秒左右,用到索引,非常不錯,計劃圖還很長,只擷取部分,可能是繞的多一點。

3.千萬級分頁儲存過程

大家百度一下這個標題立馬會出現很多相關資訊,都大同小異,我自己拷貝的一個,應專案的需要,修改了一個排序的bug以及添加了返回總記錄數,如下:

複製程式碼
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
--分頁儲存過程  
CREATE PROCEDURE [dbo].[sp_Paging] 
( 
@Tables nvarchar(1000),                --表名/檢視名
@PrimaryKey nvarchar(100),             --主鍵
@Sort nvarchar(200) = NULL,            --排序欄位(不帶order by)
@pageindex int = 1,                    --當前頁碼
@PageSize int = 10,                    --每頁記錄數
@Fields nvarchar(1000) = N'*',         --輸出欄位
@Filter nvarchar(1000) = NULL,         --where過濾條件(不帶where)
@Group nvarchar(1000) = NULL,          --Group語句(不帶Group By)
@TotalCount int OUTPUT                 --總記錄數
) 
AS  
 
DECLARE @SortTable nvarchar(100) 
DECLARE @SortName nvarchar(100) 
DECLARE @strSortColumn nvarchar(200) 
DECLARE @operator char(2) 
DECLARE @type nvarchar(100) 
DECLARE @prec int 

--設定排序語句
IF @Sort IS NULL OR @Sort = ''     
   SET @Sort = @PrimaryKey      
IF CHARINDEX('DESC',@Sort)>0   
BEGIN         
    SET @strSortColumn = REPLACE(@Sort, 'DESC', '')         
    SET @operator = '<='     
END 
ELSE     
BEGIN                
    SET @strSortColumn = REPLACE(@Sort, 'ASC', '')                
    SET @operator = '>='     
END 
IF CHARINDEX('.', @strSortColumn) > 0     
BEGIN         
    SET @SortTable = SUBSTRING(@strSortColumn, 0, CHARINDEX('.',@strSortColumn))
    SET @SortName = SUBSTRING(@strSortColumn, CHARINDEX('.',@strSortColumn) + 1, LEN(@strSortColumn))     
END 
ELSE     
BEGIN         
    SET @SortTable = @Tables         
    SET @SortName = @strSortColumn  
END 

--設定排序欄位型別和精度 
SELECT @type=t.name, @prec=c.prec FROM sysobjects o 
       JOIN syscolumns c on o.id=c.id 
       JOIN systypes t on c.xusertype=t.xusertype WHERE o.name = @SortTable AND c.name = @SortName
        
IF CHARINDEX('char', @type) > 0    
   SET @type = @type + '(' + CAST(@prec AS varchar) + ')'
   
DECLARE @strPageSize nvarchar(50) 
DECLARE @strStartRow nvarchar(50) 
DECLARE @strFilter nvarchar(1000) 
DECLARE @strSimpleFilter nvarchar(1000) 
DECLARE @strGroup nvarchar(1000)  
 
IF @pageindex <1     
   SET @pageindex = 1  
SET @strPageSize = CAST(@PageSize AS nvarchar(50)) 
--設定開始分頁記錄數 
SET @strStartRow = CAST(((@pageindex - 1)*@PageSize + 1) AS nvarchar(50))  
--篩選以及分組語句
IF @Filter IS NOT NULL AND @Filter != ''     
BEGIN         
    SET @strFilter = ' WHERE ' + @Filter + ' ' 
    SET @strSimpleFilter = ' AND ' + @Filter + ' ' 
END 
ELSE     
BEGIN         
    SET @strSimpleFilter = ''         
    SET @strFilter = ''     
END 
IF @Group IS NOT NULL AND @Group != ''  
   SET @strGroup = ' GROUP BY ' 
--計算總記錄數
DECLARE @TotalCountSql nvarchar(1000)
SET @TotalCountSql=N'SELECT @TotalCount=COUNT(*)' +N' FROM ' + @Tables + @strFilter
EXEC sp_executesql @TotalCountSql,N'@TotalCount int OUTPUT',@TotalCount OUTPUT
--執行查詢語句    
EXEC(
'
DECLARE @SortColumn ' + @type + '
SET ROWCOUNT ' + @strStartRow + '
SELECT @SortColumn=' + @strSortColumn + ' FROM ' + @Tables + @strFilter + ' ' + @strGroup + ' ORDER BY ' + @Sort + '
SET ROWCOUNT ' + @strPageSize + '
SELECT ' + @Fields + ' FROM ' + @Tables + ' WHERE ' + @strSortColumn + @operator + ' @SortColumn ' + @strSimpleFilter + ' ' + @strGroup + ' ORDER BY ' + @Sort + '
'
)
複製程式碼

 現在我們來測試一下:

複製程式碼
DECLARE    @return_value int,
        @TotalCount int

EXEC    @return_value = [dbo].[sp_Paging]
        @Tables = N'tbl_FlightsDetail',
        @PrimaryKey = N'FlightsDetailID',
        @Sort = N'FlightsDetailID',
        @pageindex = 299999,
        @PageSize = 10,
        @Fields = '*',
        @Filter = NULL,
        @Group = NULL,
        @TotalCount = @TotalCount OUTPUT

SELECT    @TotalCount as N'@TotalCount'

SELECT    'Return Value' = @return_value
複製程式碼