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;