SQL Server 2012以後對窗口函數進行了極大的增強,但對於不少開發人員來講,對窗口函數卻不甚瞭解,致使了這樣強大的功能被浪費,所以本篇文章主要談一談SQL Server中窗口函數的概念。數據庫
窗口函數,也能夠被稱爲OLAP函數或分析函數。理解窗口函數能夠從理解聚合函數開始,咱們知道聚合函數的概念,就是將某列多行中的值按照聚合規則合併爲一行,好比說Sum、AVG等等,簡單的概念如圖1所示。數組
圖1.聚合函數app
所以,一般來講,聚合後的行數都要小於聚合前的行數。而對於窗口函數來講,輸入結果等於輸出結果,舉一個簡單的例子,若是你計算產品類型A和產品類型B,A產品分5小類,B產品分2小類,應用了窗口函數的結果後能夠仍是7行,對窗口函數應用了Count後,附加在每一行上,好比說「A產品,A小類1,5「,而B小類則變爲」B產品,B小類1,2」最後一列就是應用了窗口函數的結果。函數
如今咱們對窗口函數有了初步的概覽,文章後我會提供一些具體的例子來讓對窗口函數的概念更加深入,窗口函數除了上面提到的輸入行等於輸出行以外,還有以下特性和好處:性能
窗口函數是整個SQL語句最後被執行的部分,這意味着窗口函數是在SQL查詢的結果集上進行的,所以不會受到Group By, Having,Where子句的影響。spa
窗口函數的典型範例是咱們在SQL Server 2005以後用到的排序函數,好比代碼清單1所示。3d
Row_Number() OVER (partition by xx ORDER BY xxx desc) RowNumber
代碼清單1.可用於分頁的排序函數code
所以,咱們能夠把窗口函數的語法抽象出來,如代碼清單2所示。blog
函數() Over (PARTITION By 列1,列2,Order By 列3,窗口子句) AS 列別名
下面咱們來看一個簡單的例子,假如說咱們但願將AdventureWorks示例數據庫中的Employee表按照性別進行聚合,好比說我但願獲得的結果是:「登陸名,性別,該性別全部員工的總數」,若是咱們使用傳統的寫法,那必定會涉及到子查詢,如代碼清單3所示。排序
SELECT [LoginID],gender,
(SELECT COUNT(*) FROM [AdventureWorks2012].[HumanResources].[Employee] a WHERE a.Gender=b.Gender) AS GenderTotal
FROM [AdventureWorks2012].[HumanResources].[Employee] b
代碼清單3.傳統的寫法
若是咱們使用了窗口函數,代碼瞬間就變得簡潔,再也不須要子查詢或Join,如圖2所示。
圖2.使用窗口函數
除此以外,窗口函數相比傳統寫法而言,還會有更好的性能,咱們能夠經過比較執行計劃得出如圖3所示。
圖3.經過比較執行計劃,看出窗口函數擁有更好的性能
假如咱們考慮更復雜的例子,在Over子句加上了Order By,來完成一個平均數累加,若是不使用窗口函數,那必定是遊標,循環等麻煩的方式,若是使用了窗口函數,則一切就變得很是輕鬆,如圖4所示。
圖4.窗口函數
代碼清單2展現了窗口函數的語法,其中Over子句以後第一個提到的就是Partition By。Partition By子句也能夠稱爲查詢分區子句,很是相似於Group By,都是將數據按照邊界值分組,而Over以前的函數在每個分組以內進行,若是超出了分組,則函數會從新計算,好比圖2中的例子,咱們將數據分爲男性和女性兩部分,前面的Count()函數針對這兩組分別計算值(男性206,女性84)。
針對Partition By能夠應用的函數不只僅是咱們所熟知的聚合函數,以及一些其餘的函數,好比說Row_Number()。
Order By子句是另外一類子句,會讓輸入的數據強制排序(文章前面提到過,窗口函數是SQL語句最後執行的函數,所以能夠把SQL結果集想象成輸入數據)。Order By子句對於諸如Row_Number(),Lead(),LAG()等函數是必須的,由於若是數據無序,這些函數的結果就沒有任何意義。所以若是有了Order By子句,則Count(),Min()等計算出來的結果就沒有任何意義。
下面咱們看一個頗有表明性的ROW_NUMBER()函數,該函數一般被用於分頁,該函數從1開始不斷遞增,能夠和Partition By一塊兒使用,當穿越分區邊界時,Row_Number重置爲1,一個簡單的例子如圖5所示,咱們根據請假小時數對員工進行排序。
圖5.Row_Number函數示例
另外一個比較有趣的分析函數是LEAD()和LAG(),這兩個分析函數通過Order By子句排序後,能夠在當前行訪問上N行(LAG)或下N行(LEAD)的數據,下面是一個例子,如圖6所示。
圖6.訪問上一行的LAG函數
另外一個分析函數是RANK函數,與Row_Number不一樣的是,Rank函數中若是出現了相同的值,不會像Row_Number那樣疊加計數,而是一樣的值計數同樣,好比說 1 1 3 4 5 5 7,而不是Row_Number的1 2 3 4 5 6 7。這裏就不細說了。另外若是但願並列排名的不影響下一個排名,則考慮使用Dense_Rank函數。有關其餘的諸如First_value和Last_Value之類的函數能夠參看:http://technet.microsoft.com/zh-cn/library/hh213234.aspx。
前面窗口的函數的做用範圍是整個表,或是整個Partition by後面的分區。可是使用了窗口子句咱們能夠控制輸入到窗口函數的數據集(前面說過,窗口函數是整個語句中最後執行的)的範圍。下面咱們從一個例子開始看,假如我但願找出公司每個層級休病假最長的人,咱們能夠執行圖7中的語句。
圖7.找出每一個層級休假最多的人
可是若是咱們但願把輸入數據集的粒度由Partition變爲更細的話,咱們可使用窗口子句,讓窗口函數僅僅根據當前行的前N行和後N行計算結果,那咱們可使用窗口子句,如圖8所示,圖8中,咱們排序後,僅僅根據當前行的前一行和後一行以及當前行來計算這3我的當中請病假最長時間的人。
圖8.在三行以內找到休假時間最長的人
咱們也可使用Range來指定Partition內的範圍,好比說咱們但願從當前行和以前行中找到第一行,則使用如圖9所示的用法。
圖9.
本文從窗口函數組成的三部分簡單介紹了窗口函數的概念,並給出了一些例子。更多能夠在窗口上使用的函數,能夠參照MSDN(http://technet.microsoft.com/zh-cn/library/ms189461.aspx)。在使用這些函數的時候,還要注意版本要求,不少函數是隻有在SQL Server 2012中才被支持的。