用 SQL 查询/构建树型(层次)数据

 

什么是层次/树型是什么概念?

If a table contains hierarchical data, then you can select rows in a hierarchical. (单击查看内容出处) 

注:在Oracle里称为hierarchical queries,而为了方便理解,把它译为层次/树型查询。

作用 & 特征

通常用于查询整个层次/树型关系的数据。如:公司的架构体系里面,表里存储了每一层职位的信息,需要全部查出来;(树的形式) 一些顺序型的记录,像:A群由上海搬到北京,之后又搬到广州,不久又搬到南京。(单链表形式)

 

凡是在表里存在以上形式的数据,都可以根据一个点,它所有关联的数据通过一条SELECT语句一次过查询出来。

示例

网上有太多的层次/树型结构的例子,而且在Oracle官方文档里也有很好的例子(单击查看文档例子)。因此本文从数据结构的角度去说明一下具体的用法,不涉及任何业务知识。

先看一张关系图:

大小: 4.59 K
尺寸: 258 x 200
浏览: 18 次
点击打开新窗口浏览全图

至于上图的树型,我们通常说成:1 是根节点;4的父节点是2,子节点有56;而像356…这些没有子节点的节点称为叶结点。(不同的描述有不同的说法,在文章继续之前先统一一下说法)

用表把上面的图存起来。

 

SQL代码
  1. CREATE TABLE t_tree(fnum NUMBER, fparent NUMBER);    
  2. INSERT INTO t_tree(fnum, fparent) VALUES(1, null);     
  3. INSERT INTO t_tree(fnum, fparent) VALUES(2, 1);    
  4. INSERT INTO t_tree(fnum, fparent) VALUES(3, 2);     
  5. INSERT INTO t_tree(fnum, fparent) VALUES(4, 2);     
  6. INSERT INTO t_tree(fnum, fparent) VALUES(5, 4);    
  7. INSERT INTO t_tree(fnum, fparent) VALUES(6, 4);    
  8. INSERT INTO t_tree(fnum, fparent) VALUES(7, 1);     
  9. INSERT INTO t_tree(fnum, fparent) VALUES(8, 7);    
  10. INSERT INTO t_tree(fnum, fparent) VALUES(9, 1);   
  11. INSERT INTO t_tree(fnum, fparent) VALUES(10, 9);    
  12. INSERT INTO t_tree(fnum, fparent) VALUES(11, 10);    
  13. INSERT INTO t_tree(fnum, fparent) VALUES(12, 9);    
  14. COMMIT;   

根据需求写SQL:

查出 1 下面的所有成员:(顺推)

