1. 程式人生 > 資料庫 >SQL開窗函式的具體實現詳解

SQL開窗函式的具體實現詳解

開窗函式:在開窗函數出現之前存在著很多用 SQL 語句很難解決的問題,很多都要通過複雜的相關子查詢或者儲存過程來完成。為了解決這些問題,在 2003 年 ISO SQL 標準加入了開窗函式,開窗函式的使用使得這些經典的難題可以被輕鬆的解決。目前在 MSSQLServer、Oracle、DB2 等主流資料庫中都提供了對開窗函式的支援,不過非常遺憾的是 MYSQL 暫時還未對開窗函式給予支援。

開窗函式簡介:與聚合函式一樣,開窗函式也是對行集組進行聚合計算,但是它不像普通聚合函式那樣每組只返回一個值,開窗函式可以為每組返回多個值,因為開窗函式所執行聚合計

算的行集組是視窗。在 ISO SQL 規定了這樣的函式為開窗函式,在 Oracle 中則被稱為分析函式。

資料表(Oracle):T_Person 表儲存了人員資訊,FName 欄位為人員姓名,FCity 欄位為人員所在的城市名,FAge 欄位為人員年齡,FSalary 欄位為人員工資

CREATE TABLE T_Person (FName VARCHAR2(20),FCity VARCHAR2(20),FAge INT,FSalary INT)

向 T_Person 表中插入一些演示資料:

INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Tom','BeiJing',20,3000);
INSERT INTO T_Person(FName,FSalary)
VALUES('Tim','ChengDu',21,4000);
INSERT INTO T_Person(FName,FSalary)
VALUES('Jim',22,3500);
INSERT INTO T_Person(FName,FSalary)
VALUES('Lily','London',2000);
INSERT INTO T_Person(FName,FSalary)
VALUES('John','NewYork',1000);
INSERT INTO T_Person(FName,FSalary)
VALUES('YaoMing',FSalary)
VALUES('Swing',FSalary)
VALUES('Guo',2800);
INSERT INTO T_Person(FName,FSalary)
VALUES('YuQian',24,8000);
INSERT INTO T_Person(FName,FSalary)
VALUES('Ketty',25,8500);
INSERT INTO T_Person(FName,FSalary)
VALUES('Kitty',FSalary)
VALUES('Merry',23,FSalary)
VALUES('Smith',30,FSalary)
VALUES('Bill',FSalary)
VALUES('Jerry',3300);
select * from t_person:

SQL開窗函式的具體實現詳解

要計算所有人員的總數,我們可以執行下面的 SQL 語句:SELECT COUNT(*) FROM T_Person

除了這種較簡單的使用方式,有時需要從不在聚合函式中的行中訪問這些聚合計算的值。比如我們想查詢每個工資小於 5000 元的員工資訊(城市以及年齡),並且在每行中都顯示所有工資小於 5000 元的員工個數:

select fname,fcity,fsalary,(select count(*) from t_person where fsalary < 5000) 工資少於5000員工總數
 from t_person
 where fsalary < 5000

SQL開窗函式的具體實現詳解

雖然使用子查詢能夠解決這個問題,但是子查詢的使用非常麻煩,使用開窗函式則可以大大簡化實現,下面的 SQL 語句展示瞭如果使用開窗函式來實現同樣的效果:

select fname,count(*) over() 工資小於5000員工數
 from t_person
 where fsalary < 5000

可以看到與聚合函式不同的是,開窗函式在聚合函式後增加了一個 OVER 關鍵字。

開窗函式格式: 函式名(列) OVER(選項)

OVER 關鍵字表示把函式當成開窗函式而不是聚合函式。SQL 標準允許將所有聚合函式用做開窗函式,使用 OVER 關鍵字來區分這兩種用法。

在上邊的例子中,開窗函式 COUNT(*) OVER()對於查詢結果的每一行都返回所有符合條件的行的條數。OVER 關鍵字後的括號中還經常新增選項用以改變進行聚合運算的視窗範圍。如果 OVER 關鍵字後的括號中的選項為空,則開窗函式會對結果集中的所有行進行聚合運算。

PARTITION BY 子句:

開窗函式的 OVER 關鍵字後括號中的可以使用 PARTITION BY 子句來定義行的分割槽來供進行聚合計算。與 GROUP BY 子句不同,PARTITION BY 子句建立的分割槽是獨
立於結果集的,建立的分割槽只是供進行聚合計算的,而且不同的開窗函式所建立的分割槽也不互相影響。下面的 SQL 語句用於顯示每一個人員的資訊以及所屬城市的人員數:

select fname,fage,count(*) over(partition by fcity) 所在城市人數 from t_person

COUNT(*) OVER(PARTITION BY FCITY)表示對結果集按照FCITY進行分割槽,並且計算當前行所屬的組的聚合計算結果。比如對於FName等於 Tom的行,它所屬的城市是BeiJing,同
屬於BeiJing的人員一共有6個,所以對於這一列的顯示結果為6。

