Oracle中with as、START with ... CONNECT by PRIOR (CONNECT_BY_ROOT)的用法

Oracle中with as、START with ... CONNECT by PRIOR (CONNECT_BY_ROOT)的用法

一 、with as 

(一)含义

WITH AS查询语句,也叫子查询部分(subquery factoring),定义一个SQL共用片断,该SQL片断会被整个SQL语句所用到。 它不是以select开始,而是以“WITH”关键字开头。在真正进行查询之前预先构造了一个临时表,之后便可多次使用它做进一步的分析和处理。

(二)语法:每个子查询以逗号分隔,要注意:最后一个没有任何符号。

with query1 AS

(select ...from ....where ..),

query2  AS

(select...from ...where..),

query3 AS

(select...from ...where..)

SELECT ...FROM query1,quer2,query3 

where ....;

(三)用途

1、提高SQL语句可读性。

  增加了SQL的易读性,如果构造了多个子查询,结构会更清晰。

2、提高执行效率。

with as在查询的时候建立临时表,数据是写入了内存中。“一次分析,多次使用”,达到提高sql执行性能的地方,达到了“少读”的目标。

如果WITH AS短语所定义的表名被调用两次以上,则优化器会自动将WITH AS短语所获取的数据放入一个TEMP表里,如果只是被调用一次,则不会。而提示materialize则是强制将WITH AS短语里的数据放入一个全局临时表里。很多查询通过这种方法都可以提高速度。

二、 递归查询 start with ... CONNECT by PRIOR

(一)含义

一般用来查找存在父子关系的数据,也就是树形结构的数据;其返还的数据也能够明确的区分出每一层的数据。

(二)语法

SELECT ... FROM    + 表名

WHERE              + 条件3

START WITH         + 条件1

CONNECT BY PRIOR   + 条件2

--示例

Select * From DEMO

Start With ID = '00001'

Connect By Prior ID = PID

start with condition1 是用来限制第一层的数据,或者叫根节点数据;以这部分数据为基础来查找第二层数据,然后以第二层数据查找第三层数据以此类推。

connect by [prior] id=parentid 这部分是用来指明oracle在查找数据时以怎样的一种关系去查找;比如说查找第二层的数据时用第一层数据的id去跟表里面记录的parentid字段进行匹配,如果这个条件成立那么查找出来的数据就是第二层数据,同理查找第三层第四层…等等都是按这样去匹配。

条件1: 表示从哪个节点开始查找, 也就是通过条件1 查询到的数据, 作为后续查询的起始节点(参数)。

当然可以放宽限定条件,如 ID in ('00001', '00011')以取得多个根节点,也就是多棵树;在连接关系中,除了可以使用列明外,还允许使用列表达式。

如果省略Start With

就默认把所有满足查询条件的Tree整个表中的数据从头到尾遍历一次,每一个数据做一次根,然后遍历树中其他节点信息。

条件2: 是连接条件,其中用PRIOR表示上一条记录,例如CONNECT BY PRIOR ID = PID,意思就是上一条记录的ID是本条记录的PID,即本记录的父亲是上一条记录。CONNECT BY子句说明每行数据将是按照层次顺序检索,并规定将表中的数据连入树形结构的关系中。

Prior 在父节点的一侧表示, 自底向上查, 在 子节点的一侧表示 自上向下查询;

条件3: 不能用在 Connect By 后, 这里的条件判断, 等价于 在最后查询出结果列表之后, 再进行条件筛选; 并非 删除掉 节点及子节点;

(三)语法变种

select * from table [start with condition1]

    connect by [prior] id=parentid

select * from table [start with condition1]

    connect by id= [prior] parentid

这种用法就表示从下往上查找数据,可以理解为从叶子节点往上查找父级几点,用第一层数据的parentid去跟表记录里面的id进行匹配,匹配成功那么查找出来的就是第二层数据;上面的那种就是从父级节点往下查找叶子节点。

(四)其他特性

1、level关键字,表示当前节点所处层级, 这里的层级指的是 从 start with 查询到的节点开始往下算起, 当前属于第几层级。第一层是数字1,第二层数字2,依次递增。

2、CONNECT_BY_ROOT方法,能够获取第一层集结点结果集中的任意字段的值;例CONNECT_BY_ROOT(字段名)。

3、nocycle关键字, 有时候数据本身 不合理会导致出现循环的问题, 如 将上述的 ID '00001' 记录的 'PID' 也改为 '00001', 会出现循环的问题, 这是, 需要用到 nocycle 即可消除循环;

Connect By nocycle Prior ID = PID 即可。

4、connect_by_isleaf 表示当前节点是否是叶子节点

Select ID, PID, DSC,

connect_by_isleaf isLeaf,

LEVEL

From DEMO

Connect By nocycle Prior ID = PID

Start With ID = '00001';

--结果

ID      PID     DSC  isLeaf     LEVEL

00001   00001   中国  0           0

00011   00001   陕西  0           1

00111   00011   西安  1           2

00112   00011   咸阳  1           2

00113   00011   延安  1           2

00012   00001   贵州  1           1

00013   00001   河南  1           1

另外一点: 如果在查询语句中 Select ID, PID, DSC, connect_by_isleaf isLeaf, LEVEL - 1 LEVEL 这种查询方式的话, 在 WHERE 判断条件中, 只需要判断 LEVEL = 1, 就可以取出 当前查询节点的 子节点(由于LEVEL 也是 伪列, 需要用子查询的方式);

