1. 程式人生 > >PostgreSQL學習筆記——視窗函式

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 <排序用列清單>)

視窗函式大體分為以下兩種:

  1. 能夠作為視窗函式的聚合函式(SUM、AVG、COUNT、MAX、MIN)
  2. 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