【问题标题】:How to get the path of an hierarchy table如何获取层次结构表的路径
【发布时间】:2016-05-01 08:11:51
【问题描述】:

我一直在为如何处理这种情况而苦苦挣扎:

我的表格结构如下:

Family_code  |   Parent_Family_Code  | ....
    1                   2
    2                   4
    3                   6
    4                   3
   ......................

当用户搜索特定的家庭代码时,我需要返回整个路径(最多 10 级),例如对于 family_code = 1,我需要:

Family_code | parent_1 | p_2 | p_3 | p_4 | p_5 | .....
      1          2        4     3     6     null    null.....

我知道我可以使用sys_connect_by_path(),它会给我带来预期的结果,但它是一个字符串,而不是单独的列,这是我宁愿避免的。

这也可以通过对同一个表的 10 个左连接来完成,或者使用 LEAD()/LAG() 函数来完成,这些函数将包含很多子查询,并且会产生混乱且不可读的查询,但话又说回来,这会更多应该很重,我需要尽可能简化它。

我想出了一个使用substr()函数的解决方案(代码的长度总是varchar2(3)):

SELECT s.family_code,
 s.parent_family_code_1,
 s.parent_family_code_2,
 CASE WHEN length(s.family_path) - (4 * 3 + 2) > 0 THEN substr(s.family_path, length(s.family_path) - (4 * 3 + 2), 3) ELSE NULL END as parent_family_code_3,
 CASE WHEN length(s.family_path) - (4 * 4 + 2) > 0 THEN substr(s.family_path, length(s.family_path) - (4 * 4 + 2), 3) ELSE NULL END as parent_family_code_4,
 CASE WHEN length(s.family_path) - (4 * 5 + 2) > 0 THEN substr(s.family_path, length(s.family_path) - (4 * 5 + 2), 3) ELSE NULL END as parent_family_code_5,
 CASE WHEN length(s.family_path) - (4 * 6 + 2) > 0 THEN substr(s.family_path, length(s.family_path) - (4 * 6 + 2), 3) ELSE NULL END as parent_family_code_6,
 CASE WHEN length(s.family_path) - (4 * 7 + 2) > 0 THEN substr(s.family_path, length(s.family_path) - (4 * 7 + 2), 3) ELSE NULL END as parent_family_code_7,
 CASE WHEN length(s.family_path) - (4 * 8 + 2) > 0 THEN substr(s.family_path, length(s.family_path) - (4 * 8 + 2), 3) ELSE NULL END as parent_family_code_8,
 CASE WHEN length(s.family_path) - (4 * 9 + 2) > 0 THEN substr(s.family_path, length(s.family_path) - (4 * 9 + 2), 3) ELSE NULL END as parent_family_code_9,
 CASE WHEN length(s.family_path) - (4 * 10 + 2) > 0 THEN substr(s.family_path, length(s.family_path) - (4 * 10 + 2), 3) ELSE NULL END as parent_family_code_10
  FROM (SELECT t.family_code,
               t.parent_family_code as parent_family_code_1,
               prior t.parent_family_code as parent_family_code_2,
               sys_connect_by_path(t.family_code, ',') as family_path
          FROM table t
        connect by prior t.family_code = t.parent_family_code) s

但我想要一个不使用子字符串的解决方案,因为当其他开发人员接触它时,对其进行任何维护将变得更加困难 . 所以基本上我的问题是 - 如何在不使用子字符串的情况下将整个路径选择为不同的列?

【问题讨论】:

  • 您在问题中编辑的代码不起作用 - 如果您还 SELECT family_path 那么您会看到 (a) 它没有获取整个路径并且 (b) 路径不是全部由子字符串处理,因为您有可变长度的字符串并使用固定长度的条件。
  • 如果你想要整个路径,那么你可以使用sys_connect_by_path(t.parent_family_code, ',') || ',' || t.family_code as family_path,但它仍然不能解决case条件下可变长度数据和固定长度的第二个问题。

标签: sql oracle hierarchy


【解决方案1】:

Oracle 设置

CREATE TABLE table_name ( Family_code, Parent_Family_Code ) AS
SELECT  1,    2 FROM DUAL UNION ALL
SELECT  2,    4 FROM DUAL UNION ALL
SELECT  3,    6 FROM DUAL UNION ALL
SELECT  6, NULL FROM DUAL UNION ALL
SELECT  4,    3 FROM DUAL UNION ALL
SELECT  4,    5 FROM DUAL UNION ALL
SELECT  5, NULL FROM DUAL UNION ALL
SELECT  8,    7 FROM DUAL UNION ALL
SELECT  7,    9 FROM DUAL UNION ALL
SELECT  9,   10 FROM DUAL UNION ALL
SELECT 10,   11 FROM DUAL UNION ALL
SELECT 11, NULL FROM DUAL;