這就不需要先對fcity分組求和,然後再和t_person表連線查詢了,省事兒。

SQL開窗函式的具體實現詳解

在同一個SELECT語句中可以同時使用多個開窗函式,而且這些開窗函式並不會相互幹
擾。比如下面的SQL語句用於顯示每一個人員的資訊、所屬城市的人員數以及同齡人的人數:

--顯示每一個人員的資訊、所屬城市的人員數以及同齡人的人數:
select fname,count(*) over(partition by fcity) 所屬城市的人個數,count(*) over(partition by fage) 同齡人個數
 from t_person

SQL開窗函式的具體實現詳解

ORDER BY子句:

開窗函式中可以在OVER關鍵字後的選項中使用ORDER BY子句來指定排序規則,而且有的開窗函式還要求必須指定排序規則。使用ORDER BY子句可以對結果集按
照指定的排序規則進行排序,並且在一個指定的範圍內進行聚合運算。ORDER BY子句的語法為:

ORDER BY 欄位名 RANGE|ROWS BETWEEN 邊界規則1 AND 邊界規則2

RANGE表示按照值的範圍進行範圍的定義,而ROWS表示按照行的範圍進行範圍的定義;邊界規則的可取值見下表:

SQL開窗函式的具體實現詳解

“RANGE|ROWS BETWEEN 邊界規則1 AND 邊界規則2”部分用來定位聚合計算範圍,這個子句又被稱為定位框架。

例子程式一:查詢從第一行到當前行的工資總和:

select fname,sum(fsalary) over(order by fsalary rows between unbounded preceding and current row) 到當前行工資求和
 from t_person

SQL開窗函式的具體實現詳解

這裡的開窗函式“SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN
UNBOUNDED PRECEDING AND CURRENT ROW)”
表示按照FSalary進行排序,然後計算從第
一行(UNBOUNDED PRECEDING)到當前行(CURRENT ROW)的和,這樣的計算結果就是按照
工資進行排序的工資值的累積和。

RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW”是開窗函式中最常使用的定位框架,為了簡化使用,如果使用的是這種定位框架,則可以省略定位框架宣告部分,
也就是說上邊的sql可以簡化成:

select fname,sum(fsalary) over(order by fsalary) 到當前行工資求和
 from t_person

例子程式二:把例子程式一的row換成了range,是按照範圍進行定位的

select fname,sum(fsalary) over(order by fsalary range between unbounded preceding and current row) 到當前行工資求和
 from t_person

SQL開窗函式的具體實現詳解

區別:

這個SQL語句與例1中的SQL語句唯一不同的就是“ROWS”被替換成了“RANGE”。“ROWS”是按照行數進行範圍定位的,而“RANGE”則是按照值範圍進行定位的,這兩個不同的定位方式主要用來處理並列排序的情況。比如 Lily、Swing、Bill這三個人的工資都是2000元,如果按照“ROWS”進行範圍定位,則計算從第一條到當前行的累積和,而如果 如果按照 “RANGE”進行範圍定位,則仍然計算從第一條到當前行的累積和,不過由於等於2000元的工資有三個人,所以計算的累積和為從第一條到2000元工資的人員結,所以對 Lily、Swing、Bill這三個人進行開窗函式聚合計算的時候得到的都是7000( “ 1000+2000+2000+2000 ”)。

下邊這的估計不常用:

例子程式三:

SELECT FName,FSalary,SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) 前二後二和
 FROM T_Person;

SQL開窗函式的具體實現詳解

這裡的開窗函式“SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)”表示按照FSalary進行排序,然後計算從當前行前兩行(2PRECEDING)到當前行後兩行(2 FOLLOWING)的工資和,注意對於第一條和第二條而言它們的“前兩行”是不存在或者不完整的,因此計算的時候也是要按照前兩行是不存在或者不完整進行計算,同樣對於最後兩行資料而言它們的“後兩行”也不存在或者不完整的,同樣要進行類似的處理。

例子程式四:

SELECT FName,SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN 1 FOLLOWING AND 3 FOLLOWING) 後面一到三之和
FROM T_Person;

SQL開窗函式的具體實現詳解

這裡的開窗函式“SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN 1 FOLLOWING AND 3 FOLLOWING)”表示按照FSalary進行排序,然後計算從當前行後一行(1 FOLLOWING)到後三行(3 FOLLOWING)的工資和。注意最後一行沒有後續行,其計算結果為空值NULL而非0。

例子程式五:算工資排名

SELECT FName,COUNT(*) OVER(ORDER BY FSalary ROWS BETWEEN UNBOUNDED PRECEDING AND
CURRENT ROW)
FROM T_Person;

SQL開窗函式的具體實現詳解

