排名函數是SQL Server2005新加的功能。在SQL Server2005中有以下四個排名函數: 1.row_number 2.rank 3.dense_rank 4.ntile 下面分別介紹一下這四個排名函數的功能及用法。在介紹以前假設有一個t_table表,表結構與表中的數據如圖1所示:其中field1字段的類型是int,field2字段的類型是varchar 1、row_number row_number函數的用途是很是普遍,這個函數的功能是爲查詢出來的每一行記錄生成一個序號。row_number函數的用法以下面的SQL語句所示: select row_number() over(order by field1) as row_number,* fromt_table 上面的SQL語句的查詢結果如圖2所示。
圖2 其中row_number列是由row_number函數生成的序號列。在使用row_number函數是要使用over子句選擇對某一列進行排序,而後才能生成序號。 實際上,row_number函數生成序號的基本原理是先使用over子句中的排序語句對記錄進行排序,而後按着這個順序生成序號。over子句中的order by子句與SQL語句中的order by子句沒有任何關係,這兩處的order by 能夠徹底不一樣,以下面的SQL語句所示 select row_number() over(order by field2 desc) as row_number,*from t_table order by field1 desc 上面的SQL語句的查詢結果如圖3所示。
圖3 咱們可使用row_number函數來實現查詢表中指定範圍的記錄,通常將其應用到Web應用程序的分頁功能上。下面的SQL語句能夠查詢t_table表中第2條和第3條記錄: with t_rowtable as ( select row_number() over(order by field1) as row_number,*from t_table ) select * from t_rowtable where row_number>1 and row_number<4 order by field1 上面的SQL語句的查詢結果如圖4所示。
圖4 上面的SQL語句使用了CTE,關於CTE的介紹將讀者參閱《SQL Server2005雜談(1):使用公用表表達式(CTE)簡化嵌套SQL》。 另外要注意的是,若是將row_number函數用於分頁處理,over子句中的order by 與排序記錄的order by 應相同,不然生成的序號可能不是有續的。 固然,不使用row_number函數也能夠實現查詢指定範圍的記錄,就是比較麻煩。通常的方法是使用顛倒Top來實現,例如,查詢t_table表中第2條和第3條記錄,能夠先查出前3條記錄,而後將查詢出來的這三條記錄按倒序排序,再取前2條記錄,最後再將查出來的這2條記錄再按倒序排序,就是最終結果。SQL語句以下: select * from(select top2 * from(select top3 * from t_table order by field1)a order by field1 desc) b order by field1 上面的SQL語句查詢出來的結果如圖5所示。
圖5 這個查詢結果除了沒有序號列row_number,其餘的與圖4所示的查詢結果徹底同樣。 2、rank rank函數考慮到了over子句中排序字段值相同的狀況,爲了更容易說明問題,在t_table表中再加一條記錄,如圖6所示。
圖6 在圖6所示的記錄中後三條記錄的field1字段值是相同的。若是使用rank函數來生成序號,這3條記錄的序號是相同的,而第4條記錄會根據當前的記錄數生成序號,後面的記錄依此類推,也就是說,在這個例子中,第4條記錄的序號是4,而不是2。rank函數的使用方法與row_number函數徹底相同,SQL語句以下: select rank() over(order by field1),* from t_table order by field1 上面的SQL語句的查詢結果如圖7所示。
圖7 3、dense_rank dense_rank函數的功能與rank函數相似,只是在生成序號時是連續的,而rank函數生成的序號有可能不連續。如上面的例子中若是使用dense_rank函數,第4條記錄的序號應該是2,而不是4。以下面的SQL語句所示: select dense_rank() over(order by field1),* from t_table order by field1 上面的SQL語句的查詢結果如圖8所示。
圖8 讀者能夠比較圖7和圖8所示的查詢結果有什麼不一樣 4、ntile ntile函數能夠對序號進行分組處理。這就至關於將查詢出來的記錄集放到指定長度的數組中,每個數組元素存放必定數量的記錄。ntile函數爲每條記錄生成的序號就是這條記錄全部的數組元素的索引(從1開始)。也能夠將每個分配記錄的數組元素稱爲「桶」。ntile函數有一個參數,用來指定桶數。下面的SQL語句使用ntile函數對t_table表進行了裝桶處理: select ntile(4) over(order by field1)as bucket,* from t_table 上面的SQL語句的查詢結果如圖9所示。
圖9 因爲t_table表的記錄總數是6,而上面的SQL語句中的ntile函數指定了桶數爲4。 也許有的讀者會問這麼一個問題,SQL Server2005怎麼來決定某一桶應該放多少記錄呢?可能t_table表中的記錄數有些少,那麼咱們假設t_table表中有59條記錄,而桶數是5,那麼每一桶應放多少記錄呢? 實際上經過兩個約定就能夠產生一個算法來決定哪個桶應放多少記錄,這兩個約定以下: 1.編號小的桶放的記錄不能小於編號大的桶。也就是說,第1捅中的記錄數只能大於等於第2桶及之後的各桶中的記錄。 2.全部桶中的記錄要麼都相同,要麼從某一個記錄較少的桶開始後面全部捅的記錄數都與該桶的記錄數相同。也就是說,若是有個桶,前三桶的記錄數都是10,而第4捅的記錄數是6,那麼第5桶和第6桶的記錄數也必須是6。 根據上面的兩個約定,能夠得出以下的算法: //mod表示取餘,div表示取整 if(記錄總數mod桶數==0) { recordCount=記錄總數div桶數; 將每桶的記錄數都設爲recordCount } else { recordCount1=記錄總數div桶數+1; intn=1; // n表示桶中記錄數爲recordCount1的最大桶數 m=recordCount1*n; while(((記錄總數-m) mod (桶數- n)) !=0) { n++; m=recordCount1*n; } recordCount2=(記錄總數-m)div (桶數-n); 將前n個桶的記錄數設爲recordCount1 將n+1個至後面全部桶的記錄數設爲recordCount2 } 根據上面的算法,若是記錄總數爲59,桶數爲5,則前4個桶的記錄數都是12,最後一個桶的記錄數是11。 若是記錄總數爲53,桶數爲5,則前3個桶的記錄數爲11,後2個桶的記錄數爲10。 就拿本例來講,記錄總數爲6,桶數爲4,則會算出recordCount1的值爲2,在結束while循環後,會算出recordCount2的值是1,所以,前2個桶的記錄是2,後2個桶的記錄是1。 ROW_NUMBER、RANK、DENSE_RANK 和 NTILE,這些新函數使您能夠有效地分析數據以及向查詢的結果行提供排序值。您可能發現這些新函數有用的典型方案包括:將連續整數分配給結果行,以便進行表示、分頁、計分和繪製直方圖。 Speaker Statistics 方案 下面的 Speaker Statistics 方案將用來討論和演示不一樣的函數和它們的子句。大型計算會議包括三個議題:數據庫、開發和系統管理。十一位演講者在會議中發表演講,而且爲他們的講話得到 範圍爲 1 到 9 的分數。結果被總結並存儲在下面的 SpeakerStats 表中: CREATE TABLE SpeakerStats( speaker VARCHAR(10) NOT NULL PRIMARY KEY , track VARCHAR(10) NOT NULL , score INT NOT NULL , pctfilledevals INT NOT NULL , numsessions INT NOT NULL) SET NOCOUNT ON INSERT INTO SpeakerStats VALUES('Dan', 'Sys', 3, 22, 4) INSERT INTO SpeakerStats VALUES('Ron', 'Dev', 9, 30, 3) INSERT INTO SpeakerStats VALUES('Kathy', 'Sys', 8, 27, 2) INSERT INTO SpeakerStats VALUES('Suzanne', 'DB', 9, 30, 3) INSERT INTO SpeakerStats VALUES('Joe', 'Dev', 6, 20, 2) INSERT INTO SpeakerStats VALUES('Robert', 'Dev', 6, 28, 2) INSERT INTO SpeakerStats VALUES('Mike', 'DB', 8, 20, 3) INSERT INTO SpeakerStats VALUES('Michele', 'Sys', 8, 31, 4) INSERT INTO SpeakerStats VALUES('Jessica', 'Dev', 9, 19, 1) INSERT INTO SpeakerStats VALUES('Brian', 'Sys', 7, 22, 3) INSERT INTO SpeakerStats VALUES('Kevin', 'DB', 7, 25, 4) 每一個演講者都在該表中具備一個行,其中含有該演講者的名字、議題、平均得分、填寫評價的與會者相對於參加會議的與會者數量的百分比以及該演講者發表演講的次數。本節演示如何使用新的排序函數分析演講者統計數據以生成有用的信息。 ROW_NUMBER ROW_NUMBER 函數使您能夠向查詢的結果行提供連續的整數值。例如,假設您要返回全部演講者的 speaker、track 和 score,同時按照 score 降序向結果行分配從 1 開始的連續值。如下查詢經過使用 ROW_NUMBER 函數並指定 OVER (ORDER BY score DESC) 生成所需的結果: SELECT ROW_NUMBER() OVER(ORDER BY score DESC) AS rownum, speaker, track, scoreFROM SpeakerStatsORDER BY score DESC如下爲結果集: rownum speaker track score ------ ---------- ---------- ----------- 1 Jessica Dev 9 2 Ron Dev 9 3 Suzanne DB 9 4 Kathy Sys 8 5 Michele Sys 8 6 Mike DB 8 7 Kevin DB 7 8 Brian Sys 7 9 Joe Dev 6 10 Robert Dev 6 11 Dan Sys 3 得 分最高的演講者得到行號 1,得分最低的演講者得到行號 11。ROW_NUMBER 老是按照請求的排序爲不一樣的行生成不一樣的行號。請注意,若是在 OVER() 選項中指定的 ORDER BY 列表不惟一,則結果是不肯定的。這意味着該查詢具備一個以上正確的結果;在該查詢的不一樣調用中,可能得到不一樣的結果。例如,在咱們的示例中,有三個不一樣的 演講者得到相同的最高得分 (9):Jessica、Ron 和 Suzanne。因爲 SQL Server 必須爲不一樣的演講者分配不一樣的行號,所以您應當假設分別分配給 Jessica、Ron 和 Suzanne 的值 1、2 和 3 是按任意順序分配給這些演講者的。若是值 1、2 和 3 被分別分配給 Ron、Suzanne 和 Jessica,則結果應該一樣正確。 如 果您指定一個惟一的 ORDER BY 列表,則結果老是肯定的。例如,假設在演講者之間出現得分相同的狀況時,您但願使用最高的 pctfilledevals 值來分出前後。若是值仍然相同,則使用最高的 numsessions 值來分出前後。最後,若是值仍然相同,則使用最低詞典順序 speaker 名字來分出前後。因爲 ORDER BY 列表 — score、pctfilledevals、numsessions 和 speaker — 是惟一的,所以結果是肯定的: SELECT ROW_NUMBER() OVER(ORDER BY score DESC, pctfilledevals DESC, numsessions DESC, speaker) AS rownum, speaker, track, score, pctfilledevals, numsessionsFROM SpeakerStatsORDER BY score DESC, pctfilledevals DESC, numsessions DESC, speaker如下爲結果集: rownum speaker track score pctfilledevals numsessions ------ ---------- ---------- ----------- -------------- ----------- 1 Ron Dev 9 30 3 2 Suzanne DB 9 30 3 3 Jessica Dev 9 19 1 4 Michele Sys 8 31 4 5 Kathy Sys 8 27 2 6 Mike DB 8 20 3 7 Kevin DB 7 25 4 8 Brian Sys 7 22 3 9 Robert Dev 6 28 2 10 Joe Dev 6 20 2 11 Dan Sys 3 22 4 新的排序函數的重要好處之一是它們的效率。SQL Server 的優化程序只須要掃描數據一次,以便計算值。它完成該工做的方法是:使用在排序列上放置的索引的有序掃描,或者,若是未建立適當的索引,則掃描數據一次並對其進行排序。 另外一個好處是語法的簡單性。爲了讓您感覺一下經過使用在 SQL Server 的較低版本中採用的基於集的方法來計算排序值是多麼困難和低效,請考慮下面的 SQL Server 2000 查詢,它返回與上一個查詢相同的結果: SELECT (SELECT COUNT(*) FROM SpeakerStats AS S2 WHERE S2.score > S1.score OR (S2.score = S1.score AND S2.pctfilledevals > S1.pctfilledevals) OR (S2.score = S1.score AND S2.pctfilledevals = S1.pctfilledevals AND S2.numsessions > S1.numsessions) OR (S2.score = S1.score AND S2.pctfilledevals = S1.pctfilledevals AND S2.numsessions = S1.numsessions AND S2.speaker < S1.speaker) ) + 1 AS rownum , speaker, track, score, pctfilledevals, numsessions FROM SpeakerStats AS S1 ORDER BY score DESC, pctfilledevals DESC, numsessions DESC, speaker 該查詢顯然比 SQL Server 2005 查詢複雜得多。此外,對於 SpeakerStats 表中的每一個基礎行,SQL Server 都必須掃描該表的另外一個實例中的全部匹配行。對於基礎表中的每一個行,平均大約須要掃描該表的一半(最少)行。SQL Server 2005 查詢的性能惡化是線性的,而 SQL Server 2000 查詢的性能惡化是指數性的。即便是在至關小的表中,性能差別也是顯著的。 行號的一個典型應用是經過查詢結果分頁。給定頁大小(以行數爲單位)和頁號,須要返回屬於給定頁的行。例如,假設您但願按照「score DESC, speaker」順序從 SpeakerStats 表中返回第二頁的行,而且假定頁大小爲三行。下面的查詢首先按照指定的排序計算派生表 D 中的行數,而後只篩選行號爲 4 到 6 的行(它們屬於第二頁): SELECT * FROM (SELECT ROW_NUMBER() OVER(ORDER BY score DESC, speaker) AS rownum, speaker, track, score FROM SpeakerStats) AS D WHERE rownum BETWEEN 4 AND 6 ORDER BY score DESC, speaker 如下爲結果集: rownum speaker track score ------ ---------- ---------- ----------- 4 Kathy Sys 8 5 Michele Sys 8 6 Mike DB 8 用更通常的術語表達就是,給定 @pagenum 變量中的頁號和 @pagesize 變量中的頁大小,如下查詢返回屬於預期頁的行: DECLARE @pagenum AS INT, @pagesize AS INT SET @pagenum = 2 SET @pagesize = 3 SELECT * FROM (SELECT ROW_NUMBER() OVER(ORDER BY score DESC, speaker) AS rownum ,speaker , track , score FROM SpeakerStats) AS DWHERE rownum BETWEEN (@pagenum-1)*@pagesize+1 AND @pagenum*@pagesize ORDER BY score DESC, speaker 上述方法對於您只對行的一個特定頁感興趣的特定請求而言已經足夠了。可是,當用戶發出多個請求時,該方法就不能知足須要了,由於該查詢的每一個調用都 須要您對錶進行完整掃描,以便計算行號。當用戶可能反覆請求不一樣的頁時,爲了更有效地進行分頁,請首先用全部基礎錶行(包括計算獲得的行號)填充一個臨時 表,而且對包含這些行號的列進行索引: SELECT ROW_NUMBER() OVER(ORDER BY score DESC, speaker) AS rownum, * INTO #SpeakerStatsRN FROM SpeakerStats CREATE UNIQUE CLUSTERED INDEX idx_uc_rownum ON #SpeakerStatsRN(rownum) 而後,對於所請求的每一個頁,發出如下查詢: SELECT rownum, speaker, track, score FROM #SpeakerStatsRN WHERE rownum BETWEEN (@pagenum-1)*@pagesize+1 AND @pagenum*@pagesize ORDER BY score DESC, speaker 只有屬於預期頁的行纔會被掃描。 分段 能夠在行組內部獨立地計算排序值,而不是爲做爲一個組的全部錶行計算排序值。爲此,請使用 PARTITION BY 子句,而且指定一個表達式列表,以標識應該爲其獨立計算排序值的行組。例如,如下查詢按照「score DESC, speaker」順序單獨分配每一個 track 內部的行號: SELECT track, ROW_NUMBER() OVER( PARTITION BY track ORDER BY score DESC, speaker) AS pos, speaker, score FROM SpeakerStats ORDER BY track, score DESC, speaker 如下爲結果集: track pos speaker score ---------- --- ---------- ----------- DB 1 Suzanne 9 DB 2 Mike 8 DB 3 Kevin 7 Dev 1 Jessica 9 Dev 2 Ron 9 Dev 3 Joe 6 Dev 4 Robert 6 Sys 1 Kathy 8 Sys 2 Michele 8 Sys 3 Brian 7 Sys 4 Dan 3 在 PARTITION BY 子句中指定 track 列會使得爲具備相同 track 的每一個行組單獨計算行號。 RANK, DENSE_RANK RANK 和 DENSE_RANK 函數很是相似於 ROW_NUMBER 函數,由於它們也按照指定的排序提供排序值,並且能夠根據須要在行組(分段)內部提供。可是,與 ROW_NUMBER 不一樣的是,RANK 和 DENSE_RANK 向在排序列中具備相同值的行分配相同的排序。當 ORDER BY 列表不惟一,而且您不但願爲在 ORDER BY 列表中具備相同值的行分配不一樣的排序時,RANK 和 DENSE_RANK 頗有用。RANK 和 DENSE_RANK 的用途以及二者之間的差別能夠用示例進行最好的解釋。如下查詢按照 score DESC 順序計算不一樣演講者的行號、排序和緊密排序值: SELECT speaker, track, score, ROW_NUMBER() OVER(ORDER BY score DESC) AS rownum, RANK() OVER(ORDER BY score DESC) AS rnk, DENSE_RANK() OVER(ORDER BY score DESC) AS drnk FROM SpeakerStats ORDER BY score DESC 如下爲結果集: speaker track score rownum rnk drnk ---------- ---------- ----------- ------ --- ---- Jessica Dev 9 1 1 1 Ron Dev 9 2 1 1 Suzanne DB 9 3 1 1 Kathy Sys 8 4 4 2 Michele Sys 8 5 4 2 Mike DB 8 6 4 2 Kevin DB 7 7 7 3 Brian Sys 7 8 7 3 Joe Dev 6 9 9 4 Robert Dev 6 10 9 4 Dan Sys 3 11 11 5 正 如前面討論的那樣,score 列不惟一,所以不一樣的演講者可能具備相同的得分。行號確實表明降低的 score 順序,可是具備相同得分的演講者仍然得到不一樣的行號。可是請注意,在結果中,全部具備相同得分的演講者都得到相同的排序和緊密排序值。換句話說,當 ORDER BY 列表不惟一時,ROW_NUMBER 是不肯定的,而 RANK 和 DENSE_RANK 老是肯定的。排序值和緊密排序值之間的差別在於,排序表明:具備較高得分的行號加 1,而緊密排序表明:具備明顯較高得分的行號加 1。從您迄今爲止已經瞭解的內容中,您能夠推導出當 ORDER BY 列表惟一時,ROW_NUMBER、RANK 和 DENSE_RANK 產生徹底相同的值。 NTILE NTILE 使您能夠按照指定的順序,將查詢的結果行分散到指定數量的組 (tile) 中。每一個行組都得到不一樣的號碼:第一組爲 1,第二組爲 2,等等。您能夠在函數名稱後面的括號中指定所請求的組號,在 OVER 選項的 ORDER BY 子句中指定所請求的排序。組中的行數被計算爲 total_num_rows / num_groups。若是有餘數 n,則前面 n 個組得到一個附加行。所以,可能不會全部組都得到相等數量的行,可是組大小最大隻可能相差一行。例如,如下查詢按照 score 降序將三個組號分配給不一樣的 speaker 行: SELECT speaker, track, score, ROW_NUMBER() OVER(ORDER BY score DESC) AS rownum, NTILE(3) OVER(ORDER BY score DESC) AS tile FROM SpeakerStats ORDER BY score DESC 如下爲結果集: speaker track score rownum tile ---------- ---------- ----------- ------ ---- Jessica Dev 9 1 1 Ron Dev 9 2 1 Suzanne DB 9 3 1 Kathy Sys 8 4 1 Michele Sys 8 5 2 Mike DB 8 6 2 Kevin DB 7 7 2 Brian Sys 7 8 2 Joe Dev 6 9 3 Robert Dev 6 10 3 Dan Sys 3 11 3 在 SpeakerStats 表中有 11 位演講者。將 11 除以 3 獲得組大小 3 和餘數 2,這意味着前面 2 個組將得到一個附加行(每一個組中有 4 行),而第三個組則不會獲得附加行(該組中有 3 行)。組號(tile 號)1 被分配給行 1 到 4,組號 2 被分配給行 5 到 8,組號 3 被分配給行 9 到 11。經過該信息能夠生成直方圖,而且將項目均勻分佈到每一個梯級。在咱們的示例中,第一個梯級表示具備最高得分的演講者,第二個梯級表示具備中等得分的演 講者,第三個梯級表示具備最低得分的演講者。可使用 CASE 表達式爲組號提供說明性的有意義的備選含義: SELECT speaker, track, score, CASE NTILE(3) OVER(ORDER BY score DESC) WHEN 1 THEN 'High' WHEN 2 THEN 'Medium' WHEN 3 THEN 'Low' END AS scorecategory FROM SpeakerStats ORDER BY track, speaker 如下爲結果集: speaker track score scorecategory ---------- ---------- ----------- ------------- Kevin DB 7 Medium Mike DB 8 Medium Suzanne DB 9 High Jessica Dev 9 High Joe Dev 6 Low Robert Dev 6 Low Ron Dev 9 High Brian Sys 7 Medium Dan Sys 3 Low Kathy Sys 8 High Michele Sys 8 Medium