1. 程式人生 > >postgresql系列之視窗函式

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基礎教程》