有没有人知道为什么当循环发生在顶级节点(根节点连接到根节点)时,Oracle继续遵循超出循环循环的路径?更重要的是,如何预防?
我有Oracle 11g第2版(11.2),我一直在探索分层查询。我将围绕Oracle数据库SQL语言参考第9-4页的图9-1中的树结构构建我的问题
我使用供应商和客户的概念为这棵树创建了一个表格结构:
create table t
( vendor varchar2(3)
, customer varchar2(3)
);
insert into t values ( '1' , '2' );
insert into t values ( '2' , '3' );
insert into t values ( '2' , '4' );
insert into t values ( '4' , '5' );
insert into t values ( '4' , '6' );
insert into t values ( '1' , '7' );
insert into t values ( '7' , '8' );
insert into t values ( '1' , '9' );
insert into t values ( '9' , '10' );
insert into t values ( '10' , '11' );
insert into t values ( '9' , '12' );
commit;
以下选择查询遍历树没有问题:
select vendor,
customer,
level,
connect_by_isleaf as isleaf,
connect_by_iscycle as iscycle,
connect_by_root vendor||sys_connect_by_path(customer,' ~ ') as path
from t
connect by nocycle
vendor=prior customer
start with vendor='1';
给出结果:
Vendor Cust Level Isleaf Iscycle Path
1 2 1 0 0 1 ~ 2
2 3 2 1 0 1 ~ 2 ~ 3
2 4 2 0 0 1 ~ 2 ~ 4
4 5 3 1 0 1 ~ 2 ~ 4 ~ 5
4 6 3 1 0 1 ~ 2 ~ 4 ~ 6
1 7 1 0 0 1 ~ 7
7 8 2 1 0 1 ~ 7 ~ 8
1 9 1 0 0 1 ~ 9
9 10 2 0 0 1 ~ 9 ~ 10
10 11 3 1 0 1 ~ 9 ~ 10 ~ 11
9 12 2 1 0 1 ~ 9 ~ 12
然后我通过在结构中添加循环来复杂化。首先是销售给自己的供应商的记录......
--self cycle
insert into t values ( '4' , '4' );
和一个供应商,其中客户是其供应商的供应商......
--ancestor cycle
insert into t values ( '6' , '2' );
重新执行上面的选择查询会产生与上面相同的输出,除了第3行和第5行(路径1~2~4和1~2~4~6)的Iscycle为1。请注意,CONNECT BY命名法标记循环的父记录而不是实际完成循环的子记录。 (所以我知道4和6都循环回祖先,但我不知道哪个祖先。)
再添加两条记录会在原始树的分支上创建一个更大的循环:
--cycle crossing branches of tree
insert into t values ( '6' , '9' );
insert into t values ( '11' , '2' );
再次重新执行select查询会得到以下输出:
Vendor Customer Level Isleaf Iscycle Path
1 2 1 0 0 1 ~ 2
2 3 2 1 0 1 ~ 2 ~ 3
2 4 2 0 1 1 ~ 2 ~ 4
4 5 3 1 0 1 ~ 2 ~ 4 ~ 5
4 6 3 0 1 1 ~ 2 ~ 4 ~ 6
6 9 4 0 0 1 ~ 2 ~ 4 ~ 6 ~ 9
9 10 5 0 0 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 10
10 11 6 1 1 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 10 ~ 11
9 12 5 1 0 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 12
1 7 1 0 0 1 ~ 7
7 8 2 1 0 1 ~ 7 ~ 8
1 9 1 0 0 1 ~ 9
9 10 2 0 0 1 ~ 9 ~ 10
10 11 3 0 0 1 ~ 9 ~ 10 ~ 11
11 2 4 0 0 1 ~ 9 ~ 10 ~ 11 ~ 2
2 3 5 1 0 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 3
2 4 5 0 1 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4
4 5 6 1 0 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4 ~ 5
4 6 6 1 1 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4 ~ 6
9 12 2 1 0 1 ~ 9 ~ 12
输出继续如预期。所有循环都是flaged,并且在遇到循环时映射停止。
现在问题孩子......让我们为根节点添加一个自循环,这与上面用节点4创建的第一个循环完全相同;仅适用于节点1.
insert into t values ( '1' , '1' );
这次Oracle按预期检测到节点1的循环(第一行标记为Iscycle设置为1);然而,它继续经过这个循环并构建了两次整个树结构。第2行到第21行是第22行到第41行的重复,节点1的循环被预先放在路径的前面。
Vendor Customer Level Isleaf Iscycle Path
1 1 1 0 1 1 ~ 1
1 2 2 0 0 1 ~ 1 ~ 2
2 3 3 1 0 1 ~ 1 ~ 2 ~ 3
2 4 3 0 1 1 ~ 1 ~ 2 ~ 4
4 5 4 1 0 1 ~ 1 ~ 2 ~ 4 ~ 5
4 6 4 0 1 1 ~ 1 ~ 2 ~ 4 ~ 6
6 9 5 0 0 1 ~ 1 ~ 2 ~ 4 ~ 6 ~ 9
9 10 6 0 0 1 ~ 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 10
10 11 7 1 1 1 ~ 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 10 ~ 11
9 12 6 1 0 1 ~ 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 12
1 7 2 0 0 1 ~ 1 ~ 7
7 8 3 1 0 1 ~ 1 ~ 7 ~ 8
1 9 2 0 0 1 ~ 1 ~ 9
9 10 3 0 0 1 ~ 1 ~ 9 ~ 10
10 11 4 0 0 1 ~ 1 ~ 9 ~ 10 ~ 11
11 2 5 0 0 1 ~ 1 ~ 9 ~ 10 ~ 11 ~ 2
2 3 6 1 0 1 ~ 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 3
2 4 6 0 1 1 ~ 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4
4 5 7 1 0 1 ~ 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4 ~ 5
4 6 7 1 1 1 ~ 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4 ~ 6
9 12 3 1 0 1 ~ 1 ~ 9 ~ 12
1 2 1 0 0 1 ~ 2
2 3 2 1 0 1 ~ 2 ~ 3
2 4 2 0 1 1 ~ 2 ~ 4
4 5 3 1 0 1 ~ 2 ~ 4 ~ 5
4 6 3 0 1 1 ~ 2 ~ 4 ~ 6
6 9 4 0 0 1 ~ 2 ~ 4 ~ 6 ~ 9
9 10 5 0 0 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 10
10 11 6 1 1 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 10 ~ 11
9 12 5 1 0 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 12
1 7 1 0 0 1 ~ 7
7 8 2 1 0 1 ~ 7 ~ 8
1 9 1 0 0 1 ~ 9
9 10 2 0 0 1 ~ 9 ~ 10
10 11 3 0 0 1 ~ 9 ~ 10 ~ 11
11 2 4 0 0 1 ~ 9 ~ 10 ~ 11 ~ 2
2 3 5 1 0 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 3
2 4 5 0 1 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4
4 5 6 1 0 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4 ~ 5
4 6 6 1 1 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4 ~ 6
9 12 2 1 0 1 ~ 9 ~ 12
为什么1-1循环的处理与4-4循环不相同?我错过了什么?
为了缓解这种情况,我在CONNECT BY子句中添加了一个附加条件,要求客户不是'1'。
select vendor,
customer,
level,
connect_by_isleaf as isleaf,
connect_by_iscycle as iscycle,
connect_by_root vendor||sys_connect_by_path(customer,' ~ ') as path
from t
connect by nocycle
vendor=prior customer
and customer<>'1'
start with vendor='1';
具有讽刺意味的是,所有这一切都是从第一行中删除循环标志。
任何帮助都将不胜感激。
答案 0 :(得分:1)
Oracle选择层次结构的root row(s)
(满足START WITH条件的行)。
Oracle选择每个根行的子行。
每个子行必须满足CONNECT BY
条件相对于其中一个根行的条件。
要查找父行的子项,Oracle会计算父行的CONNECT BY条件的PRIOR表达式以及表中每行的另一个表达式。
条件为真的行是父项的子项。
CONNECT BY
条件可以包含其他条件以进一步过滤查询选择的行。
A root row is the highest row within an inverted tree.
如果您尝试使用与孩子相同的父母(22或33或44),它将起作用,因为它们不是根行而只是父母 由于1是根,也是1的子节点,因为CONNECT_BY_ROOT子句将LEVEL设置为循环
输出中的重复也发生在connect by works on root which is duplicated
之后。
Oracle is not able to restrict the uniqueness since Oracle can't give preference to one of the other
使您的数据集唯一或对其进行编码,以便oracle可以在层次结构
中的首选项上工作关注: OP问题的解决方案
SELECT
VENDOR,
CUSTOMER,
LEVEL,
CONNECT_BY_ISLEAF AS ISLEAF,
CONNECT_BY_ISCYCLE AS ISCYCLE,
CONNECT_BY_ROOT VENDOR
|| SYS_CONNECT_BY_PATH ( CUSTOMER,
' ~ ' )
AS PATH
FROM
(SELECT
VENDOR,
CUSTOMER
FROM
T
WHERE
CUSTOMER <> '1')
CONNECT BY
NOCYCLE VENDOR = PRIOR CUSTOMER
START WITH
VENDOR = '1';
<强>结果:强>
VENDOR CUSTOMER LEVEL ISLEAF ISCYCLE PATH
------ -------- ---------- ---------- ------------------------------------------------------------------------------------------
1 2 1 0 0 1 ~ 2
2 3 2 1 0 1 ~ 2 ~ 3
2 4 2 0 1 1 ~ 2 ~ 4
4 5 3 1 0 1 ~ 2 ~ 4 ~ 5
4 6 3 0 1 1 ~ 2 ~ 4 ~ 6
6 9 4 0 0 1 ~ 2 ~ 4 ~ 6 ~ 9
9 10 5 0 0 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 10
10 11 6 1 1 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 10 ~ 11
9 12 5 1 0 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 12
1 7 1 0 0 1 ~ 7
7 8 2 1 0 1 ~ 7 ~ 8
1 9 1 0 0 1 ~ 9
9 10 2 0 0 1 ~ 9 ~ 10
10 11 3 0 0 1 ~ 9 ~ 10 ~ 11
11 2 4 0 0 1 ~ 9 ~ 10 ~ 11 ~ 2
2 3 5 1 0 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 3
2 4 5 0 1 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4
4 5 6 1 0 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4 ~ 5
4 6 6 1 1 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4 ~ 6
9 12 2 1 0 1 ~ 9 ~ 12
20 rows selected
答案 1 :(得分:1)
我同意@realspirituals关于Oracle如何处理分层数据的初步部分解释。在我看来,第一步是找到START WITH子句指定的树的根元素。这可能会改为以下查询:
select * from t where vendor = '1';
VENDOR CUSTOMER
------------------
1 2
1 7
1 9
1 1
实际上我们有4个根节点和4个独立的树。接下来的步骤是迭代评估CONNECT BY子句。想象一下,我们将上面的CUSTOMER值列表并寻找他们的后代:
select * from t where vendor in ('2', '7', '9', '1');
VENDOR CUSTOMER
------------------
1 2
2 3
2 4
1 7
7 8
1 9
9 10
9 12
1 1 --This one is loop and is not taken to final resultset
一旦我们指定了NOCYCLE,就会抛弃检测到的循环,导致我们进入循环记录的前一行被标记为CONNECT_BY_ISCYCLE = 1.
第三步:
select * from t where vendor in ('2', '3', '4', '7', '8', '9', '10', '12');
VENDOR CUSTOMER
------------------
2 3
2 4
4 5
4 6
7 8
9 10
10 11
9 12
4 4 --This one is loop
直到输出中至少有一条记录为止。这需要一些时间和耐心,但您的查询返回的结果是完全可重现的,对我来说似乎绝对合法。这就是Oracle的algorythm工作方式,所以每个人在编写查询时都必须牢记这一点。
我们如何避免在顶级节点上循环?我建议添加虚拟记录,使我们的顶级节点不是最重要的节点。考虑一下:
insert into t values(null, '1');
select vendor,
customer,
level,
connect_by_isleaf as isleaf,
connect_by_iscycle as iscycle,
connect_by_root vendor||sys_connect_by_path(customer,' ~ ') as path
from t
connect by nocycle
vendor=prior customer
start with vendor is null; --Note the changed condition
Vendor Customer Level Isleaf Iscycle Path
------------------------------------------------------------
1 1 0 1 ~ 1
1 2 2 0 0 ~ 1 ~ 2
2 3 3 1 0 ~ 1 ~ 2 ~ 3
2 4 3 0 1 ~ 1 ~ 2 ~ 4
4 5 4 1 0 ~ 1 ~ 2 ~ 4 ~ 5
4 6 4 0 1 ~ 1 ~ 2 ~ 4 ~ 6
6 9 5 0 0 ~ 1 ~ 2 ~ 4 ~ 6 ~ 9
9 10 6 0 0 ~ 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 10
10 11 7 1 1 ~ 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 10 ~ 11
9 12 6 1 0 ~ 1 ~ 2 ~ 4 ~ 6 ~ 9 ~ 12
1 7 2 0 0 ~ 1 ~ 7
7 8 3 1 0 ~ 1 ~ 7 ~ 8
1 9 2 0 0 ~ 1 ~ 9
9 10 3 0 0 ~ 1 ~ 9 ~ 10
10 11 4 0 0 ~ 1 ~ 9 ~ 10 ~ 11
11 2 5 0 0 ~ 1 ~ 9 ~ 10 ~ 11 ~ 2
2 3 6 1 0 ~ 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 3
2 4 6 0 1 ~ 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4
4 5 7 1 0 ~ 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4 ~ 5
4 6 7 1 1 ~ 1 ~ 9 ~ 10 ~ 11 ~ 2 ~ 4 ~ 6
9 12 3 1 0 ~ 1 ~ 9 ~ 12
当然,mignt不适合将新记录添加到生产数据库。而是将查询与实际表格相结合,并使用一些动态确定顶级节点的查询。这样的东西(给出与上面相同的输出):
delete from t where vendor is null; --Removing previosly inserted record
select vendor,
customer,
level,
connect_by_isleaf as isleaf,
connect_by_iscycle as iscycle,
connect_by_root vendor||sys_connect_by_path(customer,' ~ ') as path
from (select vendor, customer from t
union all
select distinct null, vendor from t
where vendor = 1) --Here is your START WITH condition
connect by nocycle
vendor=prior customer
start with vendor is null;
答案 2 :(得分:-1)
从节点开始并将一个节点连接到另一个节点并不是一回事。 ISCYCLE
查找客户〜供应商连接,并且每个路径只连接一次。如果你告诉oracle
START WITH vendor = '1'
它实际上从4点开始同时:
1 ~ 1
1 ~ 2
1 ~ 7
1 ~ 9
这些路径搜索是并行执行的 ,并且每条路径都尝试不以自己的路径循环。每条路径都不了解其他路径。因此,以1 ~ 1
开头的路径不知道为什么它应该停止继续到2,7和9,因为它之前没有出现过。 NOCYCLE
只是禁止再次查看1。所以你可以
START WITH (vendor='1' AND customer !='1')
避免过多的起点和/或忽略供应商和客户相同的任何连接:
CONNECT BY NOCYCLE ( vendor = PRIOR customer AND vendor != customer )
答案 3 :(得分:-1)
nocycle
实际上允许您的查询有周期,没有该关键字,Oracle会在检测到周期后立即停止(ORA-01436: CONNECT BY loop in user data
)。它还允许您使用“CONNECT_BY_ISCYCLE”来检测孩子正在进行循环的位置,但是对该结果过滤查询将删除有效行。
那么,也许,您可以在循环条件中使用connect by nocycle vendor=prior customer AND connect_by_iscycle = 0
来避免在检测到循环父项后的所有循环? (我没有东西要测试它)。这将在第一个1~1路径上停止递归。