PostgreSQL學習筆記——視窗函式
在學習視窗函式之前,我們新建一個Product表並往其中插入一些資料:
drop table if exists Product; create table Product ( product_id char(4) not null, product_name varchar(100) not null, product_type varchar(32) not null, sale_price integer , purchase_price integer , regist_date date , primary key (product_id) ); begin transaction; insert into Product values ('0001', 'T恤衫', '衣服', 100, 50, '2018-10-10'); insert into Product values ('0002', '打孔器', '辦公用品', 50, 30, '2018-10-25'); insert into Product values ('0003', '運動T恤', '衣服', 400, 280, '2018-10-01'); insert into Product values ('0004', '菜刀', '廚房用具', 300, 280, '2018-11-11'); insert into Product values ('0005', '高壓鍋', '廚房用具', 680, 500, '2018-10-22'); insert into Product values ('0006', '叉子', '廚房用具', 50, NULL, '2018-10-08'); insert into Product values ('0007', '擦菜紙', '廚房用具', 88, 66, '2018-11-12'); insert into Product values ('0008', '圓珠筆', '辦公用品', 100, NULL, '2018-10-25'); commit;
什麼是視窗函式
視窗函式 也稱為 OLAP函式 。 OLAP是OnLine Analytical Processing的簡稱,意思是對資料庫資料進行實時分析處理。例如:市場分析、建立財務報表、建立計劃等日常性商務工作。 視窗函式就是為了實現OLAP而新增的標準SQL功能。
視窗函式的語法
<視窗函式> OVER ([PARTITION BY <列清單>] ORDER BY <排序用列清單>)
視窗函式大體分為以下兩種:
- 能夠作為視窗函式的聚合函式(SUM、AVG、COUNT、MAX、MIN)
- RANK、DENSE_RANK、ROW_NUMBER等 專用視窗函式
語法的基本使用——使用RANK函式
RANK是用來計算記錄排序的函式。 對於Product表中的8件商品,使用如下SQL可以根據不同的商品種類(product_type),按照銷售單價(sale_price)從低到高的順序排序:
select product_name, product_type, sale_price,
rank() over (partition by product_type
order by sale_price) as ranking
from Product;
結果如下所示:
product_name | product_type | sale_price | ranking |
---|---|---|---|
打孔器 | 辦公用品 | 50 | 1 |
圓珠筆 | 辦公用品 | 100 | 2 |
叉子 | 廚房用具 | 50 | 1 |
擦菜紙 | 廚房用具 | 88 | 2 |
菜刀 | 廚房用具 | 300 | 3 |
高壓鍋 | 廚房用具 | 680 | 4 |
T恤衫 | 衣服 | 100 | 1 |
運動T恤 | 衣服 | 400 | 2 |
以廚房用具為例,最便宜的“叉子”排在第1位,最貴的“高壓鍋”排在第4位,確實按照我們的要求進行了排序。
PARTITION BY
能夠設定排序的物件範圍。ORDER BY
能夠指定按照哪一列、何種順序進行排序。
無需指定PARTITION BY
我們刪除上面SQL的PARTITION BY子句,如下:
select product_name, product_type, sale_price,
rank() over (
order by sale_price) as ranking
from Product;
結果如下:
product_name | product_type | sale_price | ranking |
---|---|---|---|
叉子 | 廚房用具 | 50 | 1 |
打孔器 | 辦公用品 | 50 | 1 |
擦菜紙 | 廚房用具 | 88 | 3 |
T恤衫 | 衣服 | 100 | 4 |
圓珠筆 | 辦公用品 | 100 | 4 |
菜刀 | 廚房用具 | 300 | 6 |
運動T恤 | 衣服 | 400 | 7 |
高壓鍋 | 廚房用具 | 680 | 8 |
之前我們得到的是按照商品種類分組後的排序,而這次變成了全部商品的排序。
專用視窗函式的種類
接下來我們來總結以下具有代表性的專用視窗函式:
RANK函式 計算排序時,如果存在相同位次的記錄,則會跳過之後的位次。 例)有3條記錄排在第一位時,1位、1位、1位、4位……
DENSE_RANK函式 計算排序時,即使存在相同位次的記錄,也不會跳過之後的位次。 例)有3條記錄排在第一位時,1位、1位、1位、2位……
ROW_NUMBER函式 賦予唯一的連續位次。 例)有3條記錄排在第一位時,1位、2位、3位、4位……
視窗函式的適用範圍
視窗函式只能放在SELECT子句之中。 也就是說,這類函式不能再WHERE子句或者GROUP BY子句中使用。
作為視窗函式使用的聚合函式
將SUM函式作為聚合函式使用:
select product_name, product_type, sale_price,
sum(sale_price) over (order by product_id) as current_sum
from Product;
結果:
product_name | product_type | sale_price | current_sum | 解釋 |
---|---|---|---|---|
T恤衫 | 衣服 | 100 | 100 | <--100 |
打孔器 | 辦公用品 | 50 | 150 | <--100+50 |
運動T恤 | 衣服 | 400 | 550 | <--100+50+400 |
菜刀 | 廚房用具 | 300 | 850 | |
高壓鍋 | 廚房用具 | 680 | 1530 | |
叉子 | 廚房用具 | 50 | 1580 | |
擦菜紙 | 廚房用具 | 88 | 1668 | |
圓珠筆 | 辦公用品 | 100 | 1768 |
視窗函式一般都會使用這種稱為 累計 的統計方法。
將AVG函式作為視窗函式使用:
select product_name, product_type, sale_price,
avg(sale_price) over (order by product_id) as current_avg
from Product;
結果:
product_name | product_type | sale_price | current_avg | 解釋 |
---|---|---|---|---|
T恤衫 | 衣服 | 100 | 100.0000000000000000 | <--(100)/1 |
打孔器 | 辦公用品 | 50 | 75.0000000000000000 | <--(100+50)/2 |
運動T恤 | 衣服 | 400 | 183.3333333333333333 | <--(100+50+400)/3 |
菜刀 | 廚房用具 | 300 | 212.5000000000000000 | |
高壓鍋 | 廚房用具 | 680 | 306.0000000000000000 | |
叉子 | 廚房用具 | 50 | 263.3333333333333333 | |
擦菜紙 | 廚房用具 | 88 | 238.2857142857142857 | |
圓珠筆 | 辦公用品 | 100 | 221.0000000000000000 |
從以上兩個結果中我們可以看到,current_sum和current_avg的計算方法都是包含“排在自己之上”的記錄。像這樣的“自身記錄( 當前記錄 )”作為基準進行統計,就是將聚合函式當作視窗函式使用時的最大特徵。
計算移動平均
執行如下SQL:
select product_name, product_type, sale_price,
avg(sale_price) over (order by product_id
rows 2 preceding) as moving_avg
from Product;
結果:
product_name | product_type | sale_price | moving_avg | 解釋 |
---|---|---|---|---|
T恤衫 | 衣服 | 100 | 100.0000000000000000 | <-- (100)/1 |
打孔器 | 辦公用品 | 50 | 75.0000000000000000 | <-- (100+50)/2 |
運動T恤 | 衣服 | 400 | 183.3333333333333333 | <-- (100+50+400)/3 |
菜刀 | 廚房用具 | 300 | 250.0000000000000000 | <-- (50+400+300)/3 |
高壓鍋 | 廚房用具 | 680 | 460.0000000000000000 | <-- (400+300+600)/3 |
叉子 | 廚房用具 | 50 | 343.3333333333333333 | |
擦菜紙 | 廚房用具 | 88 | 272.6666666666666667 | |
圓珠筆 | 辦公用品 | 100 | 79.3333333333333333 |
可以發現第4行資料和之前的結果不一樣了,這是因為我們指定了“框架”,將彙總物件限定為了“最靠近的3行”。 這裡我們使用 ROW (“行”)和 PRECEDING (“之前”)兩個關鍵字,限定的查詢的結果只包含本身這行和它之前的兩行(如果有的話)。 使用關鍵字 FOLLOWING (“之後”)替換 PRECEDING ,就可以指定“包含之後的幾行”。
示例:將當前記錄的前後行作為彙總物件:
select product_name, product_type, sale_price,
avg(sale_price) over (order by product_id
rows between 1 preceding and 1 following) as moving_avg
from Product;
結果:
product_name | product_type | sale_price | moving_avg |
---|---|---|---|
T恤衫 | 衣服 | 100 | 75.0000000000000000 |
打孔器 | 辦公用品 | 50 | 183.3333333333333333 |
運動T恤 | 衣服 | 400 | 250.0000000000000000 |
菜刀 | 廚房用具 | 300 | 460.0000000000000000 |
高壓鍋 | 廚房用具 | 680 | 343.3333333333333333 |
叉子 | 廚房用具 | 50 | 272.6666666666666667 |
擦菜紙 | 廚房用具 | 88 | 79.3333333333333333 |
圓珠筆 | 辦公用品 | 100 | 94.0000000000000000 |
兩個ORDER BY
OVER子句中的ORDER BY只是用來決定視窗函式按照什麼樣的順序進行計算的,對結果的順序並沒有影響。所以,要對結果進行排序,還需要新增另一個ORDER BY子句,例:
select product_name, product_type, sale_price,
rank() over (order by sale_price) as ranking
from Product
order by ranking;
結果:
product_name | product_type | sale_price | ranking |
---|---|---|---|
叉子 | 廚房用具 | 50 | 1 |
打孔器 | 辦公用品 | 50 | 1 |
擦菜紙 | 廚房用具 | 88 | 3 |
T恤衫 | 衣服 | 100 | 4 |
圓珠筆 | 辦公用品 | 100 | 4 |
菜刀 | 廚房用具 | 300 | 6 |
運動T恤 | 衣服 | 400 | 7 |
高壓鍋 | 廚房用具 | 680 | 8 |