如何编写不添加已访问值的递归查询?

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