【问题标题】:Oracle SQL: How to get the nth node between the root and any node in a tree (Optimizing)Oracle SQL:如何获取树中根节点和任意节点之间的第 n 个节点(优化)
【发布时间】:2021-05-31 11:25:15
【问题描述】:

我正在尝试从给定节点开始从根中查找第 N 个节点。如果给定节点小于或等于从根开始的第 N 个节点,则返回给定节点。我的查询有效,但速度很慢,我对此不满意。关于如何重构它以提高性能的任何想法?

test_data
---------
unique_id | lookup_acct_code 
'1'         'leaf-1'
'2'         'stem-2'
'3'         'branch-1'
'4'         'trunk-2'
'5'         'root-1'

linked_accounts
---------------
acct_code | parent_code
'leaf-1'    'stem-1'
'stem-1'    'twig-1'
'twig-1'    'stick-1'
'stick-1'   'branch-1'
'branch-1'  'trunk-1'
'trunk-1'   'root-1'
'root-1'    NULL
'leaf-2'    'stem-2'
'stem-2'    'twig-2'
'twig-2'    'stick-2'
'stick-2'   'branch-2'
'branch-2'  'trunk-2'
'trunk-2'   'root-2'
'root-2'    NULL

SELECT unique_id,
 (SELECT acct_code
  FROM (SELECT acct_code, ROW_NUMBER() OVER (ORDER BY level desc) rn
        FROM linked_accounts
        CONNECT BY acct_code = PRIOR parent_code
        START WITH acct_code = lookup_acct_code)
  WHERE rn <= 3
  ORDER BY rn DESC
  FETCH FIRST 1 ROWS ONLY)    
FROM test_data;

/* Correct output
1   branch-1
2   branch-2
3   branch-1
4   trunk-2
5   root-1
*/

【问题讨论】:

  • 什么是“根”?您的数据表明这是 parent_codeNULL 但您的查询似乎没有检查任何 NULL 值,也没有寻找以 root 开头的帐户,那么您怎么知道您找到了根?
  • 我无法理解您的要求 - 它可能有助于绘制简单的图表。我猜想最快的递归查询将使用递归公用表表达式,因为这样 CTE 可以在到达第 N 个节点时停止遍历树。
  • 我不明白。例如“从给定节点开始的根节点的第 N 个节点”。那是什么意思? 哪个根?您的样本有两个。它是从根开始的第 N 个,还是从给定节点开始的第 N 个?等等。请阅读您的问题并 - 老实说 - 看看您自己是否能理解。如果你不能,我们也不能。另外:你所有的树都是linear(意思是,没有父母有超过一个孩子)吗?

标签: sql oracle query-optimization hierarchy


【解决方案1】:

您可以首先从根生成直到第 nth 节点的值(使用递归子查询因式分解子句而不是分层查询),然后加入而不是使用相关子查询:

WITH from_roots ( acct_code, depth, nth_from_root ) AS (
  SELECT acct_code,
         1,
         acct_code
  FROM   linked_accounts
  WHERE  parent_code IS NULL
UNION ALL
  SELECT l.acct_code,
         depth + 1,
         CASE
         WHEN depth >= 3
         THEN nth_from_root
         ELSE l.acct_code
         END
  FROM   linked_accounts l
         INNER JOIN from_roots f
         ON ( f.acct_code = l.parent_code )
)
SELECT unique_id, nth_from_root
FROM   from_roots f
       INNER JOIN test_data d
       ON ( d.lookup_acct_code = f.acct_code )

或者,您可以调整您的查询以使用MAX(LEVEL) OVER () - LEVEL + 1 直接计算深度:

SELECT unique_id,
       ( SELECT acct_code
         FROM   (
           SELECT acct_code, MAX(LEVEL) OVER () - LEVEL + 1 AS depth
           FROM   linked_accounts
           CONNECT BY acct_code = PRIOR parent_code
           START WITH acct_code = lookup_acct_code
         )
         WHERE depth <= 3
         ORDER BY depth DESC
         FETCH FIRST 1 ROW ONLY
       ) AS nth_node
FROM   test_data;

对于您的示例数据:

CREATE TABLE test_data ( unique_id, lookup_acct_code ) AS
SELECT '1',         'leaf-1'   FROM DUAL UNION ALL
SELECT '2',         'stem-2'   FROM DUAL UNION ALL
SELECT '3',         'branch-1' FROM DUAL UNION ALL
SELECT '4',         'trunk-2'  FROM DUAL UNION ALL
SELECT '5',         'root-1'   FROM DUAL;

CREATE TABLE linked_accounts ( acct_code, parent_code ) AS
SELECT 'leaf-1',    'stem-1'   FROM DUAL UNION ALL
SELECT 'stem-1',    'twig-1'   FROM DUAL UNION ALL
SELECT 'twig-1',    'stick-1'  FROM DUAL UNION ALL
SELECT 'stick-1',   'branch-1' FROM DUAL UNION ALL
SELECT 'branch-1',  'trunk-1'  FROM DUAL UNION ALL
SELECT 'trunk-1',   'root-1'   FROM DUAL UNION ALL
SELECT 'root-1',    NULL       FROM DUAL UNION ALL
SELECT 'leaf-2',    'stem-2'   FROM DUAL UNION ALL
SELECT 'stem-2',    'twig-2'   FROM DUAL UNION ALL
SELECT 'twig-2',    'stick-2'  FROM DUAL UNION ALL
SELECT 'stick-2',   'branch-2' FROM DUAL UNION ALL
SELECT 'branch-2',  'trunk-2'  FROM DUAL UNION ALL
SELECT 'trunk-2',   'root-2'   FROM DUAL UNION ALL
SELECT 'root-2',    NULL       FROM DUAL;

两个输出:

UNIQUE_ID | NTH_FROM_ROOT :-------- | :------------ 1 |分支 1 2 |分支 2 3 |分支 1 4 |主干 2 5 |根-1

您需要在更大的数据集上分析所有解决方案,以查看哪个解决方案的性能更高。

db小提琴here

【讨论】:

    【解决方案2】:

    您可以尝试通过使用CONNECT_BY_ROOT 函数来避免标量子查询,以获取您开始使用的unique_id。并根据树构建后找到的最大级别进行相同的过滤。试试这个:

    with hier as (
      select
        /*Find unique ID of subtree*/
        connect_by_root(f.unique_id) as unique_id,
        connect_by_root(la.acct_code) as start_node,
        la.acct_code,
        /*Find max depth of subtree*/
        max(level) over(partition by connect_by_root(f.unique_id)) as max_lvl,
        /*Find current reversed depth of node*/
        level as lvl
      from linked_accounts la
          left join test_data f
            on la.acct_code = f.lookup_acct_code
      start with f.unique_id is not null
      connect by prior la.parent_code = la.acct_code
    )
    select
      unique_id,
      case max_lvl
        when lvl
        /*Select our start node for short subtree*/
        then start_node
        /*And node al level 3 otherwise*/
        else acct_code
      end as node_name
    from hier
    /*Select the node on level 3 from the root*/
    where max_lvl - lvl = 2
      /*Or the last node in subtree for the depth < 3*/
      or (max_lvl <= 2 and lvl = max_lvl)
    order by 1 asc
    
    UNIQUE_ID | NODE_NAME :-------- | :-------- 1 |分支 1 2 |分支 2 3 |分支 1 4 |主干 2 5 |根-1

    db小提琴here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多