Oracle樹結構表設計及複雜常見開發查詢
- 建立樹結構表
--部門目錄結構表 CREATE TABLE BAI_ORG ( BAI_ORG_ID NUMBER PRIMARY KEY , --部門ID BAI_ORG_NAME VARCHAR2(225), --部門名字 BAI_ORG_P_ID NUMBER , BAI_ORG_LEAD VARCHAR2(225), --部門領導 BAI_ORG_ROOT_ID NUMBER --部門頂級父部門ID ); --頂級根部門 Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(2 , '頂級根部門2', Null, Null , Null); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(3 , '頂級根部門3', Null, Null , Null Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(4 , '頂級根部門4', Null, Null , Null); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(5 , '頂級根部門5', Null, Null , Null); --一級部門 Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(6 , '一級部門6', 1, '張三', 1); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(7 , '一級部門7', 1, '李四', 1); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(8 , '一級部門8', 1, '王五', 1); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(9 , '一級部門9', 2, '劉六', 2); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(10, '一級部門10', 2 , '田七', 2); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(11, '一級部門11', 2 , '陳八', 2); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(12, '一級部門12', 3 , '陳八', 3); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(13, '一級部門13', 3 , '田七', 3); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(14, '一級部門14', 3 , '劉六', 3); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(15, '一級部門15', 4 , '王五', 4); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(16, '一級部門16', 4, '李四', 4); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(17, '一級部門17', 4 , '張三', 4); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(18, '一級部門18', 5 , '陳八', 5); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(19, '一級部門19', 5 , '劉六', 5); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(20, '一級部門20', 5 , '田七', 5); --二級選單 Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(21, '二級選單21', 6 , '張三', 1); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(22, '二級選單22', 7 , '李四', 1); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(23, '二級選單23', 8 , '王五', 1); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(24, '二級選單24', 9 , '張三', 2); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(25, '二級選單25', 10, '田七', 2); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(26, '二級選單26', 11, '劉六', 2); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(27, '二級選單27', 12, '王八', 3); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(28, '二級選單28', 13, '陳八', 3); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(29, '二級選單29', 14, '田七', 3); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(30, '二級選單30', 15, '張三', 4); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(31, '二級選單31', 16, '李四', 4); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(32, '二級選單32', 17, '李四', 4); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(33, '二級選單33', 18, '王五', 5); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(34, '二級選單34', 19, '王五', 5); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(35, '二級選單35', 20, '田七', 5); --三級選單 Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(36, '二級選單36', 21, '張三', 1); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(37, '二級選單37', 22, '張三', 1); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(38, '三級選單38', 23, '陳八', 1); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(39, '三級選單39', 24, '劉六', 2); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(40, '三級選單40', 25, '陳八', 2); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(41, '三級選單41', 26, '劉六', 2); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(42, '三級選單42', 27, '李四', 3); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(43, '三級選單43', 28, '張三', 3); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(44, '三級選單44', 29, '李四', 3); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(45, '三級選單45', 30, '王五', 4); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(46, '三級選單46', 31, '張三', 4); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(47, '三級選單47', 32, '王五', 4); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(48, '三級選單48', 33, '張三', 5); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(49, '三級選單49', 34, '王五', 5); Insert Into BAI_ORG(BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID) Values(50, '三級選單50', 35, '陳八', 5); |
-
說明:
ORACLE 樹查詢的主要語法: SELECT ... START WITH ... CONNECT BY ... PRIOR |
2.部門結構樹操作
2.1 查詢樹中的所有頂級父節點。
SELECT * FROM BAI_ORG WHERE BAI_ORG_P_ID IS NULL OR BAI_ORG_P_ID = 0; |
2.2 查詢一個節點的直屬子節點(所有兒子)
SELECT BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_ROOT_ID FROM BAI_ORG WHERE BAI_ORG_P_ID = 1 |
2.3 查詢一個節點的所有直屬子節點(所有後代)
SELECT BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_ROOT_ID FROM BAI_ORG START WITH BAI_ORG_ID = 1 CONNECT BY BAI_ORG_P_ID = PRIOR BAI_ORG_ID; |
2.4 查詢一個節點的直屬父節點(父親)。
SELECT B.BAI_ORG_ID, B.BAI_ORG_NAME, B.BAI_ORG_P_ID, B.BAI_ORG_LEAD, B.BAI_ORG_ROOT_ID FROM BAI_ORG A JOIN BAI_ORG B ON A.BAI_ORG_P_ID = B.BAI_ORG_ID WHERE A.BAI_ORG_ID = 18 OR B.BAI_ORG_ID = 18 ORDER BY B.BAI_ORG_ID; |
2.5 查詢一個節點的所有直屬父節點(祖宗)
SELECT M.BAI_ORG_ID, M.BAI_ORG_NAME, M.BAI_ORG_P_ID, M.BAI_ORG_LEAD, M.BAI_ORG_ROOT_ID FROM BAI_ORG M START WITH M.BAI_ORG_ID = 38 CONNECT BY PRIOR M.BAI_ORG_P_ID = M.BAI_ORG_ID ORDER BY M.BAI_ORG_ID; |
- 注意:
上面列出兩個樹型查詢方式,第2.3條語句和第2.5條語句,這兩條語句之間的區別在於PRIOR關鍵字的位置不同,所以決定了查詢的方式不同。 當BAI_ORG_P_ID = PRIOR BAI_ORG_ID時,資料庫會根據當前的BAI_ORG_ID迭代出BAI_ORG_P_ID與該BAI_ORG_ID相同的記錄,所以查詢的結果是迭代出了所有的子類記錄;而PRIOR BAI_ORG_ID = BAI_ORG_P_ID時,資料庫會跟據當前的BAI_ORG_P_ID來迭代出與當前的BAI_ORG_P_ID相同的BAI_ORG_ID的記錄,所以查詢出來的結果就是所有的父類結果。
- 補充說明:
對於資料庫來說,根節點並不一定是在資料庫中設計的頂級節點,對於資料庫來說,根節點就是START WITH開始的地方。
2.6 查詢一個節點的兄弟節點(親兄弟)。
SELECT A.BAI_ORG_ID, A.BAI_ORG_NAME, A.BAI_ORG_P_ID, A.BAI_ORG_LEAD, A.BAI_ORG_ROOT_ID FROM BAI_ORG A WHERE EXISTS (SELECT * FROM BAI_ORG B WHERE A.BAI_ORG_P_ID = B.BAI_ORG_P_ID AND B.BAI_ORG_ID = 37); |
2.7 查詢與一個節點同級的節點(族兄弟)。
WITH TMP AS (SELECT A.BAI_ORG_ID, A.BAI_ORG_NAME, A.BAI_ORG_P_ID, A.BAI_ORG_LEAD, A.BAI_ORG_ROOT_ID, LEVEL LEV FROM BAI_ORG A START WITH A.BAI_ORG_P_ID IS NULL CONNECT BY A.BAI_ORG_P_ID = PRIOR A.BAI_ORG_ID) SELECT * FROM TMP WHERE LEV = (SELECT LEV FROM TMP WHERE BAI_ORG_ID = 50); |
- 說明:
這裡使用兩個技巧,一個是使用了LEVEL來標識每個節點在表中的級別,還有就是使用with語法模擬出了一張帶有級別的臨時表。
2.8 查詢一個節點的父節點的的兄弟節點(伯父與叔父)。
WITH TMP AS (SELECT B.BAI_ORG_ID, B.BAI_ORG_NAME, B.BAI_ORG_P_ID, B.BAI_ORG_LEAD, B.BAI_ORG_ROOT_ID, LEVEL LEV FROM BAI_ORG B START WITH BAI_ORG_P_ID IS NULL CONNECT BY BAI_ORG_P_ID = PRIOR BAI_ORG_ID ORDER BY BAI_ORG_ID) SELECT B.* FROM TMP B, (SELECT * FROM TMP WHERE BAI_ORG_ID = 21 AND LEV = 2) A WHERE B.LEV = 1 UNION ALL SELECT * FROM TMP WHERE BAI_ORG_P_ID = (SELECT DISTINCT GP.BAI_ORG_ID FROM TMP GP, --祖父(Grandparent) TMP P, --父親(Parent) (SELECT * FROM TMP WHERE BAI_ORG_ID = 21 AND LEV > 2) S --兒子(Son) WHERE P.BAI_ORG_ID = S.BAI_ORG_P_ID AND GP.BAI_ORG_ID = P.BAI_ORG_P_ID); |
2.8.1這裡查詢分成以下幾步。
首先,和第2.7的一樣,將全表都使用臨時表加上級別;其次,根據級別來判斷有幾種型別,以上文中舉的例子來說,有三種情況:
- 當前節點為頂級節點,即查詢出來的lev值為1,那麼它沒有上級節點,不予考慮。
-
當前節點為2級節點,查詢出來的lev值為2,那麼就只要保證lev級別為1的就是其上級節點的兄弟節點。
-
其它情況就是3以及以上級別,那麼就要選查詢出來其上級的上級節點(祖父),再來判斷祖父的下級節點都是屬於該節點的上級節點的兄弟節點。最後,就是使用union將查詢出來的結果進行結合起來,形成結果集。
2.9 查詢一個節點的父節點的同級節點(族叔)。
WITH TMP AS (SELECT B.BAI_ORG_ID, B.BAI_ORG_NAME, B.BAI_ORG_P_ID, B.BAI_ORG_LEAD, B.BAI_ORG_ROOT_ID, LEVEL LEV FROM BAI_ORG B START WITH B.BAI_ORG_P_ID IS NULL CONNECT BY B.BAI_ORG_P_ID = PRIOR B.BAI_ORG_ID) SELECT * FROM TMP WHERE LEV = (SELECT LEV FROM TMP WHERE BAI_ORG_ID = 6) - 1; |
2.10 名稱要列出名稱全部路徑。
--從頂部開始: SELECT SYS_CONNECT_BY_PATH (BAI_ORG_NAME, '/') FROM BAI_ORG WHERE BAI_ORG_ID = 50 START WITH BAI_ORG_P_ID IS NULL CONNECT BY BAI_ORG_P_ID = PRIOR BAI_ORG_ID;
--從當前節點開始:
SELECT SYS_CONNECT_BY_PATH (BAI_ORG_NAME, '/') FROM BAI_ORG START WITH BAI_ORG_ID = 50 CONNECT BY PRIOR BAI_ORG_P_ID = BAI_ORG_ID; |
- 在上面的例子中,第一個SQL是從根節點開始遍歷,而第二個SQL是直接找到當前節點,從效率上來說已經是千差萬別,更關鍵的是第一個SQL只能選擇一個節點,而第二個SQL卻是遍歷出了一顆樹來。
- SYS_CONNECT_BY_PATH函式就是從START WITH開始的地方開始遍歷,並記下其遍歷到的節點,START WITH開始的地方被視為根節點,將遍歷到的路徑根據函式中的分隔符,組成一個新的字串。
2.11 列出當前節點的根節點。
-
說明:根節點就是START WITH開始的地方,CONNECT_BY_ROOT函式用來列的前面,記錄的是當前節點的根節點的內容
SELECT CONNECT_BY_ROOT BAI_ORG_NAME, BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID FROM BAI_ORG START WITH BAI_ORG_ID = 50 CONNECT BY PRIOR BAI_ORG_P_ID = BAI_ORG_ID; |
2.12 列出當前節點是否為葉子。
SELECT CONNECT_BY_ISLEAF, BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID FROM BAI_ORG START WITH BAI_ORG_P_ID IS NULL CONNECT BY BAI_ORG_P_ID = PRIOR BAI_ORG_ID; |
-
CONNECT_BY_ISLEAF函式用來判斷當前節點是否包含下級節點,如果包含的話,說明不是葉子節點,這裡返回0;反之,如果不包含下級節點,這裡返回1。
2.13 假設存在公司級管理員和非公司級管理員的角色,那麼進行以下查詢(備註:非公司級管理員,以當前管理員過濾查詢,公司級管理員不過濾查詢,BAI_ORG_ROOT_ID記錄樹結構的資料的頂級節點ID)
2.13.1“張三”非公司級管理員進行查詢頂級部門(BAI_ORG_ROOT_ID=4)下他的管理部門
SELECT BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID FROM BAI_ORG WHERE BAI_ORG_ID IN (SELECT BAI_ORG_ID FROM (SELECT B.BAI_ORG_ID, B.BAI_ORG_P_ID FROM BAI_ORG B WHERE B.BAI_ORG_ROOT_ID = 4) A START WITH A.BAI_ORG_ID IN (SELECT BAI_ORG_ID FROM BAI_ORG WHERE BAI_ORG_ROOT_ID = 4 AND BAI_ORG_LEAD = '張三') CONNECT BY PRIOR A.BAI_ORG_P_ID = A.BAI_ORG_ID) ORDER BY BAI_ORG_ID |
2.13.2 “張三”為公司級管理員進行查詢頂級部門(BAI_ORG_ROOT_ID=4)下的管理部門
SELECT BAI_ORG_ID, BAI_ORG_NAME, BAI_ORG_P_ID, BAI_ORG_LEAD, BAI_ORG_ROOT_ID FROM BAI_ORG WHERE BAI_ORG_ID IN (SELECT BAI_ORG_ID FROM (SELECT B.BAI_ORG_ID, B.BAI_ORG_P_ID FROM BAI_ORG B WHERE B.BAI_ORG_ROOT_ID = 4) A START WITH A.BAI_ORG_ID IN (SELECT BAI_ORG_ID FROM BAI_ORG WHERE BAI_ORG_ROOT_ID = 4 --AND BAI_ORG_LEAD = '張三' ) CONNECT BY PRIOR A.BAI_ORG_P_ID = A.BAI_ORG_ID) ORDER BY BAI_ORG_ID |
- 注意:
以上查詢是我公司存在的查詢,BAI_ORG_ROOT_ID是表結構設計的一個技巧,及優化資料,又方便了查詢;BAI_ORG_ROOT_ID 是記錄樹結構表的頂級節點唯一識別ID