最近在作公司的項目中遇到一個問題,多級級聯導航菜單,雖然只有三級目錄,但<li>中嵌套<ul>,數據庫表結構以下:前端
1 CREATE TABLE FLFL 2 ( 3 ID NUMBER NOT NULL, 4 MC NVARCHAR2(20), 5 FLJB NUMBER, 6 SJFLID NUMBER 7 )
很常見的表結構,包括自身ID和父ID,對於這種咱們已不陌生,在寫樹狀菜單的時候常常見到這樣的數據結構,可是咱們通常寫樹狀菜單的時候都會用到前端的框架好比zTree或者treeView等,咱們只要在後臺查出全部的數據,返回List到前臺前臺會根據id和superId自動解析成樹狀結構。但若是不用這些框架的話,就須要咱們本身在後臺組裝成樹結構返回前臺作處理。那麼後臺如何組裝樹結構呢,好比要實現以下功能:數據庫
這個時候就要用到數據庫的樹查詢方法。樹查詢返回的數據結構就是查出根節點下的所有子子孫孫的節點以節點包含節點的方式展示。數據結構
Oracle樹查詢的主要語法:select...start with... connect by ...prior 。框架
咱們以上述FLFL表爲例進行講解.函數
1. 查找樹中的全部頂級父節點(輩份最長的人)。spa
1 SELECT * FROM flfl WHERE sjflid =0; 設計
以上查詢中全部的根節點的上級Id也就是父Id都爲0,這個在插入數據的時候就能夠進行設定。根節點以父Id爲0進行標識。code
2.查找一個節點的直屬子節點(全部兒子)。blog
1 SELECT * FROM flfl WHERE sjflid = 819459; 字符串
這個SQL語句能夠查出父節點爲819454的所有子節點。
3.查找一個節點的全部 直屬子節點(全部後代)。
1 SELECT * FROM flfl START WITH ID = 819459 CONNECT BY sjflid = PRIOR ID;
這個查找的是ID爲819459的節點下的全部直屬子類節點,包括子輩的和孫子輩的全部直屬節點。
4.查找一個節點的直屬父節點(父親)。
1 SELECT b.* FROM flfl a JOIN flfl b ON a.sjflid = b.ID WHERE a.ID = 6758;
這個找到的是ID爲6758的節點的直屬父節點,要用到同一張表的關聯了。
5.查找一個節點的全部直屬父節點(祖宗)。
1 SELECT * FROM flfl START WITH ID = 6758 CONNECT BY PRIOR sjflid = ID;
這裏查找的就是ID爲6758的全部直屬父節點,打個比方就是找到一我的的父親、祖父等。可是值得注意的是這個查詢出來的結果的順序是先列出子類節點再列出父類節點,姑且認爲是個倒序吧。
上面列出兩個樹型查詢方式,第3條語句和第5條語句,這兩條語句之間的區別在於prior關鍵字的位置不一樣,因此決定了查詢的方式不一樣。 當sjflid = PRIOR ID時,數據庫會根據當前的ID迭代出sjflid與該ID相同的記錄,因此查詢的結果是迭代出了全部的子類記錄;而PRIOR ID = sjflid時,數據庫會跟據當前的sjflid來迭代出與當前的sjflid相同的id的記錄,因此查詢出來的結果就是全部的父類結果。
6.查詢一個節點的兄弟節點(親兄弟)。
1 SELECT a.* FROM flfl a WHERE EXISTS (SELECT * FROM flfl b WHERE a.sjflid = b.sjflid AND b.ID = 6757);
這裏查詢的就是與ID爲6757的節點同屬一個父節點的節點了,就比如親兄弟了。
7.查詢與一個節點同級的節點(族兄弟)。
1 WITH tmp AS (SELECT a.*, LEVEL lev FROM flfl a START WITH a.sjflid IS NULL CONNECT BY a.sjflid = PRIOR a.ID) SELECT * FROM tmp WHERE lev = (SELECT lev FROM tmp WHERE ID = 819394)
這裏使用兩個技巧,一個是使用了LEVEL來標識每一個節點在表中的級別,還有就是使用with語法模擬出了一張帶有級別的臨時表。
8.查詢一個節點的父節點的的兄弟節點(伯父與叔父)。
1 WITH tmp AS (SELECT flfl.*, LEVEL lev FROM flfl START WITH sjflid IS NULL CONNECT BY sjflid = PRIOR ID) SELECT b.* FROM tmp b, (SELECT * FROM tmp WHERE ID = 7004 AND lev = 2) a WHERE b.lev = 1 UNION ALL SELECT * FROM tmp WHERE sjflid = (SELECT DISTINCT x.ID FROM tmp x, tmp y, (SELECT * FROM tmp WHERE ID = 7004 AND lev > 2) z WHERE y.ID = z.sjflid AND x.ID = y.sjflid);
這裏查詢分紅如下幾步。首先,將第7個同樣,將全表都使用臨時表加上級別;其次,根據級別來判斷有幾種類型,以上文中舉的例子來講,有三種狀況:(1)當前節點爲頂級節點,即查詢出來的lev值爲1,那麼它沒有上級節點,不予考慮。(2)當前節點爲2級節點,查詢出來的lev值爲2,那麼就只要保證lev級別爲1的就是其上級節點的兄弟節點。(3)其它狀況就是3以及以上級別,那麼就要選查詢出來其上級的上級節點(祖父),再來判斷祖父的下級節點都是屬於該節點的上級節點的兄弟節點。 最後,就是使用UNION將查詢出來的結果進行結合起來,造成結果集。
9.查詢一個節點的父節點的同級節點(族叔)。
1 WITH tmp AS 2 (SELECT a.*, LEVEL lev 3 FROM flfl a 4 START WITH a.sjflid IS NULL 5 CONNECT BY a.sjflid = PRIOR a.ID) 6 SELECT * 7 FROM tmp 8 WHERE lev = (SELECT lev 9 FROM tmp 10 WHERE ID = 819394) - 1
只須要作個級別判斷就成了。
基本上,常見的查詢在裏面了,不常見的也有部分了。其中,查詢的內容都是節點的基本信息,都是數據表中的基本字段,可是在樹查詢中還有些特殊需求,是對查詢數據進行了處理的,常見的包括列出樹路徑等。
補充一個概念,對於數據庫來講,根節點並不必定是在數據庫中設計的頂級節點,對於數據庫來講,根節點就是start with開始的地方。
下面列出的是一些與樹相關的特殊需求。
10.名稱要列出名稱所有路徑。
1 從頂部開始: 2 3 4 SELECT SYS_CONNECT_BY_PATH (mc, '/') 5 FROM flfl 6 WHERE ID = 6498 7 START WITH sjflid IS NULL 8 CONNECT BY sjflid = PRIOR ID; 9 10 從當前節點開始: 11 12 13 SELECT SYS_CONNECT_BY_PATH (mc, '/') 14 FROM flfl 15 START WITH ID = 6498 16 CONNECT BY PRIOR sjflid = ID;
在上面的例子中,第一個SQL是從根節點開始遍歷,而第二個SQL是直接找到當前節點,從效率上來講已是千差萬別,更關鍵的是第一個SQL只能選擇一個節點,而第二個SQL倒是遍歷出了一顆樹來。
sys_connect_by_path函數就是從start with開始的地方開始遍歷,並記下其遍歷到的節點,start with開始的地方被視爲根節點,將遍歷到的路徑根據函數中的分隔符,組成一個新的字符串,這個功能仍是很強大的。
11.列出當前節點的根節點。
1 SELECT CONNECT_BY_ROOT mc, flfl.* 2 FROM flfl 3 START WITH ID = 6498 4 CONNECT BY PRIOR sjflid = ID;
connect_by_root函數用來列的前面,記錄的是當前節點的根節點的內容。
12.列出當前節點是否爲葉子。
1 SELECT CONNECT_BY_ISLEAF, flfl.* 2 FROM flfl 3 START WITH sjflid IS NULL 4 CONNECT BY sjflid = PRIOR ID;
connect_by_isleaf函數用來判斷當前節點是否包含下級節點,若是包含的話,說明不是葉子節點,這裏返回0;反之,若是不包含下級節點,這裏返回1。