B-tree结构菜单的递归查询
CREATE TABLE T_MENU(
ID NUMBER, -- 节点号
-- NAME VARCHAR2(50),
PID NUMBER DEFAULT 0 -- 父节点号。0为根节点,不存在id=0的节点
);
INSERT INTO T_MENU (ID, PID) VALUES ('1', '0');
INSERT INTO T_MENU (ID, PID) VALUES ('2', '1');
INSERT INTO T_MENU (ID, PID) VALUES ('3', '1');
INSERT INTO T_MENU (ID, PID) VALUES ('4', '2');
INSERT INTO T_MENU (ID, PID) VALUES ('5', '2');
INSERT INTO T_MENU (ID, PID) VALUES ('6', '3');
INSERT INTO T_MENU (ID, PID) VALUES ('7', '3');
INSERT INTO T_MENU (ID, PID) VALUES ('8', '5');
INSERT INTO T_MENU (ID, PID) VALUES ('9', '5');
INSERT INTO T_MENU (ID, PID) VALUES ('10', '7');
INSERT INTO T_MENU (ID, PID) VALUES ('11', '7');
INSERT INTO T_MENU (ID, PID) VALUES ('12', '10');
INSERT INTO T_MENU (ID, PID) VALUES ('13', '10');
SELECT * FROM T_MENU T ORDER BY T.ID ASC;
上面初始化的数据,其树形结构如下:
-
给出父节点号,查询它下边的所有子孙节点;
-
给出子节点号,查询它在菜单树中的位置。
这两个查询,可通过SQL的 start with ... connect by prior 递归查询法查询
SELECT T.ID, T.PID, LEVEL
FROM T_MENU T
START WITH T.PID = '7'
CONNECT BY PRIOR T.ID = T.PID
ORDER BY LEVEL ASC;
查询结果如下:
ID | PID | LEVEL |
10 | 7 | 1 |
11 | 7 | 1 |
12 | 10 | 2 |
13 | 10 |
2 |
SELECT T.ID, T.PID, LEVEL
FROM T_MENU T
START WITH T.ID = '7'
CONNECT BY T.ID = PRIOR T.PID
ORDER BY LEVEL DESC;
查询结果如下:
ID | PID | LEVEL |
1 | 0 | 3 |
3 | 1 | 2 |
7 | 3 | 1 |
自关联递归是如何进行的呢?根据START WITH 条件找到起始记录后,在起始记录中取PRIOR字段的值,然后在本表中查询这样的数据行:它的非PRIOR字段值与起始记录的PRIOR字段值 之间的关系,符合CONNECT BY 条件.
以上面例子中的向下遍历为例,具体地,先根据起始条件找到PID = '7'的记录,有两条,分别是ID = 10 和 ID = 11,这是查询的起始记录,也就是第一层.然后连接条件是
PRIOR T.ID = T.PID
那是说要查找PID = 起始记录的ID值(即10和11) 的数据,没有PID = 11的记录,PID = 10的记录有两条,即ID 为12 和13 的记录,这就是查询结果的第二层,以此类推,以第二层记录为起始记录,查找PID=12或13的记录,为第三层,这里没有第三层数据,递归结束.
相关写法
下面把上述例子中的向上遍历SQL,改写为WITH AS 递归写法:
WITH TMP(ID, PID) AS
(SELECT T.ID, T.PID
FROM T_MENU T
WHERE T.ID = '7'
UNION ALL
SELECT B.ID, B.PID
FROM TMP A, T_MENU B
WHERE A.PID = B.ID)
SELECT M.ID, M.PID, ROWNUM FROM TMP M ORDER BY ROWNUM DESC;