今天和朋友討論分頁,發現網上好多都是錯的。網上經常查到的那個Top Not in 或者Max 大部分都不實用,很多都忽略了Order和性能問題。為此上網查了查,順帶把2000和2012版本的也補上了。
先說說網上常見SQL的錯誤或者說局限問題
select top 30 * from table1 where id not in( select top 開始的位置 id from table1)
這樣的確是可以取到分頁數據,但是這是默認排序的,如果要按其中一列排序呢?那order by 加在哪里呢?里外都加,顯然不行,外面的Order不起作用,只能嵌套,Oh my god,編程三個Select了,這效率。
為了好用效率高,總體思路還是老老實實的用RowNumber解決,但是SQL2000沒有RowNumber,其實我們可以通過臨時表自增列搞定,不多說,上例子。
SQL 2000 用臨時表解決,通過在臨時表中增加自增列解決RowNumber。
DECLARE @Start INT DECLARE @End INT SELECT @Start = 13000,@End = 13050 CREATE TABLE #employees (RowNumber INT IDENTITY(1,1), LastName VARCHAR(100),FirstName VARCHAR(100), EmailAddress VARCHAR(100)) INSERT INTO #employees (LastName, FirstName, EmailAddress) SELECT LastName, FirstName, EmailAddress FROM Employee ORDER BY LastName, FirstName, EmailAddress SELECT LastName, FirstName, EmailAddress FROM #employees WHERE RowNumber > @Start AND RowNumber <= @End DROP TABLE #employees GO
SQL 2005/2008 由於支持了Row_Number於是通過派生表的方式解決(兩個嵌套)
DECLARE @Start INT DECLARE @End INT SELECT @Start = 13000,@End = 13050 SELECT LastName, FirstName, EmailAddress FROM (SELECT LastName, FirstName, EmailAddress, ROW_NUMBER() OVER (ORDER BY LastName, FirstName, EmailAddress) AS RowNumber FROM Employee) EmployeePage WHERE RowNumber > @Start AND RowNumber <= @End ORDER BY LastName, FirstName, EmailAddress GO
SQL 2005/2008 或者用CTE的方式實現,和派生表一樣,就是好看點,執行計划都一樣。
DECLARE @Start INT DECLARE @End INT SELECT @Start = 13000,@End = 13050; WITH EmployeePage AS (SELECT LastName, FirstName, EmailAddress, ROW_NUMBER() OVER (ORDER BY LastName, FirstName, EmailAddress) AS RowNumber FROM Employee) SELECT LastName, FirstName, EmailAddress FROM EmployeePage WHERE RowNumber > @Start AND RowNumber <= @End ORDER BY LastName, FirstName, EmailAddress GO
SQL SERVER 2012 比較給力支持了OFFSET,於是一個Select結束戰斗
SELECT LastName, FirstName, EmailAddress FROM Employee ORDER BY LastName, FirstName, EmailAddress OFFSET 13000 ROWS FETCH NEXT 50 ROWS ONLY;
最后說下,根據老外的文章,在2012里,如果前面加上TOP(50),那么執行計划就會少讀很多行數據(讀的精准了),提高性能。但是鑒於本人手頭沒2012也無法測試。至少在2008R2上加不加TOP執行計划都一樣。
另外說一下SQL Server 2012的OFFSET-FETCH篩選
TOP選項是一個非常實用的篩選類型,但它有兩個缺陷——不是標准SQL,且不支持跳過功能。標准SQL定義的TOP類似篩選稱為OFFSET-FETCH,支持跳過功能,這對針對特定頁面的查詢非常有用。SQL Server2012引入了對OFFSET-FETCH篩選的支持。
SQL Server 2012中的OFFSET-FETCH篩選被視為ORDER BY子句的一部分,通常用於實現按順序顯示效果。OFFSET子句指定要跳過的行數,FETCH子句指定在跳過的行數后要篩選的行數。請思考一下下面的查詢示例。
SELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate, orderid
OFFSET 600 ROWS FETCH NEXT 25 ROWS ONLY;
此查詢按orderdate、orderid順序(訂單日期從最遠到最近,並添加了決勝屬性(tiebreaker)orderid)排序Orders表中的行。基於此順序,OFFSET子句跳過前50行,由FETCH子句僅篩選下面的25行。
請注意,使用OFFSET-FETCH的查詢必須具有ORDER BY子句。此外,FETCH子句不支持沒有OFFSET子句。如果你不想跳過任何行,但是希望使用FETCH篩選,你應當使用OFFSET 0 ROWS來表示。不過,沒有FETCH的OFFSET是允許的,這種情況是跳過指定的行數,並返回查詢結果中所有剩余行。
OFFSET-FETCH語法有一些有趣的語言方面需要注意。單數格式ROW和復數格式ROWS是可以互換的,此舉是讓你能夠以直觀的類似英語方式來描述篩選。例如,假設你僅希望獲取一行,如果你指定了FETCH 1 ROWS,雖然這在語法上是有效的,不過看上去會很怪。因此,你可以使用FETCH 1 ROW格式。此互換同樣適用於OFFSET子句。另外,如果你不希望跳過任何行(OFFSET 0 ROWS),你可能覺得“first”比“next”更合適,因此,FIRST 和NEXT格式是可以互換的。
如你所見,從支持跳過功能看,OFFSET-FETCH子句比TOP子句更靈活。不過,OFFSET-FETCH 不支持PERCENT和WITH TIES選項,而TOP支持。由於OFFSET-FETCH是標准的,而TOP不是,我建議使用OFFSET-FETCH作為你的默認選擇,除非你需要TOP支持且OFFSET-FETCH不支持的功能。