MySQL 視窗函式
原文引用 猴子 通俗易懂的學會:SQL視窗函式
一.視窗函式有什麼用?
在日常工作中,經常會遇到需要在每組內排名,比如下面的業務需求:
排名問題:每個部門按業績來排名
topN問題:找出每個部門排名前N的員工進行獎勵
面對這類需求,就需要使用sql的高階功能視窗函數了。
二.什麼是視窗函式?
視窗函式,也叫OLAP函式(Online Anallytical Processing,聯機分析處理),可以對資料庫資料進行實時分析處理。
視窗函式的基本語法如下:
<視窗函式> over (partition by <用於分組的列名> order by <用於排序的列名>)
那麼語法中的<視窗函式>都有哪些呢?
<視窗函式>
的位置,可以放以下兩種函式:
1) 專用視窗函式,包括後面要講到的rank
, dense_rank
, row_number
等專用視窗函式。
2) 聚合函式,如sum
. avg
, count
, max
, min
等
因為視窗函式是對where
或者group by
子句處理後的結果進行操作,所以視窗函式原則上只能寫在select
子句中。
三.如何使用?
接下來,就結合例項,給大家介紹幾種視窗函式的用法。
1.專用視窗函式rank
例如下圖,是班級表中的內容
如果我們想在每個班級內按成績排名,得到下面的結果。
以班級“1”為例,這個班級的成績“95”排在第1位,這個班級的“83”排在第4位。上面這個結果確實按我們的要求在每個班級內,按成績排名了。
得到上面結果的sql語句程式碼如下:
select *,
rank() over (partition by 班級
order by 成績 desc) as ranking
from 班級表
我們來解釋下這個sql語句裡的select
子句。rank
是排序的函式。要求是“每個班級內按成績排名”,這句話可以分為兩部分:
(1)每個班級內:按班級分組
partition by用來對錶分組。在這個例子中,所以我們指定了按“班級”分組(partition by 班級
)
(2)按成績排名
order by子句的功能是對分組後的結果進行排序,預設是按照升序(asc
order by 成績 desc
)是按成績這一列排序,加了desc關鍵詞表示降序排列。
通過下圖,我們就可以理解partiition by
(分組)和order by
(在組內排序)的作用了。
視窗函式具備了我們之前學過的group by
子句分組的功能和order by
子句排序的功能。那麼,為什麼還要用視窗函式呢?
這是因為,group by
分組彙總後改變了表的行數,一行只有一個類別。而partiition by
和rank
函式不會減少原表中的行數。例如下面統計每個班級的人數。
相信通過這個例子,你已經明白了這個視窗函式的使用:
select *,
rank() over (partition by 班級
order by 成績 desc) as ranking
from 班級表
現在我們說回來,為什麼叫“視窗”函式呢?這是因為partition by
分組後的結果稱為“視窗”,這裡的視窗不是我們家裡的門窗,而是表示“範圍”的意思。
簡單來說,視窗函式有以下功能:
-
同時具有分組和排序的功能
-
不減少原表的行數
-
語法如下:
<視窗函式> over (partition by <用於分組的列名>
order by <用於排序的列名>)
2.其他專業視窗函式
專用視窗函式rank
, dense_rank
, row_number
有什麼區別呢?
它們的區別我舉個例子,你們一下就能看懂:
select *,
rank() over (order by 成績 desc) as ranking,
dense_rank() over (order by 成績 desc) as dese_rank,
row_number() over (order by 成績 desc) as row_num
from 班級表
得到結果:
從上面的結果可以看出:
rank
函式:這個例子中是5位,5位,5位,8位,也就是如果有並列名次的行,會佔用下一名次的位置。比如正常排名是1,2,3,4,但是現在前3名是並列的名次,結果是:1,1,1,4。
dense_rank
函式:這個例子中是5位,5位,5位,6位,也就是如果有並列名次的行,不佔用下一名次的位置。比如正常排名是1,2,3,4,但是現在前3名是並列的名次,結果是:1,1,1,2。
row_number
函式:這個例子中是5位,6位,7位,8位,也就是不考慮並列名次的情況。比如前3名是並列的名次,排名是正常的1,2,3,4。
這三個函式的區別如下:
最後,需要強調的一點是:在上述的這三個專用視窗函式中,函式後面的括號不需要任何引數,保持()空著就可以。
現在,大家對視窗函式有一個基本瞭解了嗎?
3.聚合函式作為視窗函式
聚和視窗函式和上面提到的專用視窗函式用法完全相同,只需要把聚合函式寫在視窗函式的位置即可,但是函式後面括號裡面不能為空,需要指定聚合的列名。
我們來看一下視窗函式是聚合函式時,會出來什麼結果:
select *,
sum(成績) over (order by 學號) as current_sum,
avg(成績) over (order by 學號) as current_avg,
count(成績) over (order by 學號) as current_count,
max(成績) over (order by 學號) as current_max,
min(成績) over (order by 學號) as current_min
from 班級表
得到結果:
有發現什麼嗎?我單獨用sum
舉個例子:
如上圖,聚合函式sum
在視窗函式中,是對自身記錄、及位於自身記錄以上的資料進行求和的結果。比如0004號,在使用sum
視窗函式後的結果,是對0001,0002,0003,0004號的成績求和,若是0005號,則結果是0001號~0005號成績的求和,以此類推。
不僅是sum
求和,平均、計數、最大最小值,也是同理,都是針對自身記錄、以及自身記錄之上的所有資料進行計算,現在再結合剛才得到的結果(下圖),是不是理解起來容易多了?
比如0005號後面的聚合視窗函式結果是:學號0001~0005五人成績的總和、平均、計數及最大最小值。
如果想要知道所有人成績的總和、平均等聚合結果,看最後一行即可。
這樣使用視窗函式有什麼用呢?
聚合函式作為視窗函式,可以在每一行的資料裡直觀的看到,截止到本行資料,統計資料是多少(最大值、最小值等)。同時可以看出每一行資料,對整體統計資料的影響。
四.注意事項
partition
子句可是省略,省略就是不指定分組,結果如下,只是按成績由高到低進行了排序:
select *,
rank() over (order by 成績 desc) as ranking
from 班級表
得到結果:
但是,這就失去了視窗函式的功能,所以一般不要這麼使用。
五.總結
1.視窗函式語法
<視窗函式> over (partition by <用於分組的列名>
order by <用於排序的列名>)
<視窗函式>的位置,可以放以下兩種函式:
1) 專用視窗函式,比如rank
, dense_rank
, row_number
等
2) 聚合函式,如sum
. avg
, count
,max
, min
等
2.視窗函式有以下功能:
1)同時具有分組(partition by
)和排序(order by
)的功能
2)不減少原表的行數,所以經常用來在每組內排名
3.注意事項
視窗函式原則上只能寫在select
子句中
4.視窗函式使用場景
1)業務需求“在每組內排名”,比如:
排名問題:每個部門按業績來排名
topN問題:找出每個部門排名前N的員工進行獎勵
例項
Employee
表包含所有員工資訊,每個員工有其對應的 Id, salary 和 department Id。
+----+-------+--------+--------------+
| Id | Name | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1 | Joe | 70000 | 1 |
| 2 | Jim | 90000 | 1 |
| 3 | Henry | 80000 | 2 |
| 4 | Sam | 60000 | 2 |
| 5 | Max | 90000 | 1 |
+----+-------+--------+--------------+
Department
表包含公司所有部門的資訊。
+----+----------+
| Id | Name |
+----+----------+
| 1 | IT |
| 2 | Sales |
+----+----------+
編寫一個 SQL 查詢,找出每個部門工資最高的員工。對於上述表,您的 SQL 查詢應返回以下行(行的順序無關緊要)。
+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT | Max | 90000 |
| IT | Jim | 90000 |
| Sales | Henry | 80000 |
+------------+----------+--------+
解釋:
Max 和 Jim 在 IT 部門的工資都是最高的,Henry 在銷售部的工資最高
題解
經典topN問題:每組最大的N條記錄。這類問題涉及到“既要分組,又要排序”的情況,要能想到用視窗函式來實現。
程式碼
select DepartmentId,Name,Salary
from (
select *,
dense_rank() over (partition by DepartmentId
order by Salary desc) as ranking
from Employee) as a
where ranking <= 3;