Oracle使用connect by进行级联查询 树型菜单(转)

connect by可以用于级联查询,常用于对具有树状结构的记录查询某一节点的所有子孙节点或所有祖辈节点。

来看一个示例,现假设我们拥有一个菜单表t_menu,其中只有三个字段:id、name和parent_id。它们是具有父子关系的,最顶级的菜单对应的parent_id为0。现假设我们拥有如下记录:

id

name

parent_id

1

菜单01

0

2

菜单02

0

3

菜单03

0

4

菜单0101

1

5

菜单0102

1

6

菜单0103

1

7

菜单010101

4

8

菜单010201

5

9

菜单010301

6

10

菜单0201

2

11

菜单0202

2

12

菜单020101

10

13

菜单020102

10

14

菜单020103

10

15

菜单0301

3

16

菜单0302

3

17

菜单030201

16

18

菜单030202

16

19

菜单030203

16

如果这个时候我们需要查询“菜单01”以及其下所有的子孙菜单应该怎么办呢?如果使用connect by的话这将会非常简单,使用如下SQL语句就可以达到对应的效果。

  1. select * from t_menu connect by parent_id=prior id start with id=1;

connect by是需要跟start with一起使用的。connect by后跟的是连接条件,在connect by后接的条件通常都需要使用关键字“prior”,可以简单的把它理解为上一级,所以上述例子中“connect by parent_id=prior id”就表示连接条件为parent_id等于上级的id,查找到下一级记录后又会找parent_id等于下一级记录的id的记录,而prior对应的最顶层的记录就是通过start with来确定的,start with后接对应的筛选条件,表示最顶层的记录是哪些,最顶层的记录可以有多个,比如我想查找“菜单01”下的子孙菜单,但是不包括“菜单01”本身,那么我就可以使用如下的SQL语句进行查找,此时“start with parent_id=1”对应的记录就会有多条。

  1. select * from t_menu connect by parent_id=prior id start with parent_id=1;

对应的结果为:

id

name

parent_id

4

菜单0101

1

5

菜单0102

1

6

菜单0103

1

7

菜单010101

4

8

菜单010201

5

9

菜单010301

6

此外,如果我们想查找“菜单010101”对应的祖辈菜单也非常简单,如下SQL就可以实现该功能,即从“菜单010101”的父菜单(对应id为4)开始查找。

  1. select * from t_menu connect by id=prior parent_id start with id=4;

对应的结果为:

id

name

parent_id

1

菜单01

0

4

菜单0101

1

level

使用connect by时我们可以使用内置的类似于rownum的一个叫level的伪列,该列表示当前记录相对于start with记录的一个层级,start with记录的level为1。如上面的两条SQL语句,如果加上level的话对应的结果将是这样的。

  1. select level,t.* from t_menu t connect by parent_id=prior id start with parent_id=1;

对应的结果为:

level

id

name

parent_id

1

4

菜单0101

1

1

5

菜单0102

1

1

6

菜单0103

1

2

7

菜单010101

4

2

8

菜单010201

5

2

9

菜单010301

6

  1. select level,t.* from t_menu t connect by id=prior parent_id start with id=4;

对应的结果为:

level

id

name

parent_id

2

1

菜单01

0

1

4

菜单0101

1

有了level后,我们就可以对查询的level做一个限制,比如只查从最顶层开始向下两级的菜单。

  1. select level,t.* from t_menu t where level<3 connect by prior id= parent_id start with parent_id=0;

从上述SQL我们可以看到where条件是直接跟在from之后的,使用connect by时我们的where条件不是在connect by之前对数据进行过滤的,而是在connect by之后才对所有的数据进行过滤的,这一点跟使用分组语句group by时是不一样的,group by是先通过where对需要分组的数据进行过滤后再通过group by来分组的。

nocycle和connect_by_iscycle

如果我们的记录中存在循环的父子关系,则使用connect by进行查询时会抛出异常,如A->B、B->C、C->A这样的记录。解决办法是在connect by语句后加上“nocycle”,表示不循环查询,如:

  1. select * from t_menu connect by nocycle prior id=parent_id start with parent_id=0;

使用nocycle后对于A->B、B->C、C->A这样的记录会通过查询B,然后通过B查询C,再通过C查询A时发现已经循环了,就不再查询了,即在C这条记录这里循环了。在对存在循环记录的查询中我们也可以通过“connect_by_iscycle”找到是哪一条记录循环了,“connect_by_iscycle”也是一个伪列,其必须和nocycle一起使用。伪列“connect_by_iscycle”对应的值有0和1,如果某一条记录的connect_by_iscycle对应的值为1则表示从该条记录这里开始循环了。如下是一个使用connect_by_iscycle的示例。

  1. select connect_by_iscycle,t.* from t_menu t connect by nocycle prior id=parent_id start with parent_id=0;

connect_by_isleaf

