DECLARE @temp TABLE( PK /* PKType */ NOT NULL PRIMARY ) INSERT INTO @temp SELECT TOP @PageSize PK FROM ( SELECT TOP(@StartRow + @PageSize ) PK, SortColumn /* If sorting column is defferent from the PK,SortColumn must be fetched as well,otherwise just the PK is necessary */ ORDER BY SortColumn /* defaultorder–typicallyASC */ ) ORDER BY SortColumn /* reversed default order–typicallyDESC */ SELECT ![]() ORDER BY SortColumn /* defaultorder */ |
行計數
這個方法的基本邏輯依賴于SQL中的SET ROWCOUNT表達式,這樣可以跳過不必要的行并且獲得需要的行記錄:
DECLARE @Sort /* the type of the sorting column */ SET ROWCOUNT @StartRow SELECT @Sort=SortColumn FROM Table ORDER BY SortColumn SET ROWCOUNT @PageSize SELECT ![]() |
SELECT ![]() SELECT TOP @PageSize PK FROM Table WHERE PK NOT IN ( SELECT TOP @StartRow PK FROM Table ORDER BY SortColumn) ORDER BY SortColumn) ORDER BY SortColumn |
DECLARE @PK /* PKType */ DECLARE @tblPK TABLE( PK /*PKType*/ NOT NULL PRIMARY KEY ) DECLARE PagingCursor CURSOR DYNAMICREAD_ONLY FOR SELECT @PK FROM Table ORDER BY SortColumn OPEN PagingCursor FETCH RELATIVE @StartRow FROM PagingCursor INTO @PK WHILE @PageSize>0 AND @@FETCH_STATUS =0 BEGIN INSERT @tblPK(PK) VALUES(@PK) FETCH NEXT FROM PagingCursor INTO @PK SET @PageSize = @PageSize - 1 END CLOSE PagingCursor DEALLOCATE PagingCursor SELECT ![]() ORDER BY SortColumn |
SELECT Customers.ContactName AS Customer, Customers.Address + ' , ' + Customers.City + ', '+ Customers.Country AS Address, SUM([OrderDetails].UnitPrice*[ OrderDetails ] .Quantity) AS [Totalmoneyspent] FROM Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID INNER JOIN [ OrderDetails ] ON Orders.OrderID = [ OrderDetails].OrderID WHERE Customers.Country <> 'USA' AND Customers.Country <> 'Mexico ' GROUP BY Customers.ContactName,Customers.Address,Customers.City, Customers.Country HAVING(SUM([OrderDetails].UnitPrice * [ OrderDetails ] .Quantity)) > 1000 ORDER BY Customer DESC ,Address DESC |
EXEC ProcedureName /*Tables */ ' Customers INNER JOIN Orders ON Customers.CustomerID=Orders.CustomerID INNER JOIN [OrderDetails] ON Orders.OrderID=[OrderDetails].OrderID ' , /* PK */ ' Customers.CustomerID ' , /* ORDERBY */ ' Customers.ContactName DESC,Customers.AddressDESC ' , /*PageNumber */ 2 , /*PageSize */ 10 , /*Fields */ ' Customers.Contact Name AS Customer, Customers.Address+'' , '' +Customers.City+ '' , '' +Customers.Country ASAddress, SUM([OrderDetails].UnitPrice*[OrderDetails].Quantity)AS[Totalmoneyspent] ' , /*Filter */ ' Customers.Country<>'' USA '' ANDCustomers.Country<> '' Mexico ''' , /*GroupBy */ ' Customers.CustomerID,Customers.ContactName,Customers.Address, Customers.City,Customers.Country HAVING(SUM([OrderDetails].UnitPrice*[OrderDetails].Quantity))>1000 ' |
值得注意的是,在原始查詢中在ORDER BY語句中使用了別名,但你最好不要在分頁存儲過程中這么做,因為這樣的話跳過開始記錄之前的行是很消耗時間的。其實有很多種方法可以用于實現,但原則是不要在一開始把所有的字段包括進去,而僅僅是包括主鍵列(等同于RowCount方法中的排序列),這樣可以加快任務完成速度。只有在請求頁中,才獲得所有需要的字段。并且,在最終查詢中不存在字段別名,在跳行查詢中,必須提前使用索引列。
行計數(RowCount)存儲過程有一個另外的問題,要實現通用化,在ORDER BY語句中只允許有一個列,這也是升序-降序方法和游標方法的問題,雖然他們可以對幾個列進行排序,但是必須保證主鍵中只有一個字段。我猜如果用更多的動態SQL是可以解決這個問題的,但是在我看來這不是很值得。雖然這樣的情況很有可能發生,但他們發生的頻率不是很高。通常你可以用上面的原理也獨立的分頁存儲過程。
性能測試
在測試中,我使用了四種方法,如果你有更好的方法的話,我很有興趣知道。不管如何,我需要對這些方法進行比較,并且評估它們的性能。首先我的第一個想法就是寫一個asp.net包含分頁DataGrid的測試應用程序,然后測試頁面結果。當然,這無法反映存儲過程的真實響應時間,所以控制臺應用程序顯得更加適合。我還加入了一個Web應用程序,但不是為了性能測試,而是一個關于DataGrid自定義分頁和存儲過程一起工作的例子。
在測試中,我使用了一個自動生成得大數據表,大概插入了500000條數據。如果你沒有一張這樣的表來做實驗,你可以點擊這里下載一段用于生成數據的表設計和存儲過程腳本。我沒有使用一個自增的主鍵列,而是用一個唯一識別碼來識別記錄的。如果我使用上面提到的腳本,你可能會考慮在生成表之后添加一個自增列,這些自增數據會根據主鍵進行數字排序,這也意味著你打算用一個帶有主鍵排序的分頁存儲過程來獲得當前頁的數據。
為了實現性能測試,我是通過一個循環多次調用一個特定的存儲過程,然后計算平均相應時間來實現的。考慮到緩存的原因,為了更準確地建模實際情況——同一頁面對于一個存儲過程的多次調用獲得數據的時間通常是不適合用來做評估的,因此,我們在調用同一個存儲過程時,每一次調用所請求的頁碼應該是隨機的。當然,我們必須假設頁的數量是固定的,10-20頁,不同頁碼的數據可能被獲取很多次,但是是隨機獲取的。
有一點我們很容易注意到,相應時間是由要獲取的頁數據相對于結果集開始的位置的距離決定的,越是遠離結果集的開始位置,就有越多的記錄要跳過,這也是我為什么不把前20也包括進我的隨機序列的原因。作為替換,我會使用2的n次方個頁面,循環的大小是需要的不同頁的數量*1000,所以,每個頁面幾乎都被獲取了1000次(由于隨機原因,肯定會有所偏差)
結果
這里有我的測試結果:
結論
測試是按照從性能最好到最差的順序進行的——行計數、游標、升序-降序、子查詢。有一件事很有趣,通常人們很少會訪問前五頁之后的頁面,因此子查詢方法可能在這種情況下滿足你的需要,這得看你的結果集的大小和對于遠距離(distant)頁面的發生頻率預測,你也很有可能使用這些方法的組合模式。如果是我,在任何情況下,我都更喜歡用行計數方法,它運行起來十分不錯,即使對于第一頁也是如此,這里的“任何情況”代表了一些很難實現通用化的情況,在這種情況下,我會使用游標。(對于前兩種我可能使用子查詢方法,之后再用游標方法)