MySQL?開(kāi)窗函數(shù)
結(jié)合order by關(guān)鍵詞和limit關(guān)鍵詞是可以解決很多的topN問(wèn)題,比如從二手房數(shù)據(jù)集中查詢出某個(gè)地區(qū)的最貴的10套房,從電商交易數(shù)據(jù)集中查詢出實(shí)付金額最高的5筆交易,從學(xué)員信息表中查詢出年齡最小的3個(gè)學(xué)員等。但是,如果需求變成從二手房數(shù)據(jù)集中查詢出各個(gè)地區(qū)最貴的10套房,從電商數(shù)據(jù)集中查詢出每月實(shí)付金額最高的5筆交易,從學(xué)員信息表中查詢出各個(gè)科系下年齡最小的3個(gè)學(xué)員,該如何解決呢?
其實(shí)這類問(wèn)題的核心就是,篩選出組內(nèi)的topN,而不是從全部數(shù)據(jù)集中挑選出topN。遇到這種既需要分組也需要排序的問(wèn)題,直接上開(kāi)窗函數(shù)就能解決了。
(1)開(kāi)窗函數(shù)的定義
開(kāi)窗函數(shù)也叫OLAP函數(shù)(Online Analytical Processing,聯(lián)機(jī)分析處理),主要用來(lái)實(shí)時(shí)分析處理數(shù)據(jù)。MySQL之前的版本是不支持開(kāi)窗函數(shù)的,從8.0版本之后開(kāi)始支持開(kāi)窗函數(shù)。
# 開(kāi)窗函數(shù)語(yǔ)法 func_name(<parameter>) OVER([PARTITION BY <part_by_condition>] [ORDER BY <order_by_list> ASC|DESC])
開(kāi)窗函數(shù)語(yǔ)句解析:
函數(shù)分為兩部分,一部分是函數(shù)名稱,開(kāi)窗函數(shù)的數(shù)量比較少,總共才11個(gè)開(kāi)窗函數(shù)+聚合函數(shù)(所有的聚合函數(shù)都可以用作開(kāi)窗函數(shù))。根據(jù)函數(shù)的性質(zhì),有的需要寫(xiě)參數(shù),有的不需要寫(xiě)參數(shù)。
另一部分為over語(yǔ)句,over()是必須要寫(xiě)的,里面的參數(shù)都是非必須參數(shù),可以根據(jù)需求有選擇地使用:
- 第一個(gè)參數(shù)是partition by + 字段,含義是根據(jù)此字段將數(shù)據(jù)集分為多份
- 第二個(gè)參數(shù)是order by + 字段,每個(gè)窗口的數(shù)據(jù)依據(jù)此字段進(jìn)行升序或降序排列

開(kāi)窗函數(shù)與分組聚合函數(shù)比較相似,都是通過(guò)指定字段將數(shù)據(jù)分成多份,區(qū)別在于:
- SQL 標(biāo)準(zhǔn)允許將所有聚合函數(shù)用作開(kāi)窗函數(shù),用OVER 關(guān)鍵字區(qū)分開(kāi)窗函數(shù)和聚合函數(shù)。
- 聚合函數(shù)每組只返回一個(gè)值,開(kāi)窗函數(shù)每組可返回多個(gè)值。
在這11個(gè)開(kāi)窗函數(shù)中,實(shí)際工作中用的最多的當(dāng)屬ROW_NUMBER()、RANK()、DENSE_RANK()這三個(gè)排序函數(shù)了。下面我們通過(guò)一個(gè)簡(jiǎn)單的數(shù)據(jù)集學(xué)習(xí)一下這三個(gè)開(kāi)窗函數(shù)。
# 首先創(chuàng)建虛擬的業(yè)務(wù)員銷售數(shù)據(jù)
CREATE TABLE Sales
(
idate date,
iname char(2),
sales int
);
# 向表中插入數(shù)據(jù)
INSERT INTO Sales VALUES
('2021/1/1', '丁一', 200),
('2021/2/1', '丁一', 180),
('2021/2/1', '李四', 100),
('2021/3/1', '李四', 150),
('2021/2/1', '劉猛', 180),
('2021/3/1', '劉猛', 150),
('2021/1/1', '王二', 200),
('2021/2/1', '王二', 180),
('2021/3/1', '王二', 300),
('2021/1/1', '張三', 300),
('2021/2/1', '張三', 280),
('2021/3/1', '張三', 280);
# 數(shù)據(jù)查詢
SELECT * FROM Sales;
# 查詢各月中銷售業(yè)績(jī)最差的業(yè)務(wù)員
SELECT month(idate),iname,sales,
ROW_NUMBER()
OVER(PARTITION BY month(idate)
ORDER BY sales) as sales_order
FROM Sales;
SELECT * FROM
(SELECT month(idate),iname,sales,
ROW_NUMBER()
OVER(PARTITION BY month(idate)
ORDER BY sales) as sales_order FROM Sales) as t
WHERE sales_order=1;

