从子查询因子到Oracles“connect by”的图的分层查询

时间:2018-05-23 09:08:16

标签: sql oracle hierarchy

我有一个简单的有向图结构,表格如下:

CREATE TABLE node (id NUMBER(8, 0), NAME VARCHAR2(100));
CREATE TABLE edge (parent NUMBER(8, 0), child NUMBER(8, 0));

其中node存储所有节点,edge所有有向边从parentchild。 我现在想要的是一个视图,显示从所有根(没有传入边的节点)到图中所有其他节点(包括零长度路径)的所有路径。图表保证没有周期。

我使用了子查询因子分层查询,结果是:

WITH rec_view(id, parent, lvl, root_id, path) AS (
    SELECT
        id
        , NULL
        , 1 lvl
        , id root_id
        , '/' || TO_CHAR(id) path
    FROM node g
    WHERE NOT EXISTS (SELECT 1 FROM edge h WHERE h.child = g.id)
    UNION ALL
    SELECT
        hier.child
        , prev.id
        , prev.lvl + 1 lvl
        , prev.root_id
        , prev.path || '/' || hier.child path
    FROM rec_view prev
        JOIN edge hier ON prev.id = hier.parent 
)
SELECT
    n.id, n.NAME, v.root_id, v.lvl, v.path
FROM
    rec_view v
    JOIN node n ON v.id = n.id

使用此测试数据,我得到10行的预期结果。

INSERT INTO node VALUES (1, 'KBR');
INSERT INTO node VALUES (2, 'H');
INSERT INTO node VALUES (3, 'N');
INSERT INTO node VALUES (4, 'KBR H');
INSERT INTO node VALUES (5, 'KBR N');
INSERT INTO node VALUES (6, 'Dummy');
INSERT INTO node VALUES (7, 'Nach H');

INSERT INTO edge VALUES (1, 4);
INSERT INTO edge VALUES (1, 5);
INSERT INTO edge VALUES (2, 4);
INSERT INTO edge VALUES (3, 5);
INSERT INTO edge VALUES (4, 7);

ID  Name   Root  Level    Path
------------------------------
1   KBR       1      1      /1
2   H         2      1      /2
3   N         3      1      /3
4   KBR H     1      2    /1/4
4   KBR H     2      2    /2/4
5   KBR N     1      2    /1/5
5   KBR N     3      2    /3/5
6   Dummy     6      1      /6
7   Nach H    2      3  /2/4/7
7   Nach H    1      3  /1/4/7

但是,我想要的是一个使用Oracles CONNECT BY功能的查询,因为我认为它比子查询因子更容易阅读和理解,它也是我们的分层查询的首选方式公司。但是我提出的问题给我带来了太多的结果。我得到的是:

SELECT
    parent.id id
    , parent.NAME NAME
    , connect_by_root parent.NAME root_id
    , LEVEL lvl
    , sys_connect_by_path(parent.id, '/') path
FROM
    node parent
    LEFT JOIN edge hier ON parent.id = hier.parent
START WITH
    NOT EXISTS (SELECT 1 FROM edge h WHERE h.child = parent.id)
CONNECT BY
    PRIOR hier.child = parent.id

这里我得到11行,而不是10行,其中包含路径/1的行包含两次。

ID  Name    Root  Level    Path
-------------------------------
1   KBR        1      1      /1
4   KBR H      1      2    /1/4
7   Nach H     1      3  /1/4/7
1   KBR        1      1      /1
5   KBR N      1      2    /1/5
2   H          2      1      /2
4   KBR H      2      2    /2/4
7   Nach H     2      3  /2/4/7
3   N          3      1      /3
5   KBR N      3      2    /3/5
6   Dummy      6      1      /6

我的查询有什么问题?如何解决这个问题只包括所需的行?

2 个答案:

答案 0 :(得分:2)

  

我的查询有什么问题?如何解决这个问题只包括所需的行?

您的查询没有任何问题。分层查询正在考虑边缘,因此如果您包含child列,您会看到行id = 1的差异:

SQL Fiddle

查询1

SELECT parent.id id
     , parent.NAME NAME
     , connect_by_root parent.NAME root_id
     , LEVEL lvl
     , sys_connect_by_path(parent.id, '/') path
     , child
FROM   node parent
       LEFT JOIN edge hier
       ON parent.id = hier.parent