connect_by_isleaf也是一个伪列,其表示对应的记录是否是一个叶子节点,即在进行connect by时不能通过该记录找到下一条记录。其对应的值有0和1,0表示非叶子节点,1表示是叶子节点。如我只想找出是叶子节点的菜单时对应的SQL可以这样写:

  1. select connect_by_isleaf,t.* from t_menu t where connect_by_isleaf=1 connect by prior id=parent_id start with parent_id=0;

connect_by_root

connect_by_root表示根节点,即某一条记录所对应的最顶级的记录,其用法跟prior类似,后面也需要跟一个字段名。如下面示例可以查询所有叶子节点菜单的最顶级菜单和上级菜单的名称。

  1. select connect_by_root name as root_name, prior name as prior_name,t.* from t_menu t where connect_by_isleaf=1 connect by prior id=parent_id start with parent_id=0;

对应上表的记录,在上述SQL中查询出来的结果应该如下所示:

root_name

prior_name

id

name

parent_id

菜单01

菜单0101

7

菜单010101

4

菜单01

菜单0102

8

菜单010201

5

菜单01

菜单0103

9

菜单010301

6

菜单02

菜单02

11

菜单0202

2

菜单02

菜单0201

12

菜单020101

10

菜单02

菜单0201

13

菜单020102

10

菜单02

菜单0201

14

菜单020103

10

菜单03

菜单03

15

菜单0301

3

菜单03

菜单0302

17

菜单030201

16

菜单03

菜单0302

18

菜单030202

16

菜单03

菜单0302

19

菜单030203

16

sys_connect_by_path

sys_connect_by_path(column,delimiter)可以用来展示以指定column和分隔符delimiter表示从根节点到当前节点的路径。以下SQL用来查询id为2的菜单下叶子节点的信息,包括以字段name和分隔符“>”表示的其对应的根节点的路径。

  1. select sys_connect_by_path(name, '>') as connect_path,t.* from t_menu t where connect_by_isleaf=1 connect by prior id=parent_id start with id=2;

对应结果如下所示:

connect_path

id

name

parent_id

>菜单02>菜单0202

11

菜单0202

2

>菜单02>菜单0202>菜单020101

12

菜单020101

10

>菜单02>菜单0202>菜单020102

13

菜单020102

10

>菜单02>菜单0202>菜单020103

14

菜单020103

10

排序order

可以使用order by对connect by之后的结果进行排序,此时order by需放在最末端,而不像where筛选那样直接定义在from之后。如需对connect by之后的结果按id进行排序,则可以使用如下SQL语句:

  1. select t.* from t_menu t connect by parent_id=prior id start with parent_id=0 order by id;

除了传统的针对查询结果的排序外,connect by语句还支持对同一父节点下的子节点进行排序,这是通过order siblings by来定义的。如我们需要查询id为2的菜单下的所有子孙菜单,然后对具有同一父节点的菜单按id进行倒序排列,则我们的SQL语句可以如下定义:

  1. select t.* from t_menu t connect by parent_id=prior id start with id=2 order siblings by id desc;

对应的结果会是这样子:

id

name

parent_id

2

菜单02

0

11

菜单0202

2

10

菜单0201

2

14

菜单020103

10

13

菜单020102

10

12

菜单020101

10

如上表所示,我们可以看到“菜单0201”和“菜单0202”具有相同的父节点“菜单02”,它们按照id进行倒序排列,所有“菜单0202”在“菜单0201”之前,同样“菜单020101”、“菜单020102”和“菜单020103”具有相同的父节点“菜单0201”,所以它们也是按照id的倒序排列。

一次针对connect by的查询优化

有这么一个需求:表A表示分类,表B表示任务模板,A与B是一对多的关系,每一个任务模板都属于一个特定的分类,在表B中用字段a表示所属的分类。分类存在父子关系,子分类的parent_id对应父分类的id。现假设需要统计id为1的分类及其子分类下存在的任务模板数量。对应SQL如下:

  1. select count(1) from B b,(select id from A connect by prior id=parent_id start with id=1) a where a.id=b.a;

现假设拥有另外一个表C,其表示任务实例,一个任务模板B可以拥有n个任务实例B,即B跟C之间是一对多的关系。任务实例C通过字段b关联任务模板B,另外任务实例C拥有一个字段status表示任务实例的具体状态。现假设需要统计id为1的分类及其子分类下各状态的任务实例数量。对应SQL如下:

  1. select c.status,count(1) from B b,(select id from A connect by prior id=parent_id start with id=1) a, C c where a.id=b.a and b.id=c.b group by c.status;

在A表数据量1000,B表数据量20000,C表数据量5000,id为1的分类下属的子孙分类数量为100的情况下第一条SQL的查询速度可以在0.1秒左右完成,而第二条SQL需要将近10秒才能完成。把查询id为1的分类下子孙分类的id的SQL语句“selectidfrom A connectbypriorid=parent_id startwithid=1”单独查询的速度也可以在0.1秒内完成。通常对于这种数量级别的三表查询都是可以在0.1秒内完成的,为此心想第二条SQL应该是受了子查询中connect by的影响。后来决定把分类的子查询直接作为B的in条件进行查询,如下所示:

  1. select c.status,count(1) from B b, C c where b.a in(select id from A connect by prior id=parent_id start with id=1) and b.id=c.b group by c.status;

其查询效果是一样的,心想应该还是connect by影响到了,既然单独使用connect by查询id为1的分类的子孙分类的id只需要不到0.1秒,那何不在程序里面先将id为1的分类的子孙分类id查询出来,再作为B、C联合查询的in条件,如:

  1. select c.status,count(1) from B b, C c where b.a in(...) and b.id=c.b group by c.status;

结果查询结果也可以在0.1秒内完成。

oracle使用connect by进行级联查询 树型菜单的更多相关文章

  1. JS树型菜单

    本树型菜单主要实现功能有:基本的树型菜单,可勾选进行多选项操作. 本树型菜单适合最初级的学者学习,涉及内容不难,下面看代码. 首先看View的代码,第一个<div>用来定义树显示的位置和i ...

  2. 下拉的DIV+CSS+JS二级树型菜单

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. 树概念及使用connect by进行级联查询

    树 树,大家都见过,以这种形式的数据关系,就是树.下面看一张图,了解什么是根节点(树干).节点或分叉.叶(叶节点) connect by 级联查询 connect by可以用于级联查询,常用于对具有树 ...

  4. php实现无限级树型菜单(函数递归算法)

    首先到数据库取数据,放到一个数组,然后把数据转化为一个树型状的数组,最后把这个树型状的数组转为html代码.也可以将第二步和第三步合为一步. 详细如下:1.数据库设计:脚本如下:CREATE TABL ...

  5. php通用的树型类创建无限级树型菜单

    生成树型结构所需要的2维数组,var $arr = array()数组格式如下: array( 1 => array('id'=>'1','parentID'=>0,'name'=& ...

  6. JavaScript:使用递归构建树型菜单

    使用递归函数将扁平数据转为树型结构,并渲染到页面 效果图: 代码: <!DOCTYPE html> <html lang="en"> <head> ...

  7. SQL Server 通过“with as”方法查询树型结构

    一.with as 公用表表达式 类似VIEW,但是不并没有创建对象,WITH  AS 公用表表达式不创建对象,只能被后随的SELECT语句,其作用: 1. 实现递归查询(树形结构) 2. 可以在一个 ...

  8. jQuery 树型菜单插件(Treeview)

    jQuery Treeview 提供了一个无序灵活的可折叠的树形菜单.试用于一些菜单的导航,支持基于 cookie 的持久性菜单

  9. Rafy 领域实体框架 - 树型实体功能(自关联表)

      在 Rafy 领域实体框架中,对自关联的实体结构做了特殊的处理,下面对这一功能进行讲解. 场景 在开发数据库应用程序时,往往会遇到自关联表的场景.例如,分类信息.组织架构中的部门.文件夹信息等,都 ...

随机推荐

  1. PHP导出Excel表

    <?php/** * Created by PhpStorm. * User: admin * Date: 2019/3/16 * Time: 9:41 *///利用excel导出插件PHPEx ...

  2. 20175234 《Java程序设计》第二周学习总结(二)

    学习内容总结 运算符与表达式 If语句.switch语句 break和continue语句 数组和for语句 IDEA的安装和调试 教材学习中的问题和解决过程 在第一次使用IDEA中出现了一些情况,在 ...

  3. JQuery复习心得

    this === event.currentTarget    event.stopPropagation  阻止冒泡  http:www.css88.com JQ和原生JS入口函数的区别: 书写个数 ...

  4. cpp 区块链模拟示例(一)工程建立

    /* 作 者: itdef 欢迎转帖 请保持文本完整并注明出处 技术博客 http://www.cnblogs.com/itdef/ 技术交流群 群号码:432336863欢迎c c++ window ...

  5. Linux运维40道精华题

    题目 1.什么是运维?什么是游戏运维? 1)运维是指大型组织已经建立好的网络软硬件的维护,就是要保证业务的上线与运作的正常,在他运转的过程中,对他进行维护,他集合了网络.系统.数据库.开发.安全.监控 ...

  6. 微信js sdk的使用初步理解

    第一步引入js文件 在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js 备注:支持使用 AMD/C ...

  7. .NET 4.0中的泛型逆变和协变

    转载自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html:自己加了一些理解 随Visual Studi ...

  8. Unity3D使用EasyMovieTexture插件播放视频

    Unity3D对于视频的播放兼容个人感觉很差劲,之前写过一篇使用Unity3D自己自带的一些功能去播放视频,链接如下: http://www.cnblogs.com/xiaoyulong/p/8627 ...

  9. cp备份操作时如何忽略指定的目录

    需求场景:进行CP拷贝备份的时候,子目录里面的某些大文件或是一些log文件是无需备份的,那么在CP操作时需要忽略掉指定的目录. 案例演示如下:备份data目录,但是不包括里面的share子目录. 先看 ...

  10. HashMap TreeMap的区别

    Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复就覆盖了),但允许值重复.Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快 ...