# ROW_NUMBER()、RANK()、DENSE_RANK()的區(qū)別 SELECT * FROM (SELECT month(idate) as imonth,iname,sales, ROW_NUMBER() OVER(PARTITION BY month(idate) ORDER BY sales) as row_order, RANK() OVER(PARTITION BY month(idate) ORDER BY sales) as rank_order, DENSE_RANK() OVER(PARTITION BY month(idate) ORDER BY sales) as dense_order FROM Sales) as t;

ROW_NUMBER():順序排序——1、2、3
RANK():并列排序,跳過(guò)重復(fù)序號(hào)——1、1、3
DENSE_RANK():并列排序,不跳過(guò)重復(fù)序號(hào)——1、1、2
(2)開(kāi)窗函數(shù)的實(shí)際應(yīng)用場(chǎng)景
在實(shí)際工作或者面試中,可能會(huì)遇到求用戶連續(xù)登錄天數(shù)、連續(xù)簽到天數(shù)等問(wèn)題。下面就提供一個(gè)用開(kāi)窗函數(shù)解決此類問(wèn)題的思路。
# 首先創(chuàng)建虛擬的用戶登錄表,并插入數(shù)據(jù) create table user_login ( user_id varchar(100), login_time datetime ); insert into user_login values (1,'2020-11-25 13:21:12'), (1,'2020-11-24 13:15:22'), (1,'2020-11-24 10:30:15'), (1,'2020-11-24 09:18:27'), (1,'2020-11-23 07:43:54'), (1,'2020-11-10 09:48:36'), (1,'2020-11-09 03:30:22'), (1,'2020-11-01 15:28:29'), (1,'2020-10-31 09:37:45'), (2,'2020-11-25 13:54:40'), (2,'2020-11-24 13:22:32'), (2,'2020-11-23 10:55:52'), (2,'2020-11-22 06:30:09'), (2,'2020-11-21 08:33:15'), (2,'2020-11-20 05:38:18'), (2,'2020-11-19 09:21:42'), (2,'2020-11-02 00:19:38'), (2,'2020-11-01 09:03:11'), (2,'2020-10-31 07:44:55'), (2,'2020-10-30 08:56:33'), (2,'2020-10-29 09:30:28'); # 查看數(shù)據(jù) SELECT * FROM user_login;
計(jì)算連續(xù)登錄天數(shù)通常會(huì)有以下三種情況:
- 查看每位用戶連續(xù)登錄的情況
- 查看每位用戶最大連續(xù)登錄的天數(shù)
- 查看在某個(gè)時(shí)間段里連續(xù)登錄天數(shù)超過(guò)N天的用戶
針對(duì)第一種情況:查看每位用戶連續(xù)登錄的情況
根據(jù)實(shí)際經(jīng)驗(yàn),我們知道在一段時(shí)間內(nèi),用戶可能出現(xiàn)多次連續(xù)登錄,這些信息我們都要輸出,所以最后結(jié)果輸出的字段可以是用戶ID、首次登錄日期、結(jié)束登錄日期、連續(xù)登錄天數(shù)這四個(gè)。
# 數(shù)據(jù)預(yù)處理:由于統(tǒng)計(jì)的窗口期是天數(shù),所以可以對(duì)登錄時(shí)間字段進(jìn)行格式轉(zhuǎn)換,將其變成日期格式然后再去重(去掉用戶同一天內(nèi)多次登錄的情況)
# 為方便后續(xù)代碼查看,將處理結(jié)果放置新表中,一步一步操作
create table user_login_date(
select distinct user_id, date(login_time) login_date from user_login);
# 處理后的數(shù)據(jù)如下:
select * from user_login_date;
# 第一種情況:查看每位用戶連續(xù)登陸的情況
# 對(duì)用戶登錄數(shù)據(jù)進(jìn)行排序
create table user_login_date_1(
select *,
rank() over(partition by user_id order by login_date) irank
from user_login_date);
#查看結(jié)果
select * from user_login_date_1;
# 增加輔助列,幫助判斷用戶是否連續(xù)登錄
create table user_login_date_2(
select *,
date_sub(login_date, interval irank DAY) idate #data_sub從指定的日期減去指定的時(shí)間間隔
from user_login_date_1);
# 查看結(jié)果
select * from user_login_date_2;
# 計(jì)算每位用戶連續(xù)登錄天數(shù)
select user_id,
min(login_date) as start_date,
max(login_date) as end_date,
count(login_date) as days
from user_login_date_2
group by user_id,idate;
# ===============【整合代碼,解決用戶連續(xù)登錄問(wèn)題】===================
select user_id,
min(login_date) start_date,
max(login_date) end_date,
count(login_date) days
from (select *,date_sub(login_date, interval irank day) idate
from (select *,rank() over(partition by user_id order by login_date) irank
from (select distinct user_id, date(login_time) login_date from user_login) as a) as b) as c
group by user_id,idate;
針對(duì)第二種情況:查看每位用戶最大連續(xù)登錄的天數(shù)
# 計(jì)算每個(gè)用戶最大連續(xù)登錄天數(shù) select user_id,max(days) from (select user_id, min(login_date) start_date, max(login_date) end_date, count(login_date) days from (select *,date_sub(login_date, interval irank day) idate from (select *,rank() over(partition by user_id order by login_date) irank from (select distinct user_id, date(login_time) login_date from user_login) as a) as b) as c group by user_id,idate) as d group by user_id;
針對(duì)第三種情況:查看在某個(gè)時(shí)間段里連續(xù)登錄天數(shù)超過(guò)N天的用戶
假如說(shuō),我們的需求是查看10/29-11/25在這段時(shí)間內(nèi)連續(xù)登錄天數(shù)≥5天的用戶。這個(gè)需求也可以用第一種情況查詢的結(jié)果進(jìn)行篩選。
# 查看在這段時(shí)間內(nèi)連續(xù)登錄天數(shù)≥5天的用戶 select distinct user_id from (select user_id, min(login_date) start_date, max(login_date) end_date, count(login_date) days from (select *,date_sub(login_date, interval irank day) idate from (select *,rank() over(partition by user_id order by login_date) irank from (select distinct user_id, date(login_time) login_date from user_login) as a) as b) as c group by user_id,idate having days>=5 ) as d;
這種寫(xiě)法是可以得出結(jié)果,但是針對(duì)這個(gè)問(wèn)題來(lái)說(shuō)有點(diǎn)麻煩了,下面介紹一個(gè)簡(jiǎn)單的方法:引用一個(gè)新的靜態(tài)窗口函數(shù)lead()
select *, lead(login_date,4) over(partition by user_id order by login_date) as idate5 from user_login_date;
lead函數(shù)有三個(gè)參數(shù),第一個(gè)參數(shù)是指定的列(這里用登陸日期),第二個(gè)參數(shù)是當(dāng)前行向后幾行的值,這里用的是4,也就是第五次登錄的日期,第三個(gè)參數(shù)是如果返回的空值可以用指定值替代,這里沒(méi)有使用第三個(gè)參數(shù)。 over語(yǔ)句里面是針對(duì)user_id分窗,每個(gè)窗口針對(duì)登錄日期升序。
用第五次登錄日期 - login_date+1,如果等于5,說(shuō)明是連續(xù)登錄五天的,如果得到空值或者大于5,說(shuō)明沒(méi)有連續(xù)登錄五天,代碼和結(jié)果如下:
# 計(jì)算第5次登錄日期與當(dāng)天的差值 select *,datediff(idate5,login_date)+1 days from (select *,lead(login_date,4) over(partition by user_id order by login_date) idate5 from user_login_date) as a; # 找出相差天數(shù)為5的記錄 select distinct user_id from (select *,datediff(idate5,login_date)+1 as days from (select *,lead(login_date,4) over(partition by user_id order by login_date) idate5 from user_logrin_date) as a)as b where days = 5;
【練習(xí)】美團(tuán)外賣(mài)平臺(tái)數(shù)據(jù)分析面試題——SQL
現(xiàn)有交易數(shù)據(jù)表user_goods_table如下:

現(xiàn)在老板想知道每個(gè)用戶購(gòu)買(mǎi)的外賣(mài)品類偏好分布,并找出每個(gè)用戶購(gòu)買(mǎi)最多的外賣(mài)品類是哪個(gè)。
# 分析題目:要求輸出字段為用戶名user_name,該用戶購(gòu)買(mǎi)最多的外賣(mài)品類goods_kind # 解題思路:這是一個(gè)分組排序的問(wèn)題,可以考慮窗口函數(shù) # 第一步:使用窗口函數(shù)row_number(),對(duì)每個(gè)用戶購(gòu)買(mǎi)的外賣(mài)品類進(jìn)行分組統(tǒng)計(jì)與排名 select user_name,goods_kind,count(goods_kind), rank() over (partition by user_name order by count(goods_kind) desc) as irank from user_goods_table group by user_name,goods_kind; # 第二步:篩選出每個(gè)用戶排名第一的外賣(mài)品類 select user_id,goods_kind from (select user_name,goods_kind,count(goods_kind), rank() over (partition by user_name order by count(goods_kind) desc) as irank from user_goods_table group by user_name,goods_kind) as a where irank=1
到此這篇關(guān)于MySQL 開(kāi)窗函數(shù)的文章就介紹到這了,更多相關(guān)MySQL 開(kāi)窗函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL中定時(shí)器的底層實(shí)現(xiàn)原理及使用方法
定時(shí)器可以用于定期執(zhí)行特定的SQL語(yǔ)句、備份數(shù)據(jù)、生成報(bào)表等操作,本文將詳細(xì)介紹MySQL中定時(shí)器的底層實(shí)現(xiàn)機(jī)制以及如何使用它2023-07-07
MySQL運(yùn)算符!=和<>及=和<=>的使用區(qū)別
本文主要介紹了MySQL運(yùn)算符!=和<>及=和<=>的使用區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
有效查詢MySQL表中重復(fù)數(shù)據(jù)的方法和技巧分享
在MySQL數(shù)據(jù)庫(kù)中,偶爾會(huì)遇到需要查找表中出現(xiàn)的重復(fù)數(shù)據(jù)的情況,這種情況下,我們可以通過(guò)編寫(xiě)一些SQL查詢語(yǔ)句輕松地找到并處理這些重復(fù)行,本文將介紹一些常見(jiàn)的方法和技巧,幫助你有效地查詢MySQL表中的重復(fù)數(shù)據(jù),需要的朋友可以參考下2023-10-10
監(jiān)聽(tīng)mysql表內(nèi)容變化 mysql開(kāi)啟binlog
這篇文章主要給大家介紹了關(guān)于監(jiān)聽(tīng)mysql表內(nèi)容變化,mysql開(kāi)啟binlog的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用mysql具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
MySQL timestamp自動(dòng)更新時(shí)間分享
在mysql中timestamp數(shù)據(jù)類型是一個(gè)比較特殊的數(shù)據(jù)類型,他可以自動(dòng)在你不使用程序更新情況下只要你更新了記錄timestamp會(huì)自動(dòng)更新時(shí)間2013-06-06
Mysql 的存儲(chǔ)引擎,myisam和innodb的區(qū)別
這篇文章主要介紹了Mysql 的存儲(chǔ)引擎,myisam和innodb的區(qū)別,需要的朋友可以參考下2014-12-12
Xtrabackup使用指南 InnoDB數(shù)據(jù)備份工具
Xtrabackup是一個(gè)對(duì)InnoDB做數(shù)據(jù)備份的工具,支持在線熱備份(備份時(shí)不影響數(shù)據(jù)讀寫(xiě)),是商業(yè)備份工具InnoDB Hotbackup的一個(gè)很好的替代品2011-10-10
探索MySQL?8中utf8mb4釋放多語(yǔ)言數(shù)據(jù)的強(qiáng)大潛力
這篇文章主要為大家介紹了探索MySQL?8中utf8mb4釋放多語(yǔ)言數(shù)據(jù)的強(qiáng)大潛力,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12

