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 byrank函數不會減少原表中的行數。例如下面統計每個班級的人數。

相信通過這個例子,你已經明白了這個窗口函數的使用:

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的員工進行獎勵

實例

184. 部門工資最高的員工

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;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM