PostgreSQL之窗口函數的用法html
轉載請註明出處:http://www.javashuo.com/article/p-pymnsgua-e.htmlmysql
PostgreSQL的高級特性本準備三篇的(遞歸、窗口函數、JSON),結果中間一直一直加班 和遺忘 拖到如今才寫到中篇,欸,加班真不是一件好事情。sql
談談我對加班的見解吧=> 若是加班能控制在一個小時內,這樣會比較好(固然若是不加班的話更好),偶爾適當的加班能提升工做進度,對創業公司來講尤其重要;但,糟糕的地方也很多,加班時間長了容易形成思惟緩慢,這對腦子原本就不快的人來講傷害尤爲的大(我就是個例子),也容易形成頸椎病、高血壓、過勞。。。等等可怕的疾病,尤爲仍是作IT的必定要注意到這個問題,以上這些話可能有童鞋不會在乎,那我就在這裏說說我見過的真實的例子,我上一家公司的CTO有比較嚴重的脊椎病,(他說)坐的時間久了背部尤爲的難受,上一家公司總監也常常加班,可能再加上自己體質的緣由,心臟如今已經裝上了起搏器,一樣是上一家公司,個人一同事,也就比我大三歲左右,頭髮已經有至關部分白了哎,每見到這樣的事兒都很難受,人一生,若是沒有足夠的時間去關注生活,關注健康,咱們生活內容還剩下什麼?數據庫
此次我就簡單的講講PostgreSQL的高級特性>窗口函數
oracle
我先用表格列出PostgreSQL裏面的窗口函數,(源文檔在這裏>http://www.postgres.cn/docs/9.3/functions-window.html,推薦去postgre的中文社區看看)函數
講第一個問題以前我先扔出一個需求>如何給查詢出來的數據添加一列序號,用最簡單的方式實現?post
Oracle>使用rownum快速生成
測試
MySql>使用變量定義:(@i:=@i+1) as rowui
SqlServer>經過定義存儲過程的方式spa
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 ,各位晚安~