SQLSERVER 儲存過程實現分頁查詢 C#後臺獲取查詢結果集
阿新 • • 發佈:2019-01-04
一、為什麼要用分頁查詢
在列表查詢時由於資料量非常多,一次性查出來非常慢,也不能一次顯示給客戶端,特別是在使用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