hive窗口函数最全总结
準備工作
一:窗口函數概況
1.1 窗口函數說明
1.2 窗口范圍說明
1.2.1 窗口范圍取值可選項
1.2.2 默認窗口范圍含義
思考一:如何理解省略order by的情況,不能指定窗口范圍?
二:窗口函數分類和特性
2.1 窗口函數分類
2.2 窗口函數功能和常見應用概括
2.2.1 功能
2.2.2 應用
三:窗口函數應用場景(基礎使用)
3.1 聚合型窗口函數
3.1.1 情境一:購買物品的用戶及其總人數
3.1.2 情景二:用戶購買物品明細及每周各種類型用戶購買總數量(補分析列)。
3.2 分析型(排序)窗口函數
3.2.1情景三:每種用戶類型銷量排行第一的用戶及其明細(分組排名)
思考二:窗口排序遇到null
3.2.2 情景四:每種用戶類型銷量排名前2的用戶明細(top n)
3.1.4 情景五:連續n天登陸
情景六:給定數字的頻率查詢中位數
3.3 取值型窗口函數
3.3.1 準備工作
3.3.2 情景七:找到車輛上一次鎖車記錄
3.3.3 情景八:那么如何求某個省份圍欄、某小時、某輛車的最長未被騎行時間。(多次窗口使用)
思考三:多次窗口使用能否改成一次窗口和group by結合得到大致數據?
思考四:多次窗口使用能否改成一次窗口和group by結合得到所有明細數據?
3.3.4 情景九:連續出現的數字
四:進階使用
4.1 窗口函數與group by異同點
4.1.1 窗口函數與group by 區別
4.1.2 group by去重
4.2 collect_list + partition by的特殊用法
4.2.1 問題提出
4.2.2 解決思路
4.2.3 實際解決
4.2.4 延伸到風控業務的實際用法
4.3 cube&grouping set這塊
五:窗口函數底層運行原理
六:其他注意事項
思考問題:
思考一:如何理解省略order by的情況,不能指定窗口范圍?
思考二:窗口排序遇到null
準備工作
已經有測試表:order_detail.本文接下來所有測試樣例如果未特殊說明均基于下面order_detail表。
| user_id (用戶ID) | order_date (訂單日期) | user_type (用戶類型) | sales (銷量) | |
| 1 | qishili | 2022-07-03 | new | 5 |
| 2 | wangshi | 2022-07-01 | old | 2 |
| 3 | liiu | 2022-07-01 | new | 1 |
| 4 | lilisi | 2022-07-05 | new | 5 |
| 5 | lisi | 2022-07-02 | old | 1 |
| 6 | wutong | 2022-07-02 | new | 6 |
| 7 | qibaqiu | 2022-07-05 | new | 1 |
| 8 | liwei | 2022-07-02 | old | 3 |
| 9 | zhangsa | 2022-07-02 | new | 2 |
| 10 | wanger | 2022-07-02 | new | 3 |
一:窗口函數概況
1.1 窗口函數說明
窗口函數簡單的說就是在執行聚合函數的時候指定一個操作窗口,這個窗口由over來進行控制。接下來重點介紹一下over()函數。
over():指定分析函數工作的數據窗口大小,這個大小可能會隨著行的變化而變化。
其基本語法如下:<分析函數> over ( partition by <用于分組的列名> order by <用于排序的列名> desc/asc rows between 開始行 and 結束行 )
樣例1--窗口函數基本使用
select user_id,user_type,sales,count(user_id) over(partition by user_type order by sales desc rows between unbounded preceding and current row) as cnt from order_detail可以看到over()里面一共有3部分組成:分區、排序、指定窗口范圍,注意這三部分可以組合使用,也可以不使用。
partition by可以理解為分組。over(partition by 列)搭配分析函數時候(可以接多個列,表示按照這些列分組),分析函數是按照每一組的數據進行分析計算的。
order by是在窗口內進行排序,desc/asc可以進行選擇使用。
rows between 開始位置 and 結束位置,指定數據范圍。 -- 例如常使用的窗口范圍是rows between unbounded preceding and current row.常用該窗口來累加計算。
1.2 窗口范圍說明
1.2.1 窗口范圍取值可選項
1)范圍取值
【a:rows+參數來控制范圍】
-
默認值(不寫):這個其實是最常用的,下面會進行不同情況下默認值總結
-
preceding:往前;
-
following: 往后
-
current row:當前行
-
unbounded :起點(一般結合preceding,following使用)
-
unbounded preceding: 表示該窗口最前面的行(起點)
-
unbounded following:該窗口最后面的行(終點)
樣例2--窗口范圍一些完整寫法
rows between unbounded preceding and current row --(表示從窗口起點到當前行) rows?between unbounded preceding and unbounded following--(表示從窗口起點到終點) rows between 2 preceding and 1 following --(表示往前2行到往后1行) rows between 2 preceding and 1 current row --(表示往前兩行到當前行) rows between current row and unbounded following --(表示當前行到終點) 【b.range between來控制范圍】range 表示的是值, 表示比這個值小n的行,比這個值大n的行即range between 是以當前值為錨點進行計算。
eg: sum(sales) range between 100 preceding and 200 following
【c.通過窗口函數來控制范圍】
-
lag(col, n, default_val): 往前第n行數據,沒有數據的話用default_value代替
-
lead(col, n, default_val):往后第n行數據,沒有數據的話用default_value代替
-
ntile(n):把有序分區中的行分發到指定數據的組中,各個組有編號,編號從1開始,對于每一行,NTILE返回此行所屬的組的編號。注意:n必須為int類型.
2)為了解釋rows between unbounded preceding and current row和rows?between unbounded preceding and unbounded following,我們舉例說明:
樣例3--rows between and取值范圍區別
select user_id,user_type,sales,count(user_id) over(partition by user_type order by sales desc rows between unbounded preceding and current row) as current_cnt, -- 樣例1中的代碼: 窗口取到當前行count(user_id) over(partition by user_type order by sales desc rows between unbounded preceding and unbounded following) as unbounded_following_cnt -- 窗口取到所有行 from order_detail樣例3測試結果(如下表),可以看出:
-
當指定范圍是從頭到當前行,分析函數得到的這個局部范圍(動態變化的)的分析值,例如current_cnt,每一行記錄得到的都是一個變化的值(同一個分組內不同行值都是不同的)。(再具體一點是到每個組中的某一行記錄)
-
當指定范圍是開頭到末尾行,分析函數得到的是全局范圍(固定的)的分析值,例如unbounded_following_cnt,同一個組每一行記錄都是一個相等值(指同一個分組內哈,不同分組值還是不同的)。
| user_id | order_date | user_type | sales | current_cnt | unbounded_following_cnt | |
| 1 | wutong | 2022-07-01 | new | 6 | 1 | 7 |
| 2 | lilisi | 2022-07-02 | new | 5 | 3 | 7 |
| 3 | qishili | 2022-07-02 | new | 5 | 3 | 7 |
| 4 | wanger | 2022-07-01 | new | 3 | 4 | 7 |
| 5 | zhangsa | 2022-07-03 | new | 2 | 5 | 7 |
| 6 | qibaqiu | 2022-07-06 | new | 1 | 7 | 7 |
| 7 | liiu | 2022-07-05 | new | 1 | 7 | 7 |
| 8 | liwei | 2022-07-05 | old | 3 | 1 | 3 |
| 9 | wangshi | 2022-07-02 | old | 2 | 2 | 3 |
| 10 | lisi | 2022-07-01 | old | 1 | 3 | 3 |
1.2.2 默認窗口范圍含義
1)情況一:當over()里面有partition by和order by的時候,但是缺少rows between and這部分,即不寫這塊,也就是我們常說的默認值,其分析函數對應的范圍是同一組從開頭到當前值。
上面的默認窗口范圍和樣例2中的窗口范圍區別:
樣例4--既有partition by和order by情況下默認值窗口范圍和常見窗口范圍比較
select user_id,user_type,sales,sum(sales) over(partition by user_type order by sales desc rows between unbounded preceding and current row) as current_cnt, -- 樣例1中的代碼: 窗口取到各組當前行(當前這行的記錄)sum(sales) over(partition by user_type order by sales desc) as default_cnt, -- 默認也是窗口取到各組當前行,但是更準確的說應該是取到各組當前值,因為order by之后,-- 當排序的字段相同的時候,-- 相同的字段會一起被計算出來,所以說是取到當前“值/行”。會把值相同的進行一起計算,-- 得到某一個結果。sum(sales) over(partition by user_type order by sales desc rows between unbounded preceding and unbounded following) as unbounded_following_cnt-- 窗口取到所有行 ,各個分組里面也取所有行,各個組里面只有同一個值 from order_detail樣例4測試結果(如下表),可以清楚看到:
-
rows between unbounded preceding and current row其分析計算出來的結果是針對開頭到當前行的范圍,所以同一組的各個記錄對應的分析值也不一樣;
-
而默認不寫窗口范圍的,其分析是針對開頭到當前值的范圍,因為當前值可以對應多條記錄,需要把這幾天記錄一起去分析得到一個明確的結果,然后這個值所對應的這幾條記錄最終都是這同一個明確的結果。
-
而窗口范圍明確寫開頭到結尾,那么同一個組內所有記錄都是同一個結果,分析的是同一個組的所有數據。
| user_id | user_type | sales | current_cnt | default_cnt | unbounded_following_cnt | |
| 1 | wutong | new | 6 | 1 | 1 | 7 |
| 2 | lilisi | new | 5 | 2 | 3 | 7 |
| 3 | qishili | new | 5 | 3 | 3 | 7 |
| 4 | wanger | new | 3 | 4 | 4 | 7 |
| 5 | zhangsa | new | 2 | 5 | 5 | 7 |
| 6 | liiu | new | 1 | 6 | 7 | 7 |
| 7 | qibaqiu | new | 1 | 7 | 7 | 7 |
| 8 | liwei | old | 3 | 1 | 1 | 3 |
| 9 | wangshi | old | 2 | 2 | 2 | 3 |
| 10 | lisi | old | 1 | 3 | 3 | 3 |
2)情況二:當over里面只有partiton by,沒有order by的時候,注意這個時候寫的窗口范圍不能寫樣例2的范圍,會直接報錯,只能按照默認范圍來。默認范圍是從分組內開頭到結尾的記錄即ows between unbounded preceding and unbounded following,雖然范圍是這個,但是sql代碼不能寫上ows between unbounded preceding and unbounded following,只能什么也不寫。(因為沒有order by每次數據記錄返回來的順序可能不一致)。
樣例5--窗口里面只有partition by
-- 只partition by select user_id,user_type,sales,sum(sales) over(partition by user_type ) as default_cnt -- 默認-- sum(sales) over(partition by user_type rows between unbounded -- preceding and unbounded following) as unbounded_following_cnt -- 直接報錯,不能寫出窗口范圍。 from order_detail樣例5測試結果:可以看待窗口范圍是各個分組里面開頭到結尾的數據,各個組分析得到的結果一樣。
| 1 | zhangsa | new | 2 | 23 |
| 2 | wanger | new | 3 | 23 |
| 3 | liiu | new | 1 | 23 |
| 4 | qibaqiu | new | 1 | 23 |
| 5 | wutong | new | 6 | 23 |
| 6 | lilisi | new | 5 | 23 |
| 7 | qishili | new | 5 | 23 |
| 8 | lisi | old | 1 | 6 |
| 9 | wangshi | old | 2 | 6 |
| 10 | liwei | old | 3 | 6 |
3)情況三:當over()里面只有order by的時候,這個時候窗口范圍的控制就和情況一中一樣,可以,只不過這個時候就只有一個分組了,全部數據放到一個分組里面!
常用的窗口范圍和默認窗口范圍見情況一,這里不再贅述。
樣例6--窗口里面沒有partition by,有order by
select user_id,user_type,sales,sum(sales) over(order by sales desc rows between unbounded preceding and current row) as current_cnt, -- 樣例1中的代碼: 窗口取到當前行sum(sales) over(order by sales desc) as default_cnt, -- 默認:取到當前“行/值”sum(sales) over(order by sales desc rows between unbounded preceding and unbounded following) as unbounded_following_cnt -- 窗口取到所有行 from order_detail樣例6測試結果
| user_id | user_type | sales | current_cnt | default_cnt | unbounded_following_cnt | |
| 1 | wutong | new | 6 | 6 | 6 | 29 |
| 2 | lilisi | new | 5 | 11 | 16 | 29 |
| 3 | qishili | new | 5 | 16 | 16 | 29 |
| 4 | wanger | new | 3 | 19 | 22 | 29 |
| 5 | liwei | old | 3 | 22 | 22 | 29 |
| 6 | zhangsa | new | 2 | 24 | 26 | 29 |
| 7 | wangshi | old | 2 | 26 | 26 | 29 |
| 8 | lisi | old | 1 | 27 | 29 | 29 |
| 9 | liiu | new | 1 | 28 | 29 | 29 |
| 10 | qibaqiu | new | 1 | 29 | 29 | 29 |
4)情況四:當窗口里面既沒有partition by,也沒有order by的時候
樣例7--既沒有partition by也沒有order by的時候
select user_id,user_type,sales,-- sum(sales) over(rows between unbounded preceding and current row) -- as current_cnt, -- 錯誤寫法 ,也就是說這種情況下,我們也不能自己寫窗口范圍。sum(sales) over() as sales_cnt -- 默認 正確寫法 范圍默認為開頭到結尾。-- sum(sales) over(rows between unbounded preceding and --unbounded following) as unbounded_following_cnt -- 錯誤寫法 from order_detail5)對上面5種情況進行初步總結:
-
如果over()里面沒有order by,那么我們就不能指定窗口范圍,只能使用默認值(代碼中row between end這處什么也不寫),此時的默認值代表的范圍是各分組的開頭到結尾數據;
-
反之,如果over()里面🈶?order by,那么我們就可以指定窗口范圍,如果不指定窗口范圍(默認值),此時默認值的范圍代表分組里面的的開頭到當前值(當前“行”)的數據范圍。
-
如果over里面沒有partition by,此時就只有一個分組,同上一樣。
思考一:如何理解省略order by的情況,不能指定窗口范圍?
在任何并行系統中,如果order by子句未生成唯一排序和總排序,則行的順序是不確定的,也就是說,如果order by 表達式生成重復的值(部分排序),則這些行的返回順序可能會有差別,也就是說窗口函數可能返回意外或不一致的結果。
二:窗口函數分類和特性
2.1 窗口函數分類
1)按照窗口來分
可以分為靜態窗口函數、動態窗口函數。其中靜態窗口函數主要是指排序函數;而動態窗口函數主要分為累計計算函數和偏分析函數。其中窗口數據集這塊對應文章第一章的詳細描述。
窗口函數窗口函數名窗口數據集靜態窗口函數動態窗口函數
排序函數row_number():當前行在組內的排序,eg:1,2,3,4,5
dense_rank()不間斷組內排序,eg:1,1,2,3,3
rank():間斷的組內排序,eg:1,1,3,4,5
累計計算函數偏移分析函數sum()count()avg()max()min()percent_rank():累計百分比cume_dist():累計分布值first_value():返回組內第一個值last_value():返回組內的最后一個值nth_value():返回組內的第n行lag():從當前行往上偏移第n行,默認為nulllead():從當前行開始向下偏移第n行,默認為nullntile():返回當前行在組內截止當前行的第n行分組參數:partition by排序參數:order by窗口參數:row between ...andover()
2)按照函數功能
可以將窗口函數分為:聚合型窗口函數、分析型窗口函數、取值型窗口函數。
窗口函數聚合型取值型分析型聚合函數:sum() /max() /min() /avg() /count()排序函數和占比函數:row_number() / dense_rank() / rank() / percent_rank() /cume_dist()?取值:lag() / lead() / first_value() / last_value()
3)舉例擴展一下窗口函數中三個排序函數區別
樣例8--三個排序函數區別(每種用戶類型銷量排名)
select user_id,user_type,sales,row_number() over(partition by user_type order by sales) as rn,dense_rank() over(partition by user_type order by sales) as rd,rank() over(partition by user_type order by sales) as rr from order_detail樣例8測試結果:
| user_id | user_type | sales | rn | rd | rr | |
| 1 | liiu | new | 1 | 1 | 1 | 1 |
| 2 | qibaqiu | new | 1 | 2 | 1 | 1 |
| 3 | zhangsa | new | 2 | 3 | 2 | 3 |
| 4 | wanger | new | 3 | 4 | 3 | 4 |
| 5 | lilisi | new | 5 | 5 | 4 | 5 |
| 6 | qishili | new | 5 | 6 | 4 | 5 |
| 7 | wutong | new | 6 | 7 | 5 | 7 |
| 8 | lisi | old | 1 | 1 | 1 | 1 |
| 9 | wangshi | old | 2 | 2 | 2 | 2 |
| 10 | liwei | old | 3 | 3 | 3 | 3 |
2.2 窗口函數功能和常見應用概括
2.2.1 功能
1)窗口函數同時具有分組和排序的功能;
2)不減少原有表的行數。即窗口函數可以理解為在本行內做運算,每一行對應一行的值。
3)可指定窗口數據范圍
2.2.2 應用
1)分組排名問題
2)top n問題
3)連續登陸問題
4)加一分析結果列
5)累計問題
6)上一個下一個記錄問題
三:窗口函數應用場景(基礎使用)
3.1 聚合型窗口函數
sum,count()這些很常見,其功能我們不再贅述
3.1.1 情境一:購買物品的用戶及其總人數
樣例9--查詢2022年7月份購買物品的用戶及其總人數
-- 正確方法:總人數是指7月份購買物品的總人數。 select user_id,count(1) over() as 7_mounth_all_cnt from order_detail where substring(order_date,1,7) = '2022-07' -- 錯誤理解;不是每個人購買物品總人數。 select user_id,count(1) as 7_mounth_all_cnt from order_detail where substring(order_date,1,7) = '2022-07' group by user_id樣例9測試結果:
| user_id | 7_mounth_all_cnt | |
| 1 | qibaqiu | 10 |
| 2 | zhangsa | 10 |
| 3 | lisi | 10 |
| 4 | wanger | 10 |
| 5 | liiu | 10 |
| 6 | wangshi | 10 |
| 7 | liwei | 10 |
| 8 | wutong | 10 |
| 9 | lilisi | 10 |
| 10 | qishili | 10 |
3.1.2 情景二:用戶購買物品明細及每周各種類型用戶購買總數量(補分析列)。
樣例10:用戶購買物品明細以及每周各種類型用戶購買總數量
select user_id,user_type,sales,sum(sales) over(partition by week(order_date),user_type) as sales_s from order_detail樣例10測試結果如下,像這樣既要明細又需要聚合值的,顯然使用group by是做不到的,要使用窗口函數。
| user_id | user_type | sales | sales_s | |
| wanger | new | 3 | 20 | |
| 2 | qibaqiu | new | 1 | 20 |
| 3 | wutong | new | 6 | 20 |
| 4 | lilisi | new | 5 | 20 |
| 5 | qishili | new | 5 | 20 |
| 6 | lisi | old | 1 | 3 |
| 7 | wangshi | old | 2 | 3 |
| 8 | zhangsa | new | 2 | 3 |
| 9 | liiu | new | 1 | 3 |
| 10 | liwei | old | 3 | 3 |
3.2 分析型(排序)窗口函數
在第二章中已經對常見排序函數進行總結過,可參考。
3.2.1情景三:每種用戶類型銷量排行第一的用戶及其明細(分組排名)
可以參照第二章中的樣例7。
樣例11:每種用戶類型銷量排行第一的用戶及其明細(分組排序)
-- 把窗口函數放在自查詢里面得到一個每條記錄的排名 select * from (select user_id,user_type,sales,row_number() over(partition by user_type order by sales desc) as rcfrom order_detail )t1 where t1.rc = 1樣例11測試結果:
| user_id | user_type | sales | rc | |
| 1 | wutong | new | 6 | 1 |
| 2 | liwei | old | 3 | 1 |
思考二:窗口排序遇到null
1)窗口中order by的 時候,使用desc降序排列的時候,null值排在首位;
使用asc升序的時候,null值排在末尾;
2)如何控制null的位置:
nulls last和 nulls first.
如下圖:此樣例建議等看完第四章中部分論述的背景,再回來看這個
rrow_number() over(partition by fence,hour(lock_time),bike_id order by time_gap desc nulls last) as rc
樣例12:排序的時候不需要先對null值進行處理,直接使用nulls last會很省事!
select -- 找到分組中最大間隔fence,hourtime,bike_id,time_gap from ( -- 分組對時間間隔進行排序selectfence,bike_id,hour(lock_time) as hourtime,time_gap,-- 發現time_gap存在很多null的情況,在參與order by排序的時候會影響排序結果,所以得到結果不準確row_number() over(partition by fence,hour(lock_time),bike_id order by time_gap desc nulls last) as rcfrom( -- 分組找到本次記錄的上次記錄的鎖車時間和事件間隔select fence,bike_id,hour(lock_time),unlock_time,lock_time,lag(unlock_time,1,null) over(partition by fence,hour(lock_time),bike_id) as last_unlock_time,(unix_timestamp(unlock_time) - unix_timestamp(lag(lock_time,1,null) over(partition by fence,hour(lock_time),bike_id))) as time_gapfrom bike_hour_inc)t1 )t2 where t2.rc =1 order by fence,hourtime,bike_id ?3.2.2 情景四:每種用戶類型銷量排名前2的用戶明細(top n)
可以參照第二章中的樣例7
樣例13:每種用戶類型銷量排名前2的用戶明細
-- 自查詢使用窗口函數得到分組里面每條記錄排名,外查詢進行排名的約束 select * from (select user_id,user_type,sales,row_number() over(partition by user_type order by sales desc) as rcfrom order_detail )t1 where t1.rc <=2樣例13測試結果:
| user_id | user_type | sales | rc | |
| 1 | wutong | new | 6 | 1 |
| 2 | lilisi | new | 5 | 2 |
| 3 | liwei | old | 3 | 1 |
| 4 | wangshi | old | 2 | 2 |
3.1.4 情景五:連續n天登陸
給定測試數據集
|
| 2022-06-02 |
| 7 | 2022-06-10 |
| 7 | 2022-06-03 |
| 7 | 2022-05-30 |
| 7 | 2022-05-31 |
| 7 | 2022-06-02 |
| 1 | 2022-06-07 |
| 7 | 2022-06-01 |
| 1 | 2022-05-30 |
sql邏輯
樣例14:連續n天登陸(去重、排序、間隔、分組、約束)
select login_id from ( selectlogin_id,login_date,-- row_number() over(partition by login_id order by login_date) as rc -- 對登陸日期進行排名-- 找到登陸日期與時間排名之間的間隔date_sub(login_date,row_number() over(partition by login_id order by login_date) ) as time_gap from (-- 每個人一天可能不止登陸一次,需要去重select login_id,login_datefrom login_tablegroup by login_id,login_date)t1)t2group by t2.login_id,t2.time_gap -- 以用戶和時間間隔進行分組having count(1) >= 5 -- 每個用戶分組和時間間隔分組里面數據記錄大于等于5的。測試結果login_id = 7;
情景六:給定數字的頻率查詢中位數
todo
3.3 取值型窗口函數
3.3.1 準備工作
1)lag/lead() over()的使用
lag(col,n,default):用于統計窗口往上第n行值:
-
第一個參數為列名;
-
第二個參數為往上第n行(默認為1);
-
第三個參數為默認值(當往上第n行為null的時候,取默認值,如果不指定,則取null)。
同理:
lead(col,n,default):用于統計窗口往下第n行值:
-
第一個參數為列名;
-
第二個參數為往下第n行(默認為1);
-
第三個參數為默認值(當往下第n行為null的時候,取默認值,如果不指定,則取null)。
-
窗口partition by和group by
-
是否添加范圍
2)first_value
todo
思考:first_value與max()區別???
3.3.2 情景七:找到車輛上一次鎖車記錄
類似情景表述:查看顧客上次的購買時間
某車輛在某一小時內某一圍欄中本次騎行的明細及對應上次鎖車時間。新的測試數據集:
| fence | bike_id | unlock_time | lock_time | |
| 1 | 湖北 | 101 | 2022-09-01 07:03:23 | 2022-09-01 07:05:03 |
| 2 | 湖北 | 101 | 2022-09-01 07:12:53 | 2022-09-01 07:15:13 |
| 3 | 湖北 | 101 | 2022-09-01 07:32:11 | 2022-09-01 07:55:13 |
| 4 | 湖北 | 101 | 2022-09-01 07:56:11 | 2022-09-01 07:59:52 |
| 5 | 湖北 | 101 | 2022-09-01 09:12:10 | 2022-09-01 09:25:53 |
| 6 | 湖北 | 101 | 2022-09-01 09:42:09 | 2022-09-01 09:45:33 |
| 7 | 湖北 | 102 | 2022-09-01 07:02:21 | 2022-09-01 07:15:13 |
| 8 | 湖北 | 102 | 2022-09-01 07:47:21 | 2022-09-01 07:55:13 |
| 9 | 山東 | 104 | 2022-09-01 07:02:21 | 2022-09-01 07:15:13 |
| 10 | 山東 | 104 | 2022-09-01 07:44:21 | 2022-09-01 07:55:13 |
樣例15:某哈羅單車車輛在某一小時內某一圍欄中本次騎行的明細及對應上次鎖車時間?,沒有order by
select fence,bike_id,unlock_time, -- 開鎖lock_time, -- 鎖車lag(lock_time,1,null) over(partition by fence,hour(unlock_time),bike_id) as last_lock_time from bike_hour_inc測試結果
| fence | bike_id | unlock_time | lock_time | last_lock_time (本次開鎖時間對應的上一次鎖車時間) | |
| 1 | 山東 | 104 | 2022-09-01 07:02:21 | 2022-09-01 07:15:13 | null |
| 2 | 山東 | 104 | 2022-09-01 07:44:21 | 2022-09-01 07:55:13 | 2022-09-01 07:15:13 |
| 3 | 湖北 | 101 | 2022-09-01 07:03:23 | 2022-09-01 07:05:03 | null |
| 4 | 湖北 | 101 | 2022-09-01 07:12:53 | 2022-09-01 07:15:13 | 2022-09-01 07:05:03 |
| 5 | 湖北 | 101 | 2022-09-01 07:32:11 | 2022-09-01 07:55:13 | 2022-09-01 07:15:13 |
| 6 | 湖北 | 101 | 2022-09-01 07:56:11 | 2022-09-01 07:59:52 | 2022-09-01 07:55:13 |
| 7 | 湖北 | 102 | 2022-09-01 07:02:21 | 2022-09-01 07:15:13 | null |
| 8 | 湖北 | 102 | 2022-09-01 07:47:21 | 2022-09-01 07:55:13 | 2022-09-01 07:15:13 |
| 9 | 湖北 | 101 | 2022-09-01 09:12:10 | 2022-09-01 09:25:53 | null |
| 10 | 湖北 | 101 | 2022-09-01 09:42:09 | 2022-09-01 09:45:33 | 2022-09-01 09:25:53 |
111
3.3.3 情景八:那么如何求某個省份、某小時、某拖拉機的最長未被騎行時間。(多次窗口使用)
樣例16:各車輛在某一省份分小時段最大未騎行間隔(不完全對的寫法,忽視了null排序情況)
-- 注意:最里層自查詢要在窗口里面使用partition by進行分組,在外層查詢里面對時間間隔排序也需要分組 select -- 找到分組中最大間隔fence,hourtime,bike_id,time_gap from ( -- 分組對時間間隔進行排序selectfence,bike_id,hour(lock_time) as hourtime,time_gap,-- 發現time_gap存在很多null的情況,在參與order by排序的時候會影響排序結果,所以得到結果不準確row_number() over(partition by fence,hour(lock_time),bike_id order by time_gap desc) as rcfrom( -- 分組找到本次記錄的上次記錄的鎖車時間和事件間隔select fence,bike_id,hour(lock_time),unlock_time,lock_time,lag(unlock_time,1,null) over(partition by fence,hour(lock_time),bike_id) as last_unlock_time,(unix_timestamp(unlock_time) - unix_timestamp(lag(lock_time,1,null) over(partition by fence,hour(lock_time),bike_id order by unlock_time))) as time_gapfrom bike_hour_inc)t1 )t2 where t2.rc =1 order by fence,hourtime,bike_id測試結果:
| fence | hourtime | bike_id | time_gap | |
| 1 | 山東 | 7 | 104 | 1748 |
| 2 | 湖北 | 7 | 101 | 58 |
| 3 | 湖北 | 7 | 102 | 1928 |
| 4 | 湖北 | 9 | 101 | 976 |
糾正后,最終正確寫法1:對null先進行處理
樣例17:各拖拉機在某一省份分小時段最大未騎行間隔(兩次窗口函數完全正確寫法)
select * from (select t2.fence,t2.bike_id,t2.every_hour,t2.unlock_time,t2.lock_time,t2.last_lock_time,t2.time_gap,t2.time_gap_not_null,row_number() over(partition by t2.fence,t2.every_hour,t2.bike_id order by time_gap_not_null desc) as rcfrom(select t1.fence,t1.bike_id,t1.every_hour,t1.unlock_time,t1.lock_time,t1.last_lock_time,t1.time_gap,case when t1.time_gap is not null then t1.time_gap else -1 end as time_gap_not_null-- nvl(t1.time_gap,999999) as time_gap_not_null2 -- 不行,數值類型不一樣的感覺。from(select fence,bike_id,hour(lock_time) as every_hour,unlock_time,lock_time,-- 使用lag,窗口里面應該需要加一個order by吧!lag(lock_time,1,null) over(partition by fence,hour(unlock_time),bike_id order by unlock_time) as last_lock_time,(unix_timestamp(unlock_time) - unix_timestamp(lag(lock_time,1,null) over(partition by fence,hour(lock_time),bike_id order by unlock_time))) as time_gapfrom bike_hour_inc)t1)t2 )t3 where t3.rc =1 order by t3.fence,t3.every_hour,t3.bike_id測試結果:
| fence | bike_id | hour | unlock_time | lock_time | last_lock_time | time_gap | time_gap_not_null | rc | |
| 1 | 山東 | 104 | 7 | 2022-09-01 07:44:21 | 2022-09-01 07:55:13 | 2022-09-01 07:15:13 | 1748 | 1748 | 1 |
| 2 | 湖北 | 101 | 7 | 2022-09-01 07:32:11 | 2022-09-01 07:55:13 | 2022-09-01 07:15:13 | 1018 | 1018 | 1 |
| 3 | 湖北 | 102 | 7 | 2022-09-01 07:47:21 | 2022-09-01 07:55:13 | 2022-09-01 07:15:13 | 1928 | 1928 | 1 |
| 4 | 湖北 | 101 | 9 | 2022-09-01 09:42:09 | 2022-09-01 09:45:33 | 2022-09-01 09:25:53 | 976 | 976 | 1 |
糾正后,正確寫法(第二種辦法)
見3.4節樣例12。
注意事項:
1)null在窗口中排序問題:(回去看3.4節的論述)
2)nvl()字段類型問題
3)多重子查詢
4)兩次分組
思考三:多次窗口使用能否改成一次窗口和group by結合得到大致數據?
子查詢里面使用窗口,外查詢里面也使用窗口
外查詢如果不使用窗口函數partition by分組,我們也可以使用group by后取max即可。
樣例18:各車輛在某一圍欄分小時段最大未騎行間隔(使用group by+一次窗口函數? )
-- 原始版 select t2.fence,t2.bike_id,t2.every_hour,max(t2.time_gap_not_null) as max_gapfrom(select t1.fence,t1.bike_id,t1.every_hour,t1.unlock_time,t1.lock_time,t1.last_lock_time,t1.time_gap,case when t1.time_gap is not null then t1.time_gap else -1 end as time_gap_not_null-- nvl(t1.time_gap,999999) as time_gap_not_null2 -- 不行,數值類型不一樣的感覺。from(select fence,bike_id,hour(lock_time) as every_hour,unlock_time,lock_time,lag(lock_time,1,null) over(partition by fence,hour(unlock_time),bike_id) as last_lock_time,(unix_timestamp(unlock_time) - unix_timestamp(lag(lock_time,1,null) over(partition by fence,hour(lock_time),bike_id))) as time_gapfrom bike_hour_inc)t1)t2group by t2.fence,t2.bike_id,t2.every_hour order by t2.fence,t2.every_hour,t2.bike_id簡潔版:因為外查詢使用的是group by后的max聚合,而null是不參與聚合這種的
樣例19:各拖拉機在某一省份分小時段最大未騎行間隔(使用group by+一次窗口函數? ?簡介版)
SQL-- 因為外查詢使用的是group by后的max聚合,而null是不參與聚合這種的, -- 所以上述代碼還可以繼續簡化,不需要對null進行專門處理 -- 簡潔版select t1.fence,t1.bike_id,t1.every_hour,max(t1.time_gap) as max_gapfrom(select fence,bike_id,hour(lock_time) as every_hour,unlock_time,lock_time,lag(lock_time,1,null) over(partition by fence,hour(unlock_time),bike_id) as last_lock_time,(unix_timestamp(unlock_time) - unix_timestamp(lag(lock_time,1,null) over(partition by fence,hour(lock_time),bike_id order by unlock_time))) as time_gapfrom bike_hour_inc)t1group by t1.fence,t1.bike_id,t1.every_hour order by t1.fence,t1.every_hour,t1.bike_id測試結果如下:可以看到除了一些明細數據沒有(后續有時間可以探討再進行關聯得到所有明細數據),其余的結果是一致的!!!
| 1 | 山東 | 104 | 7 | 1748 |
| 2 | 湖北 | 101 | 7 | 1018 |
| 3 | 湖北 | 102 | 7 | 1928 |
| 4 | 湖北 | 101 | 9 | 976 |
綜上所述:如果我們只需要的是大致數據,那么就可以使用上述方法
思考四:多次窗口使用能否改成一次窗口和group by結合得到所有明細數據?
樣例20:各拖拉機在某一省份分小時段最大未騎行間隔(group by+窗口 )測試是否可以得到明細數據
select t2.fence,t2.bike_id,t2.every_hour,t2.max_gap,t3.* from (select t1.fence,t1.bike_id,t1.every_hour,max(t1.time_gap) as max_gapfrom(select fence,bike_id,hour(lock_time) as every_hour,unlock_time,lock_time,lag(lock_time,1,null) over(partition by fence,hour(unlock_time),bike_id order by unlock_time) as last_lock_time,(unix_timestamp(unlock_time) - unix_timestamp(lag(lock_time,1,null) over(partition by fence,hour(lock_time),bike_id))) as time_gapfrom bike_hour_inc)t1group by t1.fence,t1.bike_id,t1.every_hour )t2 join bike_hour_inc t3 on t2.fence = t3.fence and t2.bike_id = t3.bike_idand t2.every_hour = hour(unlock_time) order by t2.fence,t2.every_hour,t2.bike_id測試結果:我們發現, group by得到大致數據記錄然后再去關聯,發現數據記錄變多了,這樣說明如果group by的不是主鍵,那么就會數據量變多,得不到我們需要的記錄,當僅僅group by的是主鍵,才能用這種group by分組再進行關聯得到所需的最終結果!
| fence | bike_id | every_hour | max_gap | fence | bike_id | unlock_time | lock_time | |
| 1 | 山東 | 104 | 7 | 1748 | 山東 | 104 | 2022-09-01 07:44:21 | 2022-09-01 07:55:13 |
| 2 | 山東 | 104 | 7 | 1748 | 山東 | 104 | 2022-09-01 07:02:21 | 2022-09-01 07:15:13 |
| 3 | 湖北 | 101 | 7 | 1018 | 湖北 | 101 | 2022-09-01 07:32:11 | 2022-09-01 07:55:13 |
| 4 | 湖北 | 101 | 7 | 1018 | 湖北 | 101 | 2022-09-01 07:12:53 | 2022-09-01 07:15:13 |
| 5 | 湖北 | 101 | 7 | 1018 | 湖北 | 101 | 2022-09-01 07:03:23 | 2022-09-01 07:05:03 |
| 6 | 湖北 | 101 | 7 | 1018 | 湖北 | 101 | 2022-09-01 07:56:11 | 2022-09-01 07:59:52 |
| 7 | 湖北 | 102 | 7 | 1928 | 湖北 | 102 | 2022-09-01 07:02:21 | 2022-09-01 07:15:13 |
| 8 | 湖北 | 102 | 7 | 1928 | 湖北 | 102 | 2022-09-01 07:47:21 | 2022-09-01 07:55:13 |
| 9 | 湖北 | 101 | 9 | 976 | 湖北 | 101 | 2022-09-01 09:12:10 | 2022-09-01 09:25:53 |
| 10 | 湖北 | 101 | 9 | 976 | 湖北 | 101 | 2022-09-01 09:42:09 | 2022-09-01 09:45:33 |
3.3.4 情景九:連續出現的數字
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 1 |
| 6 | 2 |
| 7 | 2 |
sql邏輯:
樣例21:連續三次出現的數字
select distinct num as consecutivenums from (select num,lead(num,1,null) over(order by id) as n1,lead(num,2,null) over(order by id) as n2from shuzi )t1 where t1.num = t1.n1 and t1.n1 = t1.n2測試結果就是:1。
四:進階使用
4.1 窗口函數與group by異同點
4.1.1 窗口函數與group by 區別
窗口函數分組匯總會改變返回數據的行數,group by 默認返回每組中的第一行;而窗口函數不會減少返回數據行數,只可能是在原始數據表新加上一列,來存放窗口分析得到的值。
樣例8--group by返回數據行數
select user_type, count(1) as cnt from order_detail group by user_type樣例8測試結果:可以看到group by只得到兩條數據(一共兩個分組),而窗口函數作用后,原來是幾條數據,現在還是幾條數據。
| user_type | cnt | |
| 1 | new | 7 |
| 2 | old | 3 |
4.1.2 group by去重
group by可以完成去重效果,但是窗口函數不能。
答案:這是不行的,因為使用窗口函數并沒有減少數據量。那么什么時候用窗口的時候不能使用group by呢。參考下面一節的總結。
4.2 collect_list + partition by的特殊用法
參考: sparksql中collect_list+partitionby的特殊用法 - 百度文庫
4.2.1 問題提出
hive或者spark中collect_list一般都是用來做分組后的合并,也就是說經常和partition by連用,本節特定講一下collect_list + partition by的用法。
問題:如何求一個用戶的連續續課次數和續課金額?
背景:有嚴格規定的兩個課程期次序列:s1,s2,s3,s4和p1,p2,p3,我們規定滿足如下條件才算是一個用戶的續課;
a:當課程屬于同一課程序列的時候,必須遵循嚴格的期次順序,且不得有重復。如s1,s3,s4,s2,站在s2的角 度看,續了3次(s3,s4,s2);站在s3的角度看,續課了1次(s4);
b:當課程屬于不同課程序列的時候,只要后面跟的是不同的課程序列都算一次,重復值不算,如. s2,p2,p1,s2,站在s2的角度看,續費了2次(p2,p1);站在p2的角度看,續費了1次(s2)
4.2.2 解決思路
1)第一種思路:如果不考慮減少數據量,那么對于用戶參加的每一次的課程而言,附加一列生成一個這個用戶的全量有序的購課記錄就好,只是要記錄一下對應的這門課在整個序列中的位置。
2)第二種思路:最好的方式肯定是對于每一個用戶而言,附加一個該用戶在這門課之后有序的選課記錄,這里也有兩種方式。
a:自身關聯這張表,找出每一個用戶買了這門課之后的所有購課記錄和購課金額;
b:如果我們不想再關聯一次,那么我們就要考慮一下如何使用collect_list + partition by進行處理。
4.2.3 實際解決
給定數據
| user_id | term_id | pay_time |
| 1 | s1 | 1 |
| 1 | s2 | 2 |
| 1 | s3 | 3 |
| 1 | s4 | 4 |
代碼邏輯:
select user_type, count(1) as cnt from order_detail group by user_type測試結果:
| user_id | term_id | pay_time | category_list |
| 1 | s4 | 4 | ["s4,"] |
| 1 | s3 | 3 | ["s4,","s3,"] |
| 1 | s2 | 2 | ["s4,","s3,","s2,"] |
| 1 | s1 | 1 | ["s4,","s3,","s2,","s1,"] |
4.3 cube&grouping set這塊
?todo 高級函數
GROUPING SETS、GROUPING__ID、CUBE、ROLLUP這幾個分析函數通常用于OLAP中,不能累加,而且需要根據不同維度上鉆和下鉆的指標統計,比如,分小時、天、月的UV數
參考:Hive窗口函數保姆級教程
-
grouping sets是一種將多個group by邏輯寫在一個sql語句中的便利寫法。等價于將不同維度的group by結果集進行union all。
-
cube:根據group by的維度的所有組合進行聚合。
-
rollup的使用:是cube的子集,以最左側的維度為主,從該維度進行層級聚合。
五:窗口函數底層運行原理
?todo
六:其他注意事項
參考:
一文讀懂SQL窗口函數 - 墨天輪
SQL高級功能窗口函數及使用場景 - 知乎
SQL窗口函數不懂?五大應用場景讓你一步到位_數據小斑馬的博客-CSDN博客
HIVE 窗口及分析函數 應用場景_Data_IT_Farmer的博客-CSDN博客
Hive之窗口函數(一文搞懂)_For_dongyang的博客-CSDN博客_hive 窗口函數
Hive常用函數大全(二)(窗口函數、分析函數、增強group)_李國冬的博客-CSDN博客_常用窗口函數
總結
以上是生活随笔為你收集整理的hive窗口函数最全总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在vue中安装axios库
- 下一篇: java使用hdf.jar_在HDFVi