如图所示:

5、层次查询限制
1)层次查询from之后如果是table,只能是一个table,不能有join。
2)from之后如果是view,则view不能是带join的。
3)使用order by子句,order子句是在等级层次做完之后开始的,所以对于层次查询来说没有什么意义,除非特别关注level,获得某行在层次中的深度,但是这两种都会破坏层次。见5.3增强特性中的使用siblings排序。
4)在start with中表达式可以有子查询,但是connect by中不能有子查询。
以上是10g之前的限制,10g之后可以使用带join的表和视图,connect by中可以使用子查询。
理解关键操作:
层次查询的关键操作组件就是start with,connect by start with确定树的开始节点,如果这个语句确定的开始节点有多个(没有start with语句,那么每行都是一个根节点),那么根据层次查询的算法规则,会有多个符合start with条件的节点作为不同树的根节点,然后以每个根节点为开始搜索点,根据connect by条件来搜索符合条件的子节点,当所有符合connect by条件的根节点都找完其子节点,层次查询结束。
层次查询是在同一层where条件之前执行的,这个要注意,所以where条件不会破坏层次查询的节点所属根和层次(level),where只是简单的起到过滤结果的作用。这个容易出错,不要以为where写在from后connect by前就认为where先执行,那是不正确的,看下面结果(scott下的表):
--最普通的层次查询,根节点mgr is 确定只有一个,empno=7839为root
SQL> select empno,mgr,sys_connect_by_path(mgr,'->') ptree from emp
2 start with mgr is
3 connect by prior empno = mgr;
EMPNO MGR PTREE
----- ----- ---------------------------------------------------------------------------
7839 ->
7566 7839 ->->7839
7902 7566 ->->7839->7566
7369 7902 ->->7839->7566->7902
7698 7839 ->->7839
7499 7698 ->->7839->7698
7521 7698 ->->7839->7698
7654 7698 ->->7839->7698
7844 7698 ->->7839->7698
7900 7698 ->->7839->7698
7782 7839 ->->7839
7934 7782 ->->7839->7782
12 rows selected
--看start with增加了个or条件,那么现在的根就有两个,一个是上面的,另一个是mgr=7566,因此,下面的结果包含了:
--上面的结果,另外多了棵树的根是7566的,第1和第2行
--你的sql如果把start with中的flag=0却掉就是这种情况了,暂且不讨论你的connect by中flag=0去掉的情况
SQL> select empno,mgr,sys_connect_by_path(mgr,'->') ptree from emp
2 start with mgr is or mgr =7566
----- ----- ------------------------------------------------------------------------
7902 7566 ->7566
7369 7902 ->7566->7902
14 rows selected
--看增加where条件,只会的两行结果,但是mrg=7566所属的层次和所属的树都是没有变的,这相当于从两棵树上剪下两棵树枝,又叫树的修剪。
--可以看出where是在connect by之后起作用的,如果在之前起作用,就一个节点,何来树呢?看计划也可以的。
SQL> select empno,mgr,sys_connect_by_path(mgr,'->') ptree from emp
2 where mgr=7566
3 start with mgr is or mgr =7566
4 connect by prior empno = mgr
5 ;
EMPNO MGR PTREE
----- ----- --------------------------------------------------------------------
6、应用
1)查询每个等级上节点的数目
先查看总共有几个等级:
select count(distinct level) -- max(level)也可以
from s_emp
要查看每个等级上有多少个节点,只要按等级分组,并统计节点的数目即可,可以这样写:
select level,count(last_name)
connect by prior Elena' –被管理的节点
start with manager_id is –开始节点
connect by prior 成本' as name , as amount from dual union all
select 2,1 , '工资', from dual union all
select 3,2 , '基本工资', 1000 from dual union all
select 4,2 , '奖金' , 200 from dual union all
select 5,1 , '保险' , 400 from dual
)
SELECT root_id,SUM(amount)
FROM (select CONNECT_BY_ROOT(id) root_id,amount
from tmp
WHERE CONNECT_BY_ISLEAF=1
CONNECT BY PRIOR id = parentid
)
GROUP BY root_id;
--因为根节点没有amount,所以不需要去掉,如果有amount,而不需要包含根节点,则用10G的CONNECT_BY_ISLEAF=1只查子节点。
with tmp as (
select 1 as id , as parentid , '成本' as name , as amount from dual union all
SELECT id,parentid,name,
( SELECT SUM(amount) FROM
tmp a
-- WHERE CONNECT_BY_ISLEAF=1
START WITH a./'),2,
decode(instr(sys_connect_by_path(id,'/'),'/',1,2),
0,length(sys_connect_by_path(id,'/'))-1,
instr(sys_connect_by_path(id,'/'),'/',1,2)- instr(sys_connect_by_path(id,'/'),'/',1,1)-1 )
) root_id
8)找出指定层次中的叶子节点
Leaf(叶子)就是没有子孙的孤立节点。Oracle 10g提供了一个简单的connect_by_isleaf=1,0表示非叶子节点。
select level,id,manager_id,last_name, title from s_emp
where connect_by_isleaf=1 –表示查询叶子节点
下面的方法也可以,NOT EXISTS表示找到的没有子孙的节点,这种方法在10g之前比较好。
SELECT LEVEL,id,manager_id,last_name, title
FROM s_emp a
WHERE NOT EXISTS (SELECT 1 FROM s_emp b WHERE b.manager_id = a.id)
START WITH manager_id =2 CONNECT BY PRIOR 2008-10-1', 'YYYY-MM-DD') + ROWNUM - 1
FROM dual
CONNECT BY rownum <= to_date('2008-10-5', 'YYYY-MM-DD') -
to_date('2008-10-1', 'YYYY-MM-DD') + 1;
获取01到99
select case when length(rownum)=1 then to_char('0')||rownum else to_char(rownum) end
from dual
connect by rownum<=99;
select lpad(rownum,2,'0') from dual connect by rownum<=99;
9)在合并行上使用
比如:
create table t_test (l_pro_id varchar2(16), r_pro_id varchar2(16));
INSERT INTO t_test VALUES(1,10);
INSERT INTO t_test VALUES(1,11);
INSERT INTO t_test VALUES(1,12);
INSERT INTO t_test VALUES(1,13);
INSERT INTO t_test VALUES(1,14);
INSERT INTO t_test VALUES(1,15);
INSERT INTO t_test VALUES(1,16);
INSERT INTO t_test VALUES(1,17);
INSERT INTO t_test VALUES(2,2);
INSERT INTO t_test VALUES(2,3);
INSERT INTO t_test VALUES(2,4);
INSERT INTO t_test VALUES(2,5);
INSERT INTO t_test VALUES(2,6);
INSERT INTO t_test VALUES(2,7);
INSERT INTO t_test VALUES(2,8);
INSERT INTO t_test VALUES(2,9);
COMMIT;
要求结果
l_pro_id r_pro_list
--------------------------------
1 10,11,12,13,14,15,16,17
2 2,3,4,5,6,7,8,9
可以使用wmsys.wm_concat,也可以使用层次查询,这里用层次查询。
select l_pro_id,ltrim(max(sys_connect_by_path(r_pro_id,',')),',') --需要剔除第1个逗号
from
(select l_pro_id,r_pro_id,row_number over(partition by l_pro_id order by r_pro_id) rn from t_test)
start with rn =1
connect by prior rn = rn-1 and prior l_pro_1'||sys_connect_by_path(b,'->') path from t
where connect_by_isleaf=1
start with a=1
connect by prior b = a;
1->2->3->4->5->9
1->5->9
1->8->9
--这个找任意开始到结束节点,注意如果有循环的要加nocycle,10g才有
select *
from (select ltrim(sys_connect_by_path(a, '->') || '->' || b, '->') r
from t x
start with x.a = 1
connect by prior x.b = x.a)
where instr(r, '9') > 0
三、增强特性
1、SYS_CONNECT_BY_PATH
Oracle 9i提供了sys_connect_by_path(column,char),其中column是字符型或能自动转换成字符型的列名。它的主要目的就是将父节点到当前节点的”path”按照指定的模式展现出现。这个函数只能使用在层次查询中。
例如,要求将s_emp表中的层次关系展现出来,并且将last_name按照’=>’展现。如root,则是=>root_last_name, level=2的就是=>root_last_name=>level_2_last_name,并且利用lpad格式化报表。语句是:
select last_name,
level,
id,
lpad(' ', level * 2 - 1) || sys_connect_by_path(last_name, '=>') –前面按层次加空格,--并且后面加上路径
connect by prior id = manager_id;
结果如图所示:

