【问题标题】:Oracle SQL looped self-joinOracle SQL 循环自连接
【发布时间】:2020-04-03 19:15:36
【问题描述】:

上下文:
假设我有一个表,它有一个引用它自己的 PRIMARY KEY 的 FOREIGN KEY,如下所示:

|---------------------|------------------|------------------|
|          ID         |       NAME       |     PARENT_ID    |
|---------------------|------------------|------------------|
|          01         |       John       |         04       |
|---------------------|------------------|------------------|
|          02         |       Paul       |         01       |
|---------------------|------------------|------------------|
|          03         |       George     |         02       |
|---------------------|------------------|------------------|
|          04         |       Ringo      |         03       |
|---------------------|------------------|------------------|


问题:
如您所见,存在循环层次结构:Ringo->George->Paul->John->Ringo->George->Paul->John->etc.

问题:
是否有可以检测此类循环的 SQL select?

我知道我可以编写递归 PL/SQL 过程,但我更喜欢使用“纯”SQL 的解决方案。

提前谢谢你

【问题讨论】:

  • 带有循环检测的递归 cte/with?
  • 这是一个常见问题解答。 (显然。)在考虑发布之前,请阅读手册和谷歌任何错误消息或您的问题/问题/目标的许多清晰、简洁和精确的措辞,有和没有您的特定字符串/名称和站点:stackoverflow.com 和标签;阅读许多答案。如果您发布问题,请使用一个短语作为标题。反映你的研究。请参阅How to Ask 和投票箭头鼠标悬停文本。

标签: sql oracle optimization recursive-query self-join


【解决方案1】:

您可以使用带有 CONNECT_BY_ISCYCLE 伪列的 CONNECT BY 查询来查找循环 - 请参阅 example from Oracle docs

SELECT last_name "Employee", CONNECT_BY_ISCYCLE "Cycle"
FROM employees
WHERE level <= 3 
AND   department_id = 80
START WITH last_name = 'King'
CONNECT BY NOCYCLE PRIOR employee_id = manager_id;

【讨论】:

    【解决方案2】:

    您可以使用connect by nocycleconnect_by_iscycle 执行此操作。对于您的表结构,如下所示:

    select id, name, parent_id, connect_by_iscycle
    from mytable
    connect by nocycle id = prior parent_id
    start with id = 4
    

    connect by nocycle 导致查询在遇到循环时停止迭代,并且伪列 connect_by_iscycle 包含一个标志,指示它发生在哪个点,如 shown in this demo

    身份证 |姓名 |父 ID | CONNECT_BY_ISCYCLE -: | :----- | --------: | -----------------: 4 |林戈 | 3 | 0 3 |乔治 | 2 | 0 2 |保罗 | 1 | 0 1 |约翰 | 4 | 1 --> 在此处检测到循环

    【讨论】:

      【解决方案3】:

      这是一个递归 cte 的解决方案:

      with cte (id, parent_id, ids) as
      (
        select id, parent_id, to_char(id) from mytable
        union all
        select t.id, t.parent_id, ids || ' -> ' || t.id
        from cte
        join mytable t on t.id = cte.parent_id
      )
      cylce id set cycle to 1 default 0
      select ids as cycling_ids
      from cte
      where cycle = 1
      order by ids;
      

      结果:

      + ----------------------+ | CYCLING_IDS | + ----------------------+ | 1 -> 4 -> 3 -> 2 -> 1 | | 2 -> 1 -> 4 -> 3 -> 2 | | 3 -> 2 -> 1 -> 4 -> 3 | | 4 -> 3 -> 2 -> 1 -> 4 | + ----------------------+

      如果您只想查看每个循环一次(我假设),请记住每个循环的最小 ID,并且每个最小 ID 只显示一个循环:

      with cte (id, parent_id, ids, min_id) as
      (
        select id, parent_id, to_char(id), id from mytable
        union all
        select t.id, t.parent_id, ids || ' -> ' || t.id, least(t.id, cte.min_id)
        from cte
        join mytable t on t.id = cte.parent_id
      )
      cycle id set cycle to 1 default 0
      select min(ids) as cycling_ids
      from cte
      where cycle = 1
      group by min_id
      order by min_id;
      

      结果:

      + ----------------------+ | CYCLING_IDS | + ----------------------+ | 1 -> 4 -> 3 -> 2 -> 1 | + ----------------------+

      更多ID和不同案例的Demo:https://dbfiddle.uk/?rdbms=oracle_18&fiddle=f7f924cd8759d67a188b7c11f2d071ef

      (这仍然不完美。如果一个非常小的 ID 导致更高的 ID 形成一个循环,例如,如果我们插入一个 ID 0 引用 ID 3 作为父级,查询将多次显示循环。这是不容易避免,因为我们必须检测圆圈内的最小 ID。我可能会编写一个小的 PL/SQL 函数来从 ID 字符串中获取这个最小 ID。)

      【讨论】:

        猜你喜欢
        • 2016-12-22
        • 1970-01-01
        • 2010-10-01
        • 2013-12-21
        • 1970-01-01
        • 2019-05-20
        • 2015-06-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多