如何编写不添加已访问值的递归查询?
How to write a recursive query which does not add already visited values?
让我们假设任何给定的客户端都可以有多个盒子。每个框可以包含多个项目以及多个框(子框)。
BoxA -> Item1, Item2, BoxB, BoxC
遗憾的是,由于业务规则,可能会形成循环。
BoxA -> Item1, BoxB, BoxC
BoxB -> BoxA, BoxD
如您所见,BoxA包含BoxB,BoxB包含BoxA。
我试图解决的问题是获取客户端中给定框列表的所有子框。
因此,如果我正在寻找 BoxA 的子框(来自前面的示例),我将得到以下内容:BoxB、BoxC、BoxA、BoxD。
这是我目前拥有的:
WITH box_info AS (
-- This is typically a bit more complicated, that's why I have it in a seperate WITH clause
SELECT sub_box_id
FROM client_box
WHERE box_id = 1
),
all_sub_boxes(sub_box_id) AS (
SELECT sub_box_id
FROM box_info
WHERE sub_box_id IS NOT NULL
UNION ALL
SELECT cb.sub_box_id
FROM client_box cb, all_sub_boxes asb
WHERE cb.box_id = asb.sub_box_id AND cb.sub_box_id IS NOT NULL
-- AND cb.sub_box_id NOT IN (SELECT sub_box_id FROM all_sub_boxes)
)
SELECT sub_box_id FROM all_sub_boxes;
但是,由于有可能陷入递归循环,"all_sub_boxes" WITH 子句将失败。注释掉的行是我凭直觉输入的内容,因为它可以防止将已经访问过的子框添加到递归列表中,但似乎我们无法从内部引用 "all_sub_boxes"。
所以本质上,我需要一种方法来在递归查询中不包含已经包含的子框。
也许我可以创建一个临时文件 table?但我什至不知道是否可以在递归查询期间插入到 table 中。此外,如果我们每次都创建临时 table,那么每次此查询 运行 的成本是多少?
我正在尝试编写此查询,以便它可以跨不同的商业数据库使用,所以如果我可以避免非标准 sql,那就太好了。但我明白,如果不可能,那就是事实。
编辑
为了清楚起见,client_box
table 可能是这样的:
+--------+---------+------------+
| BOX_ID | ITEM_ID | SUB_BOX_ID |
+--------+---------+------------+
| BoxA | Item1 | (null) |
| BoxA | (null) | BoxB |
| BoxA | (null) | BoxC |
| BoxB | (null) | BoxA |
| BoxB | (null) | BoxD |
+--------+---------+------------+
您走在正确的轨道上,您似乎只需要一点帮助来处理循环。请参阅递归 CTE 定义末尾的 CYCLE 子句(即使 CYCLE 子句出现在递归 CTE 的右括号之后,它仍然是它的一部分):
with
-- Begin simulated data.
client_box ( box_id, item_id, sub_box_id ) as (
select 'BoxA', 'Item1', null from dual union all
select 'BoxA', null , 'BoxB' from dual union all
select 'BoxA', null , 'BoxC' from dual union all
select 'BoxB', null , 'BoxA' from dual union all
select 'BoxB', null , 'BoxD' from dual
),
-- End of simulated data (for testing only, not part of the solution).
-- SQL query consists of the keyword WITH (above) and the lines below.
-- Use your actual table and column names.
-- Use whatever mechanism works for you in the ANCHOR branch of r (below).
r ( box_id ) as (
select 'BoxA' from dual -- Modify this for inputs
union all
select c.sub_box_id
from client_box c join r on c.box_id = r.box_id
where c.sub_box_id is not null
)
cycle box_id set cycle to 1 default 0
select box_id
from r
where cycle = 0
;
BOX_ID
------
BoxA
BoxB
BoxC
BoxD
让我们假设任何给定的客户端都可以有多个盒子。每个框可以包含多个项目以及多个框(子框)。
BoxA -> Item1, Item2, BoxB, BoxC
遗憾的是,由于业务规则,可能会形成循环。
BoxA -> Item1, BoxB, BoxC
BoxB -> BoxA, BoxD
如您所见,BoxA包含BoxB,BoxB包含BoxA。
我试图解决的问题是获取客户端中给定框列表的所有子框。
因此,如果我正在寻找 BoxA 的子框(来自前面的示例),我将得到以下内容:BoxB、BoxC、BoxA、BoxD。
这是我目前拥有的:
WITH box_info AS (
-- This is typically a bit more complicated, that's why I have it in a seperate WITH clause
SELECT sub_box_id
FROM client_box
WHERE box_id = 1
),
all_sub_boxes(sub_box_id) AS (
SELECT sub_box_id
FROM box_info
WHERE sub_box_id IS NOT NULL
UNION ALL
SELECT cb.sub_box_id
FROM client_box cb, all_sub_boxes asb
WHERE cb.box_id = asb.sub_box_id AND cb.sub_box_id IS NOT NULL
-- AND cb.sub_box_id NOT IN (SELECT sub_box_id FROM all_sub_boxes)
)
SELECT sub_box_id FROM all_sub_boxes;
但是,由于有可能陷入递归循环,"all_sub_boxes" WITH 子句将失败。注释掉的行是我凭直觉输入的内容,因为它可以防止将已经访问过的子框添加到递归列表中,但似乎我们无法从内部引用 "all_sub_boxes"。
所以本质上,我需要一种方法来在递归查询中不包含已经包含的子框。
也许我可以创建一个临时文件 table?但我什至不知道是否可以在递归查询期间插入到 table 中。此外,如果我们每次都创建临时 table,那么每次此查询 运行 的成本是多少?
我正在尝试编写此查询,以便它可以跨不同的商业数据库使用,所以如果我可以避免非标准 sql,那就太好了。但我明白,如果不可能,那就是事实。
编辑
为了清楚起见,client_box
table 可能是这样的:
+--------+---------+------------+
| BOX_ID | ITEM_ID | SUB_BOX_ID |
+--------+---------+------------+
| BoxA | Item1 | (null) |
| BoxA | (null) | BoxB |
| BoxA | (null) | BoxC |
| BoxB | (null) | BoxA |
| BoxB | (null) | BoxD |
+--------+---------+------------+
您走在正确的轨道上,您似乎只需要一点帮助来处理循环。请参阅递归 CTE 定义末尾的 CYCLE 子句(即使 CYCLE 子句出现在递归 CTE 的右括号之后,它仍然是它的一部分):
with
-- Begin simulated data.
client_box ( box_id, item_id, sub_box_id ) as (
select 'BoxA', 'Item1', null from dual union all
select 'BoxA', null , 'BoxB' from dual union all
select 'BoxA', null , 'BoxC' from dual union all
select 'BoxB', null , 'BoxA' from dual union all
select 'BoxB', null , 'BoxD' from dual
),
-- End of simulated data (for testing only, not part of the solution).
-- SQL query consists of the keyword WITH (above) and the lines below.
-- Use your actual table and column names.
-- Use whatever mechanism works for you in the ANCHOR branch of r (below).
r ( box_id ) as (
select 'BoxA' from dual -- Modify this for inputs
union all
select c.sub_box_id
from client_box c join r on c.box_id = r.box_id
where c.sub_box_id is not null
)
cycle box_id set cycle to 1 default 0
select box_id
from r
where cycle = 0
;
BOX_ID
------
BoxA
BoxB
BoxC
BoxD