下面的是oracle10g新增特性。
2、CONNECT_BY_ISLEAF伪列
在oracle9i的时候,查找指定root下的叶子节点,是很复杂的,oracle10g引入了一个新的伪列,connect_by_isleaf,如果行的值为0表示不是叶子节点,1表示是叶子节点。
找出s_emp中找出manager_http://www.mmnpw.com/upload_files/toutiao/161009/201610091146393451"
>
----找根节点为a,对应的所有叶子节点。

3、CONNECT_BY_ISCYCLE伪列和NOCYCLE关键字
如果从root节点开始找其子孙,找到一行,结果发生和祖先互为子孙的情况,则发生循环,Oracle会报ORA-01436: CONNECT BY loop in user data,在9i中只能将发生死循环的不加入到树中或删除,在10g中可以用nocycle关键字加在connect by之后,避免循环的参加查询操作。并且通过connect_by_iscycle得到哪个节点发生循环(也就是onnect_by_iscycle伪列只能与nocycle连用)。0表示未发生循环,1表示发生了循环,如:

结果是:
4、CONNECT_BY_ROOT
Oracle10g新增connect_by_root,用在列名之前表示此行的根节点的相同列名的值(这个和PRIOR一样,是层次查询的操作符)。如:
select connect_by_root last_name root_last_name, connect_by_root id root_id,
id,last_name,manager_id
结果为:

9i办法:用SYS_CONNECT_BY_PATH, 然后用INSTR, SUBSTR把第一截解析出来。
上面的可以改写为:
//选第2次出现分割符的位置,然后截取,因为根节点只有一个分隔符,所以加个decode.

5、使用SIBLINGS关键字排序
前面说了,对于层次查询如果用order by排序,比如order by last_name则是先做完层次获得level,然后按last_name排序,这样破坏了层次,比如特别关注某行的深度,按level排序,也是会破坏层次的。
在oracle10g中,增加了siblings关键字的排序。
语法:order siblings by
它会保护层次,并且在每个等级中按expre排序。
select level,
order siblings by last_name;
结果如图:

9i 办法:

相关专题:
精选专题(官网:dbaplus.cn)
◆ 近期热文◆
◆ 专家专栏◆
◆ 近期活动◆
Gdevops全球敏捷运维峰会广州站
峰会官网:www.gdevops.com