postgresql系列之視窗函式
本文是《sql基礎教程》《postgresql實戰》的讀書筆記;具體可以參考這兩本書相關章節。
一、 視窗函式
1.1 基本概念
視窗函式可以進行排序、生成序列號等一般的聚合函式無法實現的高階操作;聚合函式將結果集進行計算並且通常返回一行。視窗函式也是基於結果集的運算。與聚合函式不同的是,視窗函式並不會將結果集進行分組合並輸出一行;而是將計算的結果合併到基於結果集運算的列上。
思考:為什麼說視窗函式是基於結果集的預算 ?
解讀: 關於這個問題,則必須要談談 查詢語句的邏輯處理順序
SQL 邏輯處理順序
- FROM
- WHERE
- GROUP BY
- HAVING
- SELECT
- 表示式
- DISTINCT
- ORDER BY
- OFFSET-FETCH
注:在over()
子句中指定的order by
子句不應該與顯示排序混淆,並且並且它不會改變結果的關係本質。
根據視窗函式的執行順序的位置便可以知道:視窗函式是基於結果集進行運算的。它將計算出的結果合併到輸出的結果集上。
1.2 語法相關
<視窗函式>
OVER ([PARTITION BY <列清單>]
ORDER BY <排序用列清單>)
over
:視窗函式關鍵字partition by
:對結果集進行分組order by
**可作為視窗函式的函式分類: **
聚合函式:① 聚合函式(SUM、AVG、COUNT、MAX、MIN) 內建函式:② RANK、DENSE_RANK、ROW_NUMBER 等專用視窗函
1.3 入門案例
聚合函式後接 over
屬性的視窗函式表示在一個查詢結果集上應用聚合函式。
描述: 查詢每名學生學習成績並且顯示課程的平均分。
-- 建立成績表
create table score(
id serial PRIMARY key,
subject character(32),
stu_name character(32),
grade NUMERIC (3,0)
);
-- 插入資料
INSERT INTO SCORE(subject,stu_name,grade) values
('語文','小王',80),
('語文','小張',70),
('語文','小李',80),
('英語','小王',90),
('英語','小張',70),
('英語','小李',50),
('數學','小王',100),
('數學','小張',70),
('數學','小李',65)
使用 group by
和 視窗函式
但是:我在寫sql查詢的時候,不小心在視窗函式增加了排序欄位。導致結果不是我想要的。如下圖所示:
思考:在視窗函式中使用order by 導致最後資料並非所希望的;
問題一:那什麼時候應該用order by
;
問題二:聚合函式型別的視窗函式中使用order by
是什麼意思呢。
嘗試尋找規律
(一) 去除視窗函式中的 order by
和 最外層的 order by
順序可能不一致,資料行能夠對應上,最終資料也是正確的。
(二) 去除最外層的 order by
。 順序不一致,記錄相等,但最後結果不正確。
(三) 單獨使用 subject進行排序,結果是正確的。
通過這種方式去探索可能不是一個好的主意;另闢蹊徑 (參考《sql基礎教程》)
1.4 作為視窗函式使用的聚合函式
先準備資料;
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));
INSERT INTO Product VALUES ('0001', 'T恤' ,'衣服', 1000, 500, '2009-09-20');
INSERT INTO Product VALUES ('0002', '打孔器', '辦公用品', 500, 320, '2009-09-11');
INSERT INTO Product VALUES ('0003', '運動T恤', '衣服', 4000, 2800, NULL);
INSERT INTO Product VALUES ('0004', '菜刀', '廚房用具', 3000, 2800, '2009-09-20');
INSERT INTO Product VALUES ('0005', '高壓鍋', '廚房用具', 6800, 5000, '2009-01-15');
INSERT INTO Product VALUES ('0006', '叉子', '廚房用具', 500, NULL, '2009-09-20');
INSERT INTO Product VALUES ('0007', '擦菜板', '廚房用具', 880, 790, '2008-04-28');
INSERT INTO Product VALUES ('0008', '圓珠筆', '辦公用品', 100, NULL, '2009-11-11');
先用 SUM
函式作為視窗函式使用 的例子
SELECT product_id, product_name, sale_price,
SUM (sale_price) OVER (ORDER BY product_id) AS current_sum
FROM Product;
但是我們得到的並不僅僅是合計值,而是按照 ORDER BY
子句指定 的 product_id
的升序進行排列,計算出商品編號“小於自己”的商品 的銷售單價的合計值。因此,計算該合計值的邏輯就像金字塔堆積那樣,一行一行逐漸新增計算物件。在按照時間序列的順序,計算各個時間的銷 售額總額等的時候,通常都會使用這種稱為累計
的統計方法
使用其他聚合函式時的操作邏輯也和本例相同
SELECT product_id, product_name, sale_price,
AVG (sale_price) OVER (ORDER BY product_id) AS current_avg
FROM Product;
從結果中可以看到,current_avg
的計算方法確實是計算平 均值的方法,但作為統計物件的卻只是“排在自己之上”的記錄。像這樣 以“自身記錄(當前記錄)”作為基準進行統計,就是將聚合函式當作視窗函式使用時的最大特徵。
兩個order by
OVER
子句中的 ORDER BY
只是用來決定 視窗函式按照什麼樣的順序進行計算的,對結果的排列順序並沒有影響; 而需要在select的最後指定排序,不然整個結果集不確定順序。主意:這兩個order by的作用和意思完全不同。
通過上面的學習,先前不是自己想要的記錄的語句被解釋清楚了。
1.5 理解:partition by 與 order by
PARTITION BY 在橫向上對錶進行分組,而 ORDER BY 決定了縱向排序的規則
視窗函式兼具之前我們學過的GROUP BY
子句的分組功能以及 ORDER BY
子句的排序功能。但是,PARTITION BY
子句並不具備 GROUP BY
子句的彙總功
通過 PARTITION BY
分組後的記錄集合稱為視窗。此處的視窗並 非“窗戶”的意思,而是代表範圍。這也是“視窗函式”名稱的由來
1.6 內建函式之 RANK、DENSE_RANK、ROW_NUMBER
RANK
函式 計算排序時,如果存在相同位次的記錄,則會跳過之後的位次。(行號產生間隙)
有 3 條記錄排在第 1 位時:1 位、1 位、1 位、4 位……
DENSE_RANK
函式 同樣是計算排序,即使存在相同位次的記錄,也不會跳過之後的位次。
有 3 條記錄排在第 1 位時:1 位、1 位、1 位、2 位……
ROW_NUMBER
函式 賦予唯一的連續位次。(可用於做分頁的行號)
有 3 條記錄排在第 1 位時:1 位、2 位、3 位、4 位……
案例: 在插入幾條資料,驗證三個函式
INSERT INTO SCORE(subject,stu_name,grade) values
('語文','小強',80),
('英語','小強',90),
('數學','小強',100)
擴充套件ROW_NUMBER
可以對ROW_NUMBER
做擴充套件,因為其標註的行號是從0開始。可以不指定partition by
引數。這樣ROW_NUMBER
視窗函式顯示所有記錄的行號。
另外:如果order by 能夠確定每一行都唯一(如,按照主鍵排序);則可以用行號做分頁查詢。
1.7 內建函式 firt_value() 、last_value()、nth_value()
first_value()
用來取結果集每一個分組的第一行資料的欄位值。last_value()
用來取結果集每一個分組的最後一行資料的欄位值。nth_value()
用來取結果集每一個分組的指定行數的欄位值。(如果不存在返回null)
-- 學科分組,分數排序降序,取最高值
SELECT first_value(grade) OVER(PARTITION BY subject ORDER BY grade DESC),*
FROM score;
SELECT nth_value(grade,1) OVER(PARTITION BY subject ORDER BY grade DESC),*
FROM score;
--
-- last_value 新增 order by 不起作用?(pg9.6)
SELECT last_value(grade) OVER(PARTITION BY subject ORDER BY grade DESC),*
FROM score;
思考: last_value 語句不起作用?
補充 視窗函式別名的使用
如果SQL中需要多次使用視窗函式,可以使用視窗函式別名,語法如下:
SELECT ...FROM ... WINDOW window_name AS (window_definition)
例如:
1.8 lag()
獲取行偏移offset那行某個欄位的資料
語法格式
lag(value anyelement) [,offset integer [, default anyelement ]])
其中offset
預設值為1 default
預設值為null
1.9 計算移動平均
視窗函式就是將表以視窗為單位進行分割,並在其中進行排序的函式。 其實其中還包含在視窗中指定更加詳細的彙總範圍的備選功能,該備選功 能中的彙總範圍稱為框架
SELECT product_id, product_name,sale_price,
AVG (sale_price) OVER (
ORDER BY
product_id ROWS 2 PRECEDING
) AS moving_avg
FROM
Product
指定框架(彙總範圍)
我們將上述結果與之前的結果進行比較,可以發現商品編號為“0004
” 的“菜刀”以下的記錄和視窗函式的計算結果並不相同。這是因為我們指 定了框架,將彙總物件限定為了“最靠近的 3行
”。
這裡我們使用了 ROWS
(“行”)和 PRECEDING
(“之前”)兩個關鍵 字,將框架指定為“截止到之前 ~ 行”,因此“ROWS 2 PRECEDING
" 這樣的統計方法稱為移動平均
使用關鍵字FOLLOWING
(“之後”)替換 PRECEDING
,就可以指 定“截止到之後 ~ 行”作為框架了
將當前記錄的前後行作為彙總物件
如果希望將當前記錄的前後行作為彙總物件時,同時使用 PRECEDING
(“之前”)和 FOLLOWING
(“之後”)關 鍵字來實現
SELECT
product_id,
product_name,
sale_price,
AVG (sale_price) OVER (
ORDER BY product_id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS moving_avg
FROM
Product;
參考
- 《sql基礎教程》
- 《postgresql實戰》
- 《sql server 2012 T-sql基礎教程》