1. 程式人生 > >PostgreSQL>窗口函數的用法

PostgreSQL>窗口函數的用法

cti wid 查詢語句 業務開發 只需要 表結構 需求 數據庫 target

PostgreSQL之窗口函數的用法

轉載請註明出處:https://www.cnblogs.com/funnyzpc/p/9311281.html

  PostgreSQL的高級特性本準備三篇的(遞歸、窗口函數、JSON),結果中間一直一直加班 和遺忘 拖到現在才寫到中篇,欸,加班真不是一件好事情。

  談談我對加班的看法吧=> 如果加班能控制在一個小時內,這樣會比較好(當然如果不加班的話更好),偶爾適當的加班能提高工作進度,對創業公司來說尤為重要;但,糟糕的地方也不少,加班時間長了容易造成思維緩慢,這對腦子本來就不快的人來說傷害尤其的大(我就是個例子),也容易造成頸椎病、高血壓、過勞。。。

等等可怕的疾病,尤其還是做IT的一定要註意到這個問題,以上這些話可能有童鞋不會在意,那我就在這裏說說我見過的真實的例子,我上一家公司的CTO有比較嚴重的脊椎病,(他說)坐的時間久了背部尤其的難受,上一家公司總監也經常加班,可能再加上本身體質的原因,心臟現在已經裝上了起搏器,同樣是上一家公司,我的一同事,也就比我大三歲左右,頭發已經有相當部分白了哎,每見到這樣的事兒都很難受,人一輩子,如果沒有足夠的時間去關註生活,關註健康,我們生活內容還剩下什麽?

  這次我就簡單的講講PostgreSQL的高級特性>窗口函數

  我先用表格列出PostgreSQL裏面的窗口函數,(源文檔在這裏>http://www.postgres.cn/docs/9.3/functions-window.html,推薦去postgre的中文社區看看)

技術分享圖片

  講第一個問題之前我先扔出一個需求>如何給查詢出來的數據添加一列序號,用最簡單的方式實現?

    Oracle>使用rownum快速生成

    MySql>使用變量定義:(@i:=@i+1) as row

    SqlServer>通過定義存儲過程的方式

    PostgreSQL>通過函數generate_series(start_value,end_value)

  額,以上方式我大概都用過,對於Oracle的方式雖然語句簡單,但是涉及到排序的時候可就亂了,mysql的方式也還算可以,但是這樣並沒有通用性,子查詢的時候會相當麻煩,同時個人覺得這更像是存儲過程和sql的結合體,也破壞了Sql本該有的形式,PostgreSQL的方式雖然不錯,但是總要指定起始和終止值,這個在生成測試數據的時候還好用,具體業務開發用起來可就麻煩多了;這裏,當然有更好的實現方式>窗口函數,這個屬性在主流的數據庫系統中都有實現(以前用oracle的時候竟然沒發現這麽好用的東西,好遺憾)。

  這裏我先放出表結構語句:

 1 DROP TABLE IF EXISTS "public"."products";
 2 CREATE TABLE "public"."products" (
 3     "id" varchar(10) COLLATE "default",
 4     "name" text COLLATE "default",
 5     "price" numeric,
 6     "uid" varchar(14) COLLATE "default",
 7     "type" varchar(100) COLLATE "default"
 8 )
 9 WITH (OIDS=FALSE);
10 
11 BEGIN;
12 INSERT INTO "public"."products" VALUES (0006, iPhone X, 9600, null, 電器);
13 INSERT INTO "public"."products" VALUES (0012, 電視, 3299, 4, 電器);
14 INSERT INTO "public"."products" VALUES (0004, 辣條, 5.6, 4, 零食);
15 INSERT INTO "public"."products" VALUES (0007, 薯條, 7.5, 1, 零食);
16 INSERT INTO "public"."products" VALUES (0009, 方便面, 3.5, 1, 零食);
17 INSERT INTO "public"."products" VALUES (0005, 鉛筆, 7, 4, 文具);
18 INSERT INTO "public"."products" VALUES (0014, 作業本, 1, null, 文具);
19 INSERT INTO "public"."products" VALUES (0001, 鞋子, 27, 2, 衣物);
20 INSERT INTO "public"."products" VALUES (0002, 外套, 110.9, 3, 衣物);
21 INSERT INTO "public"."products" VALUES (0013, 圍巾, 93, 5, 衣物);
22 INSERT INTO "public"."products" VALUES (0008, 香皂, 17.5, 2, 日用品);
23 INSERT INTO "public"."products" VALUES (0010, 水杯, 27, 3, 日用品);
24 INSERT INTO "public"."products" VALUES (0015, 洗發露, 36, 1, 日用品);
25 INSERT INTO "public"."products" VALUES (0011, 毛巾, 15, 1, 日用品);
26 INSERT INTO "public"."products" VALUES (0003, 手表, 1237.55, 5, 電器);
27 INSERT INTO "public"."products" VALUES (0016, 繪圖筆, 15, null, 文具);
28 INSERT INTO "public"."products" VALUES (0017, 汽水, 3.5, null, 零食);
29 COMMIT;

這我先用第一個函數row_number() ,一句即可實現>

  select type,name,price,row_number() over(order by price asc) as idx from products ;

結果>

技術分享圖片

用窗口函數的好處不僅僅可實現序號列,還可以在over()內按指定的列排序,上圖是按照price列升序。

  這裏,對於以上提到的一個問題,根據上面的數據 我再做個擴充>如果需要在類別(type)內按照價格(price) 升序排列(就是在類別內做排序),該怎麽做呢?

  當然也很簡單,只需要在窗口(over())中聲明分隔方式 Partition .

  分類排序序號,row_number() 實現>

 select type,name,price,row_number() over(PARTITION by type order by price asc) as idx from products ;

  查詢結果>

技術分享圖片

  上面的問題這裏需求完美實現,額,這裏其實還可以做個擴充,你可以註意到零食類別內的 方便面和汽水價格是一樣的,如何將零食和汽水並列第一呢?答案是:用窗口函數>rank()

   分類排序序號並列, rank() 實現>

 SELECT type,name,price,rank() over(partition by type order by price asc) from products;

  SQL輸出>

技術分享圖片

  需求又完美的實現了,但,註意到沒,零食類別中的第三個 辣條 排到第三了,如果這裏需要在類別裏面能保持序號不重不少(將辣條排名至第二),如何實現呢?答案>使用窗口函數 dense_rank()

  分類排序序號並列順序,dense_rank() 實現>

 SELECT type,name,price,dense_rank() over(partition by type order by price asc) from products;

  SQL輸出>

技術分享圖片

  OK,以上的幾個窗口函數已經能實現大多數業務需求了,如果有興趣可以看看一些特殊業務可能用到的功能,比如說如何限制序號在0到1之間排序呢?

  限制序號在0~1之間(0作為第一個序),窗口函數 percernt_rank() >

 SELECT type,name,price,percent_rank() over(partition by type order by price asc) from products;

  SQL語句輸出>

技術分享圖片

註意:上面的percernt_rank()函數默認是從0開始排序的,如果需要使用相對0~1之間的排名,需要這樣:

  限制序號在0~1之間相對排名,窗口函數 cume_dist() 實現>

SELECT type,name,price,cume_dist() over(partition by type order by price asc) from products;

  SQL語句輸出>

技術分享圖片

註意:上面的序號是相對於0開始排序的。

  對於排序序號還可以限制最大序號,這樣做:

  限制最大序號為指定數字序號 ntile(val1) 實現 >

SELECT type,name,price,ntile(2) over(partition by type order by price asc) from products;

  SQL語句輸出 >

技術分享圖片

  窗口函數還可以實現在子分類排序的情況下取偏移值,這樣實現>

  獲取到排序數據的每一項的偏移值(向下偏移) , lag(val1,val2,val3) 函數實現>

SELECT id,type,name,price,lag(id,1,‘‘) over(partition by type order by price asc) as topid from products;

  SQL語句輸出 >

技術分享圖片

註意:函數lag(val1,val2,val3) 中的三個參數分別為->(輸出的上一條記錄的字段,偏移值,無偏移值的默認值);以上這裏的偏移值為1,偏移字段為id,無偏移默認值為空(‘‘)

  若獲取數據項偏移值(向上偏移) , lead(val1,val2,val3)>

SELECT id,type,name,price,lead(id,1,‘‘) over(partition by type order by price asc) as downid from products;

  SQL 語句輸出 >

技術分享圖片

  當然,窗口函數還可以實現每個子類排序中的第一項的某個字段的值,可以這樣實現:

  獲取分類子項排序中的第一條記錄的某個字段的值, first_value(val1) 實現>

 SELECT id,type,name,price,first_value(name) over(partition by type order by price asc) from products;

  SQL語句輸出>

技術分享圖片

註意:以上函數取的是排序子類記錄中的第一條記錄的name字段。

  當然也可以向下取分類排序中的最後一條記錄的某個字段, last_value(val1)實現>

 SELECT id,type,name,price,last_value(name) over(partition by type order by price range between unbounded preceding and unbounded following) from products; -- order by type asc ;-- ,price asc;

  SQL 語句輸出 >

技術分享圖片

額,這裏需要說明的是,當取分類在最後一條記錄的時候 自然排序下不可以在over() 使用排序字段,不然取得的值為相對於當前記錄的值,故這裏按價格(price) 升序的時候指定 排序字段 -> range between unbounded preceding and unbounded following

  窗口函數還能在分類排序下取得指定序號記錄的某個字段,這樣:

  取得排序字段項目中指定序號記錄的某個字段值, nth_value(val1,val2)>

SELECT id,type,name,price,nth_value(name,2) OVER(partition by type order by price range between unbounded preceding and unbounded following ) from products;

  SQL語句輸出 >

技術分享圖片

  額,窗口函數在單獨使用的時候能省略很多不必要的查詢 ,比如子查詢、聚合查詢,當然窗口函數能做得更多(配合聚合函數使用的時候) ,額,這裏我給出一個示例 >

  SQL查詢語句 ,窗口函數+聚合函數 實現 >

sum(price) over (partition by type) 類別金額合計,
(sum(price) over (order by type))/sum(price) over() 類別總額占所有品類商品百分比,
round(price/(sum(price) over (partition by type rows between unbounded preceding and unbounded following)),3) 子除類別百分比,
rank() over (partition by type order by price desc) 排名,
sum(price) over() 金額總計
from products ORDER BY type,price asc;

  SQL 語句輸出>

技術分享圖片

上面的語句看起來會有點兒暈,查詢語句子項就像是在輸出參數項裏面直接寫子查詢的感覺,事實上為使語句有更好的可讀性,窗口條件可以放在from後面 ,這樣子>

 1 select 
 2     id,type,name,price,
 3     sum(price) over w1 類別金額合計,
 4     (sum(price) over (order by type))/sum(price) over() 類別總額占所有品類商品百分比,
 5     round(price/(sum(price) over w2),3) 子除類別百分比,
 6     rank() over w3 排名,
 7     sum(price) over() 金額總計
 8 from 
 9     products 
10 WINDOW 
11     w1 as (partition by type),
12     w2 as (partition by type rows between unbounded preceding and unbounded following),
13     w3 as (partition by type order by price desc)
14 ORDER BY 
15     type,price asc; 

現在是 2018-07-22 21:59:31 ,各位晚安~

PostgreSQL>窗口函數的用法