這裡的開窗函式“COUNT(*) OVER(ORDER BY FSalary RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)”表示按照FSalary進行排序,然後計算從第一行(UNBOUNDED PRECEDING)到當前行(CURRENT ROW)的人員的個數,這個可以看作是計算人員的工資水平排名。

不再用ROWNUM 了 省事了。這個over簡寫就會出錯。

例子程式6:結合max求到目前行的最大值

SELECT FName,MAX(FSalary) OVER(ORDER BY FAge) 此行之前最大值
FROM T_Person;

SQL開窗函式的具體實現詳解

這裡的開窗函式“MAX(FSalary) OVER(ORDER BY FAge)”是“MAX(FSalary) OVER(ORDER BY FAge RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)”的簡化寫法,它表示按照FSalary進行排序,然後計算從第一行(UNBOUNDED PRECEDING)到當前行(CURRENT ROW)的人員的最大工資值。

例子程式6:over(partition by XX order by XX) partition by和order by 結合

員工資訊+同齡人最高工資,按工資排序

SELECT FName,MAX(FSalary) OVER(PARTITION BY FAge order by fsalary) 同齡人最高工資
FROM T_Person;

SQL開窗函式的具體實現詳解

PARTITION BY子句和ORDER BY 可以 共 同 使用,從 而 可以 實現 更 加復 雜 的 功能

==================================================================================

高階開窗函式/ 排名的實現ROW_NUMBER();rank(),dense_rank()

除了可以在開窗函式中使用COUNT()、SUM()、MIN()、MAX()、AVG()等這些聚合函式,還可以在開窗函式中使用一些高階的函式,有些函式同時被DB2和Oracle同時支援,比如RANK()、DENSE_RANK()、ROW_NUMBER(),而有些函式只被Oracle支援,比如RATIO_TO_REPORT()、NTILE()、LEAD()、LAG()、FIRST_VALUE()、LAST_VALUE()。

下面對這幾個函式進行詳細介紹。

RANK()和DENSE_RANK()函式都可以用於計算一行的排名,不過對於並列排名的處理方式不同;ROW_NUMBER()函式計算一行在結果集中的行號,同樣可以將其當成排名函式。這三個函式的功能存在一定的差異,舉例如下:工資從高到低排名:

SELECT FName,RANK() OVER(ORDER BY fsalary desc) f_RANK,DENSE_RANK() OVER(ORDER BY fsalary desc) f_DENSE_RANK,ROW_NUMBER() OVER(ORDER BY fsalary desc) f_ROW_NUMBER
FROM T_Person;

SQL開窗函式的具體實現詳解

rank(),dense_rank()語法:

RANK()
dense_rank()
【語法】RANK ( ) OVER ( [query_partition_clause] order_by_clause )
dense_RANK ( ) OVER ( [query_partition_clause] order_by_clause )

【功能】聚合函式RANK 和 dense_rank 主要的功能是計算一組數值中的排序值。
【引數】dense_rank與rank()用法相當,
【區別】dence_rank在並列關係是,相關等級不會跳過。rank則跳過
rank()是跳躍排序,有兩個第二名時接下來就是第四名(同樣是在各個分組內)
dense_rank()l是連續排序,有兩個第二名時仍然跟著第三名。

row_number() 函式語法:

ROW_NUMBER()
【語法】ROW_NUMBER() OVER (PARTITION BY COL1 ORDER BY COL2)
【功能】表示根據COL1分組,在分組內部根據 COL2排序,而這個值就表示每組內部排序後的順序編號(組內連續的唯一的)
row_number() 返回的主要是“行”的資訊,並沒有排名
【引數】
【說明】Oracle分析函式

主要功能:用於取前幾名,或者最後幾名等

===================================================================

排序函式實際場景使用:計算排行榜,排名

微信活動,每天參與,有得分,活動結束後選出排名靠前的發獎。

每參與一次,就是一個訂單,表結構:

SQL開窗函式的具體實現詳解

比如要查詢期號issue為20170410期的排行榜,按得分倒敘排序,得分一樣按訂單建立先後,算排行,sql需要這麼寫:

select ROWNUM rank,t.*
 from (select *
   from t_zhcw_order
   where issue = '20170410'
   order by integral desc,create_date asc) t

SQL開窗函式的具體實現詳解

使用了開窗函式後就可以簡化:

select t.*,row_number() over(order by t.integral desc,t.create_date asc) 排名
   from t_zhcw_order t
   where issue = '20170410'

SQL開窗函式的具體實現詳解

如果想只要排名範圍,可以在外邊再包一層,這也是高效分頁的一種方式:

select tt.* from (
select t.id,t.integral,t.cell,t.create_date,t.create_date asc) rankNum
 from t_zhcw_order t
 where t.issue = 20170331
)tt where tt.rankNum<=50

到此這篇關於SQL開窗函式的具體實現詳解的文章就介紹到這了,更多相關SQL開窗函式內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!