【问题标题】:Oracle: Numbered groups in a hierarchical queryOracle:分层查询中的编号组
【发布时间】:2020-08-10 08:01:21
【问题描述】:

在我的 Oracle 数据库中,我有一个表定义了前任/继任者的层次结构,它既可以分支也可以循环。我附上了一个 SQL fiddle 来演示该表是如何工作的。我的目的是为每棵孤立的树分配它自己的编号。有关所需结果的说明,请参见下图(请注意,在这张图片中,成员被命名为 a,b,c,d...而在随附的小提琴中,它们的编号为 1 ,2,3,4...):

http://sqlfiddle.com/#!4/4c887d/4/0

我还没有弄清楚如何构建这样的查询,此时我非常绝望。 任何帮助,甚至是指向解决方案的指针——任何类型的输入——都将不胜感激。

提前谢谢大家。

【问题讨论】:

  • 我无法理解您希望从小提琴链接中输出结果的方式。你能添加你的预期结果吗
  • 嗨@Sujitmohanty30,小提琴并不完全应该解释所需的输出。出于这个确切原因,该图表包含在问题中。但是,我添加了第二张图片以表格形式展示我想要的输出,这将更容易理解。感谢您的意见。

标签: sql database oracle hierarchical-data recursive-query


【解决方案1】:

那些不是层次结构;它们是有向图。您仍然可以在 Oracle 中使用 CONNECT BY 来处理它们,但如果没有 START WITH 的根节点,如果您的数据集很大,性能可能会出现问题。

无论如何,你需要做的是CONNECT BY NOCYCLE 没有START WITH。这将从每个单独的节点开始计算一棵树。然后,获取每个节点的 CONNECT_BY_ROOT 并获取每个不同节点值的 MIN()

这是一个带有数据的工作示例。它比它需要的更复杂,因为没有地方可以获得所有节点的完整列表,所以你必须将每个顶点分成两行(一个有 from_node,一个有 to_node)以确保你包括全部。

CREATE TABLE tbl_tst ( from_node VARCHAR2(1), to_node VARCHAR2(1) );

INSERT INTO tbl_tst VALUES ( 'a', 'b');
INSERT INTO tbl_tst VALUES ( 'b', 'c');
INSERT INTO tbl_tst VALUES ( 'b', 'd');
INSERT INTO tbl_tst VALUES ( 'd', 'a');
INSERT INTO tbl_tst VALUES ( 'd', 'e');

INSERT INTO tbl_tst VALUES ( 'f', 'g');
INSERT INTO tbl_tst VALUES ( 'g', 'h');
INSERT INTO tbl_tst VALUES ( 'h', 'f');

INSERT INTO tbl_tst VALUES ( 'i', 'j');

COMMIT;


WITH groups AS (
SELECT DISTINCT 
       tt.from_node, 
       tt.to_node, 
       MIN(CONNECT_BY_ROOT(from_node)) 
             OVER ( PARTITION BY tt.from_node) group_min
FROM   tbl_tst tt
CONNECT BY NOCYCLE from_node = PRIOR to_node
              OR   to_node = PRIOR from_node
)
SELECT DENSE_RANK() OVER ( ORDER BY group_min ) group_number,
       DECODE(splitter.rn,1,groups.from_node,2,groups.to_node) node       
FROM groups 
CROSS JOIN ( SELECT rownum rn FROM dual CONNECT BY rownum <= 2 ) splitter
GROUP BY DECODE(splitter.rn,1,groups.from_node,2,groups.to_node),
         group_min
ORDER BY group_min, node;
+--------------+------+
| GROUP_NUMBER | NODE |
+--------------+------+
|            1 | a    |
|            1 | b    |
|            1 | c    |
|            1 | d    |
|            1 | e    |
|            2 | f    |
|            2 | g    |
|            2 | h    |
|            3 | i    |
|            3 | j    |
+--------------+------+

【讨论】:

  • from_node = PRIOR to_node -- 可能更改为from_node = PRIOR to_node OR to_node = PRIOR from_node,因为您不需要考虑图形弧线的方向。这种变化可以更快地遍历子图,产生更少的分支,并提高性能。嗯...只要我理解你的解决方案。
  • @TheImpaler 实际上在第一个 CTE 中产生了更多行。使CONNECT BY 条件更宽,可以产生更多的连接,而不是更少。
  • @TheImpaler 但我刚刚意识到,如果您有一行,例如“m->h”,我的解决方案将不起作用。所以,我想我需要你改变产品在所有情况下的正确结果。谢谢。
  • 您的解决方案比我的要简洁得多。另外,它适用于 Oracle。 +1
  • 圣烟。这完美地工作。我会花一些时间来分析你的解决方案和具体的内部运作,但赏金肯定是你的。1 小时后,我才能授予它。脱帽致敬 - 而且,感谢您给我指点以查找有关此问题的更多信息。了解它的名称将有助于我更深入地了解它。
【解决方案2】:

换句话说,对于每个 root_node,您需要为其分配一个组号。要找到根节点,我们可以使用connect_by_root,然后我们可以使用dense_rank 给它一个数字。

到目前为止我得到了这个,你能检查一下sqlfiddle

SELECT predecessor
      ,successor
      ,sys_connect_by_path(successor
                          ,'/') AS hierarchy_path
      ,LEVEL node_level
      ,dense_rank() over(ORDER BY connect_by_root successor) group_number
FROM   tbl_tst
CONNECT BY nocycle PRIOR predecessor = successor;

【讨论】:

  • 不完全是,虽然这已经接近了。如您的小提琴中所示,GROUP_NUMBERs 1,2,3 应该都是 GROUP_NUMBER=1,因为它们之间存在连接。路径 /1/3/2 与路径 /2/1/3 或 /3/2/1 是同一棵树的一部分。因此,它们都应该具有相同的 GROUP_NUMBER。
  • 明白你的意思。这里需要做一些分析。尝试了一些更改,但没有运气
【解决方案3】:

免责声明:此解决方案适用于 PostgreSQL,而不是按要求适用于 Oracle。

下面的查询演示了解决为每个子图分配唯一序列号问题的策略。

注意:我尝试为 Oracle 编写查询,但 XMLTable() 函数似乎有问题。如果您找到解决方法(我放弃了),也许您可​​以将其适应 Oracle。

with recursive
n (root_id, pred, succ, s) as (
  select pred, pred, succ, '/' || least(pred, succ) || '/' || greatest(pred, succ) || '/' from t
 union all
  select n.root_id, t.pred, t.succ, (
    select '/' || string_agg(vl, '/') || '/' from (
      select unnest(string_to_array(substring(n.s from 2 for length(n.s) - 2), '/')) as vl
      union all select (case when t.pred = n.pred or t.pred = n.succ then t.succ else t.pred end)
      order by vl
      ) y
    )
  from n
  join t on not (n.pred = t.pred and n.succ = t.succ) and (n.pred = t.pred or n.pred = t.succ or n.succ = t.pred or n.succ = t.succ)
            and not n.s ~ ('/' || case when t.pred = n.pred or t.pred = n.succ then t.succ else t.pred end || '/')
),
s as (
  select distinct s from (
    select distinct on (root_id) root_id, s from n group by root_id, s order by root_id, length(s) desc
  ) j
)
select * from (
  select
    unnest(string_to_array(substring(s from 2 for length(s) - 2), '/')) as node,
    row_number() over(order by s) as g 
  from s order by s
) z
order by g, node;

结果:

node  g
----  -
a     1
b     1
c     1
d     1
e     1
f     2
g     2
h     2
i     3
j     3

请参阅DB Fiddle 的运行示例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多