PostgreSQL=>遞歸查詢html
轉載請註明源地址:http://www.cnblogs.com/funnyzpc/p/8232073.html數據庫
距上次博客更新恰好兩週,這兩週發生了不少,好比:SFTP服務拉取數據,第三方公共平臺接口邏輯遷移新框架,新框架(Spring Cloud)上手,公司月報和審計數據獲取等等。。。,差很少都有無盡的坑,尤爲是最後者,實是折騰人啊~;牢騷歸牢騷,可是事情仍是要認真作的,。。。,就目前來看,這些對於我最大的好處就是有助於快速理解公司業務邏輯
;啊哈~,扯完,從這些日子開始抽週末時間學習數據庫->PosgreSQL(我的慣稱:大象
api
),遂從本節起說PostgreSQL有關的動西。數組
記得在上一家公司的時候作過一個冷門的附加功能,就是把根據傳入的部門ID(一個List)查找部門下全部的人員,當時是Oracle數據庫配合着Mybatis來作的,中間填過兩個坑,一個是Mybatis的forach的參數個數超過1K會報錯,致使遞歸不能查詢,另外一個坑是Oracle的遞歸造型稍難,這個。。。我翻了幾篇博客寫了好幾行註釋加以理解,但願後來人能明白前人的良苦用心。因爲新買MBP 未裝Oracle環境,oracle的遞歸講解就此略過哈(◡‿◡✿)o~oracle
首先給出一個測試表(elevel) 關於職稱級別的表,一位數的ID是最大分類(英語、計算機、會計),而後子級別的parent_id字段引用父級ID,有些級別比較籠統這裏不討論哈~:框架
testDB=> select * from elevel order by rpad(id::varchar,5,'0');函數
id | name | parent_id 學習
------+----------------------+-----------測試
1 | 英語 | spa
11 | 英語專業四八級 | 1
111 | 英語專業四級 | 11
112 | 英語專業八級 | 11
12 | 大學英語3、4、六級 | 1
121 | 大學英語三級 | 12
122 | 大學英語四級 | 12
123 | 大學英語六級 | 12
2 | 計算機 |
21 | NCR計算機等級 | 2
211 | NCR計算機一級 | 21
212 | NCR計算機二級 | 21
213 | NCR計算機三級 | 21
214 | NCR計算機四級 | 21
22 | IT認證類考試 | 2
221 | CISCO認證 | 22
222 | ORACLE認證 | 22
3 | 會計 |
31 | 會計從業證 | 3
32 | 會計職稱 | 3
321 | 初級職稱(助理會計師) | 32
322 | 中級職稱(會計師) | 32
323 | 高級職稱(高級職稱) | 32
3231 | 正高級會計師 | 323
3232 | 副高級會計師 | 323
(25 rows)
數據造型已經給出了,這裏我放出建表語句及測試數據:
1 -- create table
2 CREATE TABLE elevel 3 ( 4 id integer, 5 "name" CHARACTER VARYING (20), 6 parent_id integer
7 ); 8
9 -- insert data
10 INSERT INTO elevel (id, "name", parent_id) VALUES (1, '英語', NULL); 11 INSERT INTO elevel (id, "name", parent_id) VALUES (2, '計算機', NULL); 12 INSERT INTO elevel (id, "name", parent_id) VALUES (3, '會計', NULL); 13 INSERT INTO elevel (id, "name", parent_id) VALUES (11, '英語專業四八級', 1); 14 INSERT INTO elevel (id, "name", parent_id) VALUES (111, '英語專業四級', 11); 15 INSERT INTO elevel (id, "name", parent_id) VALUES (112, '英語專業八級', 11); 16 INSERT INTO elevel (id, "name", parent_id) VALUES (121, '大學英語三級', 12); 17 INSERT INTO elevel (id, "name", parent_id) VALUES (122, '大學英語四級', 12); 18 INSERT INTO elevel (id, "name", parent_id) VALUES (12, '大學英語3、4、六級', 1); 19 INSERT INTO elevel (id, "name", parent_id) VALUES (123, '大學英語六級', 12); 20 INSERT INTO elevel (id, "name", parent_id) VALUES (21, 'NCR計算機等級', 2); 21 INSERT INTO elevel (id, "name", parent_id) VALUES (22, 'IT認證類考試', 2); 22 INSERT INTO elevel (id, "name", parent_id) VALUES (211, 'NCR計算機一級', 21); 23 INSERT INTO elevel (id, "name", parent_id) VALUES (212, 'NCR計算機二級', 21); 24 INSERT INTO elevel (id, "name", parent_id) VALUES (213, 'NCR計算機三級', 21); 25 INSERT INTO elevel (id, "name", parent_id) VALUES (214, 'NCR計算機四級', 21); 26 INSERT INTO elevel (id, "name", parent_id) VALUES (221, 'CISCO認證', 22); 27 INSERT INTO elevel (id, "name", parent_id) VALUES (222, 'ORACLE認證', 22); 28 INSERT INTO elevel (id, "name", parent_id) VALUES (31, '會計從業證', 3); 29 INSERT INTO elevel (id, "name", parent_id) VALUES (32, '會計職稱', 3); 30 INSERT INTO elevel (id, "name", parent_id) VALUES (321, '初級職稱(助理會計師)', 32); 31 INSERT INTO elevel (id, "name", parent_id) VALUES (322, '中級職稱(會計師)', 32); 32 INSERT INTO elevel (id, "name", parent_id) VALUES (323, '高級職稱(高級職稱)', 32); 33 INSERT INTO elevel (id, "name", parent_id) VALUES (3231, '正高級會計師', 323); 34 INSERT INTO elevel (id, "name", parent_id) VALUES (3232, '副高級會計師', 323); 35 COMMIT;
如今我定一個需求:查詢「會計」(id=3)類別下的全部的子記錄(包含id=3的記錄):
1 WITH RECURSIVE le (id,name,parent_id) as
2 ( 3 select id,name,parent_id from elevel where id=3
4 union all
5 select e2.id,e2.name,e2.parent_id from elevel e2,le e3 where e3.id=e2.parent_id 6 ) 7 select * from le order by rpad(id::varchar,5,'0') asc;
查詢結果:
id | name | parent_id
------+----------------------+-----------
3 | 會計 |
31 | 會計從業證 | 3
32 | 會計職稱 | 3
321 | 初級職稱(助理會計師) | 32
322 | 中級職稱(會計師) | 32
323 | 高級職稱(高級職稱) | 32
3231 | 正高級會計師 | 323
3232 | 副高級會計師 | 323
(8 rows)
根據以上查詢結果,這裏敲黑板,劃重點:
=>「RECURSIVE」 是PostgreSQL的關鍵字不是具體存在的表
=>第一行中的:"(id,name,parent_id)"定義的是虛擬el表的參數,字段的名稱可隨意,但字段的個數必定要與3~5行中的查詢結果的個數一致!
=>"el"是聲明的虛擬表,每次遞歸一層後都會將本層數據寫入el中
=>第三行中的id=3是須要查詢開始層的ID,關鍵是第五行=>須要將虛擬表「el"表與「elevel」實體表連表查詢
=>特別須要注意的是第三行中的中的where條件(e3.id=e2.parent_id) ,取虛擬表的ID和實體表parent_id連
這個條件決定了當前遞歸查詢的查詢方式(向上查詢仍是向下查詢);
=>第三行的遞歸開始查詢不可缺乏,否則查詢報錯,我的理解這是PostgreSQL根據首行的記錄來遞歸子記錄
好了,須要總結的大概就是這些,至於第七行中的rpad函數是向右補齊的函數,用於排序的須要,讀者能夠略去order by以後的內容。
好了,一個簡單的遞歸查詢就成了,嗯。。。,如需求同窗說:我須要將每條記錄的遞歸結構(path)和層級(depath)的順序都顯示出來。
遺憾的是PG遞歸查詢自己並無提供相應的函數和關鍵字來方便咱們的需求,怎麼辦=>加字段:
1 with RECURSIVE le (id,name,parent_id,path,depath) as
2 ( 3 select id,name,parent_id,Array[id] as path,1 as depath from elevel where id=3
4 union all
5 select e2.id,e2.name,e2.parent_id,e3.path||e3.id,e3.depath+1
6 from elevel e2,le e3 where e3.id=e2.parent_id 7 ) 8 select * from le order by rpad(id::varchar,5,'0') asc;
查詢結果:
id | name | parent_id | path | depath
------+----------------------+-----------+--------------+--------
3 | 會計 | | {3} | 1
31 | 會計從業證 | 3 | {3,3} | 2
32 | 會計職稱 | 3 | {3,3} | 2
321 | 初級職稱(助理會計師) | 32 | {3,3,32} | 3
322 | 中級職稱(會計師) | 32 | {3,3,32} | 3
323 | 高級職稱(高級職稱) | 32 | {3,3,32} | 3
3231 | 正高級會計師 | 323 | {3,3,32,323} | 4
3232 | 副高級會計師 | 323 | {3,3,32,323} | 4
(8 rows)
嗯~,能夠看到查詢SQL與之上的查詢不一樣的是第三行中定義了一個"Array[id]" 的遞歸結構字段,最爲和一個「1」 的深度字段,Array函數是PostgreSQL特有的數組函數,讀者能夠自行查閱資料瞭解哈( ^)o(^ )~。
固然以上查詢語句知足既已有的需求,想下->若是這裏變我最成最初我作過的那個需求(查詢部門下的全部人,不含部門記錄),該怎麼辦呢。
額~,遞歸自己提供給咱們的結果已經趨於完美了,因爲官方api並無提供進一步的方法,這裏只有從查詢結果着手解決這個問題囖~
with RECURSIVE le (id,name,parent_id,path,depath) as ( select id,name,parent_id,Array[id] as path,1 as depath from elevel where id=3
union all
select e2.id,e2.name,e2.parent_id,e3.path||e3.id,e3.depath+1
from elevel e2,le e3 where e3.id=e2.parent_id ) select * from le l where 0=(select count(1) from le where parent_id=l.id) order by rpad(id::varchar,5,'0') asc;
查詢結果:
id | name | parent_id | path | depath
------+----------------------+-----------+--------------+--------
31 | 會計從業證 | 3 | {3,3} | 2
321 | 初級職稱(助理會計師) | 32 | {3,3,32} | 3
322 | 中級職稱(會計師) | 32 | {3,3,32} | 3
3231 | 正高級會計師 | 323 | {3,3,32,323} | 4
3232 | 副高級會計師 | 323 | {3,3,32,323} | 4
(5 rows)
根據以上查詢SQL來看,答案其實很簡單,在遞歸完成後將存在子記錄的用where條件過濾掉便可(見查詢語句最後一行)
嗯,以上幾個例子所有是向下遞歸查詢,下面我展現下向上查詢的語句,很簡單=>
1 with RECURSIVE le (id,name,parent_id) as
2 ( 3 select id,name,parent_id from elevel where id=323
4 union all
5 select e2.id,e2.name,e2.parent_id from elevel e2,le e3 where e3.parent_id=e2.id 6 ) 7 select * from le order by rpad(id::varchar,5,'0') asc;
查詢結果:
id | name | parent_id
-----+--------------------+-----------
3 | 會計 |
32 | 會計職稱 | 3
323 | 高級職稱(高級職稱) | 32
能夠看到與向上查詢的查詢語句相差不幾,關鍵,關鍵是=>第5行的where條件,很意外吧,如此小的改動就有查詢方向上的變化,我的對此的理解是:
=>遞歸向下查詢是用虛擬表的id去聯結遞歸表的parent_id
=>遞歸向上查詢是用虛擬表的parent_id去聯結遞歸表的id
本人愚鈍,目前對於二者的區別發現僅限於此,歡迎讀者點撥哈。。。
~
最後,須要說明的是,在公司業務知足的狀況下儘量用單層查詢語句查詢,尤爲對於層級較少較固定的結構下較爲合適,此建議主要針對的是遞歸的兩大問題而言:
1>遞歸的查詢效率較低,尤爲是記錄較多層級龐大的記錄
2>若現有記錄的層級若有交叉,極容易致使遞歸死循環,這點尤爲要注意
OK, 本節完成,下節開始講:「窗口函數」
如今是:2018-01-21 21:20:50,願各位晚安,明天要上班哦~