START WITH
    NOT EXISTS (SELECT 1 FROM edge h WHERE h.child = parent.id)
CONNECT BY
    PRIOR hier.child = parent.id

<强> Results

| ID |   NAME | ROOT_ID | LVL |   PATH |  CHILD |
|----|--------|---------|-----|--------|--------|
|  1 |    KBR |     KBR |   1 |     /1 |      4 |
...
|  1 |    KBR |     KBR |   1 |     /1 |      5 |
...

行有不同,因为它们具有不同的子边。

如果您想获得不同的行,请添加DISTINCT关键字。

查询2

SELECT DISTINCT
       parent.id id
     , parent.NAME NAME
     , connect_by_root parent.NAME root_id
     , LEVEL lvl
     , sys_connect_by_path(parent.id, '/') path
FROM   node parent
       LEFT JOIN edge hier
       ON parent.id = hier.parent
START WITH
    NOT EXISTS (SELECT 1 FROM edge h WHERE h.child = parent.id)
CONNECT BY
    PRIOR hier.child = parent.id

<强> Results

| ID |   NAME | ROOT_ID | LVL |   PATH |
|----|--------|---------|-----|--------|
|  2 |      H |       H |   1 |     /2 |
|  7 | Nach H |       H |   3 | /2/4/7 |
|  4 |  KBR H |       H |   2 |   /2/4 |
|  4 |  KBR H |     KBR |   2 |   /1/4 |
|  7 | Nach H |     KBR |   3 | /1/4/7 |
|  5 |  KBR N |     KBR |   2 |   /1/5 |
|  1 |    KBR |     KBR |   1 |     /1 |
|  3 |      N |       N |   1 |     /3 |
|  5 |  KBR N |       N |   2 |   /3/5 |
|  6 |  Dummy |   Dummy |   1 |     /6 |

答案 1 :(得分:1)

左连接查询未按预期返回结果:

SQL> SELECT *
  2  FROM
  3      node parent
  4      LEFT JOIN edge hier ON parent.id = hier.parent  ;
       ID NAME                                                                                PARENT     CHILD
--------- -------------------------------------------------------------------------------- --------- ---------
        1 KBR                                                                                      1         4
        1 KBR                                                                                      1         5
        2 H                                                                                        2         4
        3 N                                                                                        3         5
        4 KBR H                                                                                    4         7
        6 Dummy                                                                                      
        7 Nach H                                                                                     
        5 KBR N                                                                                      
8 rows selected

Parent.id应该在heir.child上加入,以使结果形状正确,可以与connect by子句一起使用

SQL> SELECT *
  2  FROM
  3      node parent
  4      LEFT JOIN edge hier ON parent.id = hier.child  ;
       ID NAME                                                                                PARENT     CHILD
--------- -------------------------------------------------------------------------------- --------- ---------
        4 KBR H                                                                                    1         4
        5 KBR N                                                                                    1         5
        4 KBR H                                                                                    2         4
        5 KBR N                                                                                    3         5
        7 Nach H                                                                                   4         7
        6 Dummy                                                                                      
        1 KBR                                                                                        
        2 H                                                                                          
        3 N                                                                                          
9 rows selected

现在,可以使用parent作为null来完成开始,并将heir.parent连接到parent.id的先前以获取正确的层次结构

SQL> column name format a20
SQL> column root_id format a10
SQL> column path format a10
SQL> 
SQL> SELECT
  2      parent.id id
  3      , parent.NAME NAME
  4      , connect_by_root parent.NAME root_id
  5      , LEVEL lvl
  6      , sys_connect_by_path(parent.id, '/') path
  7  FROM
  8      node parent
  9      LEFT JOIN edge hier ON parent.id = hier.child
 10  START WITH
 11      PARENT IS NULL
 12  CONNECT BY
 13      hier.parent = PRIOR parent.id;
       ID NAME                 ROOT_ID           LVL PATH
--------- -------------------- ---------- ---------- ----------
        1 KBR                  KBR                 1 /1
        4 KBR H                KBR                 2 /1/4
        7 Nach H               KBR                 3 /1/4/7
        5 KBR N                KBR                 2 /1/5
        2 H                    H                   1 /2
        4 KBR H                H                   2 /2/4
        7 Nach H               H                   3 /2/4/7
        3 N                    N                   1 /3
        5 KBR N                N                   2 /3/5
        6 Dummy                Dummy               1 /6
10 rows selected

SQL>