查询

SELECT TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth, NULL, 1 ) ) AS family_code,
       CASE WHEN max_depth >  1 THEN TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth -  1, NULL, 1 ) ) END AS p1,
       CASE WHEN max_depth >  2 THEN TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth -  2, NULL, 1 ) ) END AS p2,
       CASE WHEN max_depth >  3 THEN TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth -  3, NULL, 1 ) ) END AS p3,
       CASE WHEN max_depth >  4 THEN TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth -  4, NULL, 1 ) ) END AS p4,
       CASE WHEN max_depth >  5 THEN TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth -  5, NULL, 1 ) ) END AS p5,
       CASE WHEN max_depth >  6 THEN TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth -  6, NULL, 1 ) ) END AS p6,
       CASE WHEN max_depth >  7 THEN TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth -  7, NULL, 1 ) ) END AS p7,
       CASE WHEN max_depth >  8 THEN TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth -  8, NULL, 1 ) ) END AS p8,
       CASE WHEN max_depth >  9 THEN TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth -  9, NULL, 1 ) ) END AS p9,
       CASE WHEN max_depth > 10 THEN TO_NUMBER( REGEXP_SUBSTR( path, '/(\d+)', 1, max_depth - 10, NULL, 1 ) ) END AS p10
FROM   (
  SELECT SYS_CONNECT_BY_PATH( Family_code, '/' ) AS path,
         LEVEL AS max_depth
  FROM   table_name
  WHERE  CONNECT_BY_ISLEAF = 1
  CONNECT BY PRIOR Family_Code = Parent_Family_Code
  START WITH Parent_Family_Code IS NULL
);

输出

FAMILY_CODE         P1         P2         P3         P4         P5         P6         P7         P8         P9        P10
----------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
          1          2          4          5                                                                              
          1          2          4          3          6                                                                   
          8          7          9         10         11                                                                   

【讨论】:

  • 谢谢,我已经使用 substr 做了类似的事情,但我一直想知道是否可以不弄乱字符串,也许是一些分层的内置函数,而且 connect_by_isleaf 是不必要的因为我需要每个人的结果,而不仅仅是叶子
【解决方案2】:

使用PIVOT 子句对@MT0 答案稍作修改的查询。

SELECT * 
FROM (
    select connect_by_root( family_code ) as Family_code, 
           'P_' || level lev_el,  
           parent_family_code
    from table_name t
    start with not exists(
        select 1 from table_name t1
        where t.family_code = t1.parent_family_code )
    connect by prior parent_family_code =  family_code
)
PIVOT (
  max( parent_family_code ) 
  FOR (lev_el) IN ( 
       'P_1', 'P_2', 'P_3', 'P_4', 'P_5', 'P_6','P_7', 'P_8','P_9','P_10' ,
       'P_11', 'P_12', 'P_13', 'P_14', 'P_15', 'P_16','P_17', 'P_18','P_19','P_20',
       'P_21', 'P_22', 'P_23', 'P_24', 'P_25', 'P_26','P_27', 'P_28','P_29','P_30' 
       /* add more "levels" here if required */
)
);

来自@MT0答案的样本数据查询结果(@MT0,感谢您提供样本数据):

    FAMILY_CODE      'P_1'      'P_2'      'P_3'      'P_4'      'P_5'      'P_6'      'P_7'      'P_8'      'P_9'     'P_10'     'P_11'     'P_12'     'P_13'     'P_14'     'P_15'     'P_16'     'P_17'     'P_18'     'P_19'     'P_20'     'P_21'     'P_22'     'P_23'     'P_24'     'P_25'     'P_26'     'P_27'     'P_28'     'P_29'     'P_30'
--------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
              1          2          4          5          6                                                                                                                                                                                                                                                                                              
              8          7          9         10         11                                                                                                                                                                                                                                                                                              

【讨论】:

  • 谢谢!这就是我一直在寻找的,没有想到一个枢纽解决方案。
  • 您似乎使用了我的问题中的示例数据(删除了 NULL 行,因为您使用的是 START WITH NOT EXISTS) - 它只返回两行 - 合并以 @987654326 开头的两行@ 并且该行不正确 1,2,4,5,6 - 6 的父级是 3 而不是 5
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多