SQL代码
  1. SELECT fparent  -- 父节点     
  2.        ,fnum    -- 子节点   
  3.        ,LEVEL AS n_level  -- 第几层(逻辑)   
  4.        ,sys_connect_by_path(fnum, '/'AS path -- 整个路径   
  5.        ,connect_by_isLeaf AS "isLeaf" -- 是否为叶节点(1 - 是;0 - 否)   
  6.    FROM t_tree   
  7.   START WITH fparent = 1   
  8.  CONNECT BY PRIOR fnum = fparent  

 

结果
  1. FPARENT       FNUM    N_LEVEL PATH             isLeaf   
  2. ------- ---------- ---------- ------------   ----------   
  3.       1          2          1 /2                 0   
  4.       2          3          2 /2/3               1   
  5.       2          4          2 /2/4               0   
  6.       4          5          3 /2/4/5             1   
  7.       4          6          3 /2/4/6             1   
  8.       1          7          1 /7                 0   
  9.       7          8          2 /7/8               1   
  10.       1          9          1 /9                 0   
  11.       9         10          2 /9/10              0   
  12.      10         11          3 /9/10/11           1   
  13.       9         12          2 /9/12              1   
  14.   
  15. rows selected   

 

查出 8 上面的所有成员:(逆推)

SQL代码
  1. SELECT fparent -- 父节点      
  2.        ,fnum   -- 子节点      
  3.        ,LEVEL AS n_level -- -- 第几层(逻辑)      
  4.        ,sys_connect_by_path(fnum, '/'AS path -- 整个路径      
  5.        ,connect_by_isLeaf AS "isLeaf" -- 是否为叶节点(1 - 是;0 - 否)      
  6.   FROM t_tree      
  7.  START WITH fnum = 8      
  8. CONNECT BY fnum = PRIOR fparent    

 

结果
  1. FPARENT       FNUM    N_LEVEL      PATH           isLeaf      
  2. --------- ---------- ---------- ------------- ----------      
  3.    7          8        1            /8               0      
  4.    1          7        2            /8/7             0      
  5.               1        3            /8/7/1           1      

 

查出所有叶(Leaf)结点,没有子节点的节点:

SQL代码
  1. SELECT *   
  2.   FROM t_tree tr   
  3.  WHERE 0 = (SELECT COUNT(1)   
  4.               FROM t_tree   
  5.              WHERE fparent = tr.fnum)   
  6.  ORDER BY fnum  

 

结果
  1. FNUM    FPARENT   
  2. ---- ----------   
  3.    3          2   
  4.    5          4   
  5.    6          4   
  6.    8          7   
  7.   11         10   
  8.   12          9   

 

其它说明

调优

内部不断根据子、父节点来查询本表。数据量大的话,给表添加子、父节点两个字段的索引,会有很明显的快速效果。

 

根节点

Oracle 提供了可以快速找到根节点的操作符,如下SQL

SQL代码
  1. SELECT fparent  -- 父节点     
  2.        ,fnum    -- 子节点   
  3.        ,LEVEL AS n_level  -- 第几层(逻辑)   
  4.        ,sys_connect_by_path(fnum, '/'AS path -- 整个路径   
  5.        ,connect_by_root fparent AS root_top -- 根节点   
  6.    FROM t_tree   
  7.   START WITH fparent = 9   
  8.  CONNECT BY PRIOR fnum = fparent  

 

结果
  1. FPARENT       FNUM    N_LEVEL PATH               ROOT_TOP   
  2. ------- ---------- ---------- ----------------- ----------   
  3.       9         10          1 /10                  9   
  4.      10         11          2 /10/11               9   
  5.       9         12          1 /12                  9   

 

如果指定一个节点开始查的话,ROOT就是指定的那个节点,直接用 START WITH 后面那个值就行。不过如果没有指定一个节点,表里有多棵树,这个就很有用了。

 

循环关系

在记录里有循环关系,树型有根和子节点,而没有循环的关系。这里简单说明一下,循环是怎回事吧。如添加一条这样的记录:

SQL代码
  1. INSERT INTO t_tree(fnum, fparent) VALUES(7, 8);  

 

1-7-8 这条路径制造一条循环记录。之后从8开始逆推到上一层(会报错):

SQL代码
  1. SQL> SELECT fparent -- 父节点   
  2.   2         ,fnum   -- 子节点   
  3.   3         ,LEVEL AS n_level -- -- 第几层(逻辑)   
  4.   4         ,sys_connect_by_path(fnum, '/'AS path -- 整个路径   
  5.   5         ,connect_by_isLeaf AS "isLeaf" -- 是否为叶节点(1 - 是;0 - 否)   
  6.   6    FROM t_tree   
  7.   7   START WITH fnum = 8   
  8.   8   CONNECT BY fnum = PRIOR fparent   
  9.   9  /   
  10.   
  11. SELECT fparent -- 父节点   
  12.        ,fnum   -- 子节点   
  13.        ,LEVEL AS n_level -- -- 第几层(逻辑)   
  14.        ,sys_connect_by_path(fnum, '/'AS path -- 整个路径   
  15.        ,connect_by_isLeaf AS "isLeaf" -- 是否为叶节点(1 - 是;0 - 否)   
  16.   FROM t_tree   
  17.  START WITH fnum = 8   
  18.  CONNECT BY fnum = PRIOR fparent   
  19.   
  20. ORA-01436: 用户数据中的 CONNECT BY 循环   
  21.   
  22. SQL>   

 

这里需要用到一个循环处理,如下:

SQL代码
  1. SQL> SELECT fparent -- 父节点   
  2.   2         ,fnum   -- 子节点   
  3.   3         ,LEVEL AS n_level -- -- 第几层(逻辑)   
  4.   4         ,sys_connect_by_path(fnum, '/'AS path -- 整个路径   
  5.   5         ,connect_by_isLeaf AS "isLeaf" -- 是否为叶节点(1 - 是;0 - 否)   
  6.   6         ,connect_by_isCycle AS "isCycle" -- 是否为循环节点(1 - 是;0 - 否)   
  7.   7    FROM t_tree   
  8.   8   START WITH fnum = 8   
  9.   9   CONNECT BY NOCYCLE fnum = PRIOR fparent   
  10.  10  /   
  11.   
  12.    FPARENT       FNUM    N_LEVEL PATH                 isLeaf    isCycle   
  13. ---------- ---------- ---------- ------------------ ---------- ----------   
  14.          7          8          1 /8                     0          0   
  15.          1          7          2 /8/7                   0          0   
  16.                     1          3 /8/7/1                 1          0   
  17.          8          7          2 /8/7                   1          1   
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值