(五)start with ,connect by prior其他变形

上面讲的用START WITH 指定树的根,然后用CONNECT BY指定递归条件.是最简单的也是最常用的形式.但实际上还有些变形.

1.START WITH 可以省略

比如

SELECT son FROM tree

CONNECT BY PRIOR son = father;

此时不指定树的根的话,就默认把Tree整个表中的数据从头到尾遍历一次,每一个数据做一次根,然后遍历树中其他节点信息.

在这个例子中,上面的SQL等价于

SELECT son FROM tree

START WITH father IN (爷爷,爸爸,儿子,孙子NB,孙子SB)

CONNECT BY PRIOR son = father;

那查询到的结果如下,有很多重复信息的

爸爸,儿子,孙子NB,孙子SB    儿子,孙子NB,孙子SB    孙子NB,孙子SB

2.START WITH 与CONNECT BY PRIOR位置可互换


SELECT son FROM tree

CONNECT BY PRIOR son = father

START WITH father = '爷爷';

3.nocycle关键字

我们知道标准的树结构中是不会有环的,但表中的树形结构不是标准的,有可能导致环的出现

比如

             ---------孙子SB

             |              ^

             |              |

爷爷 --> 爸爸 --> 儿子 -->孙子NB

哎在这里想用线条整个箭头出来真他妈麻烦啊.我又有点懒不想用其他画图工具啥的啊.反正假设儿子的儿子是孙子SB ,而孙子SB的儿子是爸爸.这样就形成一个环了.

当然在Oracle中的role是禁止出现循环的.比如你grant A to B ,grant B to C .再来个grant C to A会出错的.

假如有个上面的环,在再使用开始的递归查询语言会出错.得用nocycle关键字指定忽略环.

SELECT son FROM tree

START WITH father = '爷爷'

CONNECT BY NOCYCLE PRIOR son = father;

此时结果是

爸爸 儿子 孙子NB

你会注意到由于忽略了环,所以孙子SB的信息也被忽略掉了.

4. connect by prior 后面条件顺序的改变 

SELECT son FROM tree

START WITH father = '爷爷'

CONNECT BY PRIOR son = father;

这是开头的写法,但实际上也可以写成father = son倒过来写。有人说没倒过来是从上到下,从根往下.如果倒过来则是从下到上。

5.还可以加where条件

可以把start with ,connect 假装看成where 条件一样.所以在这个sql语句其他地方还可以加其他where 语句,可以看成与递归查询无关.只是对整个结果起过滤作用

比如

SELECT son FROM tree  WHERE son = '孙子SB'

START WITH father = '爷爷'

CONNECT BY PRIOR son = father;

--代码演示

--获取首条评论

with fatherComment as

 (select t.add_time,

         t.comm_content,

         decode(t.top_target_type,

                'image',

                '单图',

                'post',

                '帖子',

                'article',

                '文章',

                '其它') productType,

         t.target_type,

         t.target_id,

         t.comment_id,

         t.device_id

    from dbread.t_usercomment t

   where t.top_target_type not in

         ('comic', 'comic_part', 'cartoon', 'cartoon_part')

     and t.target_type != 'comment'

     and t.add_time >= date '2021-6-1'

     and t.add_time < date '2021-6-10'

   order by t.add_time desc ),

--获取评论的评论:使用start with... connect by prior获取父子层级树状数据

childComment as

 (select *

    from (select t.add_time,

                 t.comm_content,

                 t.target_id,

                 t.comment_id,

                 t.device_id,

                 LEVEL lev, --评论在结构树中的层级

                 CONNECT_BY_ROOT(t.comment_id) fatherCommentID --首条评论的id

            from dbread.t_usercomment t

           WHERE t.top_target_type not in

                 ('comic', 'comic_part', 'cartoon', 'cartoon_part')

             and t.target_type = 'comment'

             and t.add_time >= date '2021-6-1'

             and t.add_time < date '2021-6-10'

           START with t.target_type != 'comment'

                  and t.comment_id is not null

          CONNECT by PRIOR t.comment_id = t.target_id

           ) t

   where exists

   (select 1

            from dbread.t_usercomment tmp1

           where t.device_id = tmp1.device_id

             and trunc(t.add_time) = trunc(tmp1.add_time)

             and tmp1.add_time >= date '2021-6-1'

             and tmp1.add_time < date '2021-6-10')),

--评论回复数

commentReply as

 (select t.fatherCommentID, count(1) replyCount

    from childComment t

  --where condition

   group by t.fatherCommentID)

--最终结果

select t.add_time 发布时间,

       t.comm_content 评论内容,

       t.productType 产品类别,

       nvl(t1.replyCount, 0) 评论回复数,

       nvl(t2.praiseCount, 0) 评论获赞数

  from fatherComment t

  left join commentReply t1

    on t.comment_id = t1.fatherCommentID

  left join (select t.target_id, count(1) praiseCount

               from dbread.t_userPraise t

              where t.oper_type in ('01', '03', '04')

                and t.target_type = 'comment'

                and t.add_time >= date '2021-6-1'

                and t.add_time < date '2021-6-10'

              group by t.target_id) t2

    on t.comment_id = t2.target_id;

你可能感兴趣的:(Oracle中with as、START with ... CONNECT by PRIOR (CONNECT_BY_ROOT)的用法)