地址:https://blog.csdn.net/hellowordapi/article/details/75763432
在平常的業務系統開發中,我們經常需要設計數據層次關系,如在經典的user-role-permission權限設計中, 需要對權限表的數據設計成一種層次依賴關系,如最頂層的為系統管理,系統管理的下一層為角色 管理,角色管理的下一層又為角色的CRUD操作, 那么這種表就可以抽象成為數據結構里面的B樹. 如下表 :
CREATE TABLE "U_PERMISSION"
( "ID" NUMBER(20,0),
"URL" VARCHAR2(256 BYTE),
"NAME" VARCHAR2(50 BYTE),
"PARENT" NUMBER(20,0)
)
在上表中 id表示當前樹的節點。url, name表示可訪問的url路徑,name表示url描述。 parent表示當前節點的父節點,如果當前節點是跟節點則parent用0表示(別用NULL違反了數據庫約束)。 那么上面的表就可以抽象成如下圖.
接着我們插入測試數據 :
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (1,'*','系統管理',0);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (2,'*','權限管理',1);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (20,'/role/allocation','角色分配',23);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (4,'/permission/index','權限列表',2);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (6,'/permission/addPermission.shtml','權限添加',2);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (7,'/permission/deletePermissionById','權限刪除',2);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (8,'/member/list.shtml','用戶列表',22);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (9,'/member/online.shtml','在線用戶',22);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (10,'/member/changeSessionStatus','用戶Session踢出',22);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (11,'/member/forbidUserById','用戶激活or禁止',22);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (12,'/member/deleteUserById','用戶刪除',22);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (13,'/permission/addPermission2Role','權限分配',2);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (14,'/role/clearRoleByUserIds','用戶角色分配清空',23);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (15,'/role/addRole2User','角色分配保存',23);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (16,'/role/deleteRoleById.shtml','角色列表刪除',23);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (17,'/role/addRole','角色列表添加',23);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (18,'role/index','角色列表',23);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (19,'/permission/allocation','權限分配2',2);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (22,'*','用戶管理',1);
Insert into U_PERMISSION (ID,URL,NAME,PARENT) values (23,'*','角色管理',1);
既然都已經創建樹形表了那么肯定要對樹進行操作。就像我們在大學的數據結構一書中所做的那樣對樹進行遞歸遍歷(前序,中序,后序. 忘記了的面壁....) 在oralce中通過
start with....connect by...prior語法,依托該語法我們就可以對上面樹形表進行遞歸遍歷。
示例:
給出一個節點的值,求出的他父節點和祖宗節點:
start with子句: 遞歸的條件,需要注意的是如果with后面的值是子節點那么求出的就是他的父節點和祖宗節點,如果是父節點那么求出的就是他的子節點和子孫節點,
如果不懂可以把上面start with 后面的條件改成 p.parent=0,就可以理解了。
connect by子句:連接條件。 關鍵詞prior,prior跟它右邊的父節點放在一起(prior p.parent)表示往父節點方向遍歷, 反之,如果 prior跟子節點放在一起(prior p.id)表示往
葉子方向遍歷。 這里需要注意的 =p.id 放在prior關鍵詞的前面或者后面都沒什么關系,也就是上面可以這樣寫 p.id= prior p.paren。重要的是prior旁邊放的
是什么。
level偽列: 遞歸的層次表示, 用來進行輸出縮進。可以看到遞歸層次,看起來很直觀。 需要注意的是Level 也可以放在Group by后面,也可以放在select 后面.
下面我來講一下遞歸語法的一些用法和一些需要注意的地方:
有趣的是我們可以不使用start with 子句,它不像程序語言一樣不寫就會造成死循環(插一句嘴,曾經有一人和我聊天提到SQL不是編程語言,因為他不滿足圖靈完全。因為他並不能造成死循環,其實通過遞歸語句就可以,這個坑我會在后面演示,在本文中並不討論SQL是不是編程語言)。它只會把會整個表的數據遍歷一遍,每一個數據做一次
根節點,然后遍歷樹中的其他節點。
他等價於如下SQL:
select p.ID,p.NAME,p.PARENT, level from u_permission p start with p.PARENT in( '祖宗節點','父節點','子節點','孫子節點' ) connect by p.id= prior p.parent;
2. start with 和connect by prior的位置可以互換:
select p.ID,p.NAME,p.PARENT, level from u_permission p start with p.PARENT=1 connect by p.id= prior p.parent;
select p.ID,p.NAME,p.PARENT, level from u_permission p connect by p.id= prior p.parent start with p.PARENT=1;
上面二條的SQL意思是一樣的。
3.我們可以把start with看成是一個where,所以這也意味着我們可以在我們可以在除了最后部分以外的地方加where條件, 如:
select p.ID,p.NAME,p.PARENT, level from u_permission p where p.id=xxx start with p.PARENT=1 connect by p.id= prior p.parent;
但是這里隱藏着一個坑, where條件的作用域不是在遞歸查詢中的,而是在遞歸查詢完后的。 所以如果理解不當他可以造成死循環(雖然會檢測出來報ORA錯誤)也就是a的父節點是b, b的父節點是a。
看一個實用的列子:
如果有一個需求如根據user表的id查出他的權限樹,這個時候我們就需要start with后面加子查詢。
我們可以看到根據子查詢返回的根節點查出所有子節點的父親節點和祖宗節點。
下面是一些跟遞歸語法相關的函數(來自Oracle SQL高級編程):
SYS_CINNECT_BY_PATH函數:
這個函數是用來返回組成層級的直到當前行的值, 我一圖勝千言把。
簡單來說就是用來做拼接的,至於想拼接什么看各位看官的心情咯。
CONNECT_BY_ISLEAF偽列:
這個偽列呢,說直白一點跟level的作用差不多,只不過他是用來在遞歸查詢中顯示葉子節點而非遞歸深度。 留給看官實驗,不上傳實驗圖片了。
CONNECT_BY_ISCYCLE偽列 和NOCYCLE:
這個函數用於檢測樹中是不是出現了環,傳統的數據結構樹是不會出現環的,只有在圖中才會出現環。 而在數據庫中我說過,他不是正兒八經
的B樹,他可能會出現一些不合人倫的事情,比如就像上面所說的死循環, a是b的父親,b又叫a兒子這樣尷尬的事情,Oracle會直接報出ORA-1436的
錯誤,告訴你這樣不太好。 所以我們可以用nocycle函數解決這個問題,也就是讓b當兒子。使得SQL符合邏輯。
select connect_by_isleaf ,p.ID,p.NAME,p.PARENT, CONNECT_BY_ISCYCLE , level from u_permission p start with p.PARENT=1 connect by nocycle p.id= prior p.parent;
connect_by_isleaf 函數:
在遞歸中檢測當前節點是否包含下級節點,也就是說是不是葉子節點,是返回0, 不是返回1, 在動態的目錄中有用。
select connect_by_isleaf ,p.ID,p.NAME,p.PARENT, level from u_permission p start with p.PARENT is null connect by p.id= prior p.parent;