Oracle:分层查询中的编号组

Oracle: Numbered groups in a hierarchical query

在我的 Oracle 数据库中,我有一个 table 定义了 predecessors/successors 的层次结构,它既可以分支也可以循环。我附上了 SQL fiddle 来演示 table 是如何工作的。我的意图是为每棵孤立的树分配它自己的编号。请参阅下图以了解所需结果(请注意,在此图片中,成员被命名为 a,b,c,d... 而在所附的 fiddle 中,它们编号为 1,2,3,4...):

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

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

提前谢谢大家。

换句话说,我的理解是,对于每个 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;

免责声明:此解决方案适用于 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 中的 运行 示例。

那些不是层次结构;它们是有向图。您仍然可以在 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    |
+--------------+------+