最近使用窗口函數的頻率越來越高,這裏打算簡單介紹一下幾個排序的函數,做一個引子希望以後這方面的問題能夠更深入的理解,這裏先簡單介紹一下幾個簡單的排序函數及其相關子句,這裏先從什麼是排序開始吧。
排序函數是做什麼的?
排序函數的作用是基於一個結果集返回一個排序值。排序值就是一個數字,這個數字是典型的以1開始且自增長爲1的行值。由ranking函數決定排序值可以使唯一的對於當前結果集,或者某些行數據有相同的排序值。在接下來我將研究不同的排序函數以及如何使用這些函數。
使用RANK函數的例子
RANK函數每個分區的排序都是從1開始。“partition”是一組有相同指定分區列值的數據行的集合。如果一個分區中有相同排序列的值(這個列指定在ORDER BY後面),然後相同排序列值的行將會分配給相同的排序值。有點繞口,爲了更好的理解,如何使用,讓我們看下下面的語法:
RANK ( ) OVER ( [ PARTITION BY <partition_column> ] ORDER BY <order_by_column> )
這裏有幾個參數:
- <partition_column>: 指定一個或者多個列名作爲分區數據
- <order by column>: 確定一個或者多個列然後用來對每個分區的輸出數據進行排序
注意:
PARTITION BY子句是一個可選項。如是不使用,數據將按照一個分區對所有數據進行排序。如果指定了PARTITION BY子句,則每個分區的數據集都各自進行從1開始的排序。
現在對RANK函數的語法和如何工作有了一定的理解,下面運行一對該函數的例子。需要說明一下我的例子的運行環境都是AdventureWorks2012 數據庫,可以從網絡上下載這裏給出一個下載地址http://msftdbprodsamples.codeplex.com/releases/view/93587。
下面是第一個使用RANK函數的例子:
1 2 3 4 5 6 7 |
|
Code1: 只有RANK函數不分區
運行代碼後,結果集如下:
PostalCode StateProvinceID RankingValue --------------- --------------- -------------------- 03064 46 1 03064 46 1 03106 46 3 03276 46 4 03865 46 5 83301 23 6 83402 23 7 83501 23 8 83702 23 9 83864 23 10
如上所示,按照RANK函數使結果集按照列RankingValue進行了排序。在例子中排序是基於列PostalCode。每一個唯一的PostalCode 得到一個不同的排序值。這裏PostalCode 爲03054 有兩行數據,它們的排序值都是1,因爲有兩個1,所以排序2就被跳過。其餘的排序繼續往下依次進行。
由於RANK函數的分區子句沒有使用,那麼整個結果集被當做一個單一的分區。如果我打算按照獨立的StateProvinceID 進行分區,然後進行排序我可以做按照如下的例子來執行:
1 2 3 4 5 6 7 8 |
|
Code 2: 使用分區子句
運行代碼後的結果集:
PostalCode StateProvinceID RankingValue --------------- --------------- -------------------- 83301 23 1 83402 23 2 83501 23 3 83702 23 4 83864 23 5 03064 46 1 03064 46 1 03106 46 3 03276 46 4 03865 46 5
在輸出結果中分爲了兩個分區,一個分區是StateProvinceID 是23的,而另一個是包含StateProvinceID 值爲46的、注意每個分區都是從1開始進行排序的。
使用DENSE_RANK函數
當運行RANK函數時,由於有一個相同的PostalCode ,輸出結果會跳過一個排序值2,通過使用DENSE_RANK函數我能生成一個不省略改相同排序值的一個排序。該函數語法如下:
DENSE_RANK ( ) OVER ( [ PARTIION BY <partition_column> ] ORDER BY <order_by_column> )
語法中唯一的不同就是函數名稱的改變。讓我們運行下面的代碼來研究下函數:
1 2 3 4 5 6 7 8 |
|
Code3: 使用 DENSE_RANK
結果集如下:
PostalCode StateProvinceID RankingValue --------------- --------------- -------------------- 83301 23 1 83402 23 2 83501 23 3 83702 23 4 83864 23 5 03064 46 1 03064 46 1 03106 46 2 03276 46 3 03865 46 4
根據結果集,可以看到PostalCode 03064 有相同的排序值,但是下一個PostalCode 的排序值爲2而不是3了。與RANK函數的不同就是當有重複排序值時它能保證了排序序列中沒有省略排序。
使用NTILE 函數
該函數將數據集合劃分爲不同的組。得到組的數量是根據指定的一個整數來確定的。下面就是NTILE 函數的語法:
NTILE (integer_expression) OVER ( [ PARTIION BY <partition_column> ] ORDER BY <order_by_column> )
Where:
- <integer_expression>: 確定創建不同組的數量
- <partition_column>:確定一個或者多個列用來進行分區數據
- <order by column>: 確定一個或者多個列然後用來對每個分區的輸出數據進行排序
爲了更好地理解,讓我們回顧幾個不同的例子。運行下面代碼:
1 2 3 4 5 6 7 |
|
Code4: 使用NTILE 函數查詢
運行結果如下:
PostalCode StateProvinceID NTileValue --------------- --------------- -------------------- 03064 46 1 03064 46 1 03106 46 1 03276 46 1 03865 46 1 83301 23 2 83402 23 2 83501 23 2 83702 23 2 83864 23 2
通過觀察結果集,能很容易發現有兩個不同的NTileValue 的列值,1和2。兩個不同的NTileValue 值被創建是因爲這裏我查詢語句中指定了“NTILE(2)” 。這個括號內的值就是整數表達式,作用就是指定創建的組的數量。當看到結果集中有10行數據,前五行NTileValue 爲1,後五行爲2。不出所料整個結果集被平均分成了兩組。
如果不能被平均分配到不同個組的時候,比如參數導致有不能被整除的時候。當發生這種情況是那麼將不能被整除的行按序放到每一個組內,知道所有的剩餘行都被分配完畢。如下所示:
1 2 3 4 5 6 7 8 |
|
Code 5: NTile 查詢不能平均分配結果集
運行代碼如下:
PostalCode StateProvinceID NTileValue --------------- --------------- -------------------- 03064 46 1 03064 46 1 03106 46 1 03276 46 2 03865 46 2 83301 23 2 83402 23 3 83501 23 3 83702 23 4 83864 23 4
這裏直奔主題,10個結果行,參數爲4需要分成4組,那麼10除以4 餘數爲2。這意味着前兩組會多一行比後兩組。如上所示,在這個輸出結果中1和2組都有3行,然後NTileValue 爲3和4的組只有兩行。
跟RANK函數一樣,我們也能使用partition 分區子句來創建分區下的NTILE 函數。當引入PARTITION BY 子句時,每個分區內部都從1開始進行NTILE排序。下面展示一下運行代碼:
1 2 3 4 5 6 7 8 9 |
|
Code 6: 使用分區子句後,使用NTile 查詢不平均分組
運行代碼如下:
PostalCode StateProvinceID NTileValue --------------- --------------- -------------------- 83301 23 1 83402 23 1 83501 23 2 83702 23 2 83864 23 3 03064 46 1 03064 46 1 03106 46 2 03276 46 2 03865 46 3
通過結果集可以看到加入分區子句後對NTILE函數的影響。如果觀察輸出的NTileValue列值,可以發現排序從StateProvinceID 爲46開始重新從1開始。這就是加入“PARTITION BY StateProvinceID”子句的作用,先分區在分組排序。
使用 ROW_NUMBER 函數
當打算爲輸出的行生成一個行號時,行號順序地自增長,步長爲1.爲了完成目標我們需要使用ROW_NUMBER 函數。
下面是使用ROW_NUMBER 的例子:
ROW_NUMBER () OVER ( [ PARTIION BY <partition_expressions> ] ORDER BY <order_by_column> )
代碼如下:
1 2 3 4 5 6 7 |
|
Code 7: 使用ROW_NUMBER 函數
運行結果如下:
PostalCode StateProvinceID RowNumber --------------- --------------- -------------------- 03064 46 1 03064 46 2 03106 46 3 03276 46 4 03865 46 5 83301 23 6 83402 23 7 83501 23 8 83702 23 9 83864 23 10
如果想對輸出的PostalCode進行排序,但是你打算先按照StateProvinceID進行分組,再排序。爲了實現上述要求,我加入PARTITION BY子句,代碼如下:
1 2 3 4 5 6 7 8 |
|
Code 8: 使用PARTITION BY 子句和ROW_NUMBER 函數查詢
運行結果如下:
PostalCode StateProvinceID RowNumber --------------- --------------- -------------------- 83301 23 1 83402 23 2 83501 23 3 83702 23 4 83864 23 5 03064 46 1 03064 46 2 03106 46 3 03276 46 4
正如你看到的結果,通過添加分區子句,行數列RowNumber 每個不同的StateProvinceID 值都會從1重新開始排序。
總結
本篇講了多種不同的排序數據的方式,並且有一些方式要求分配一個序列化的數字。我先後展示瞭如何使用ROW_NUMBER, NTILE, RANK 和 DENSE_RANK函數,如何爲每一行數據生成序列化的列值。希望能夠讓大家在使用時更方便,這裏也只是展示了一部分窗口函數的使用。還有很多新的窗口函數希望跟大家一起討論學習。這裏只是做一個簡單介紹了。