【问题标题】:Calculate the percentage of the root owned by its parents计算其父母拥有的根的百分比
【发布时间】:2026-01-11 23:40:01
【问题描述】:

简而言之,我正在尝试计算其父级拥有的树根的百分比,在树的更上方。如何仅在 SQL 中执行此操作?

这是我的(示例)架构。请注意,虽然层次结构本身非常简单,但还有一个额外的holding_id,这意味着单亲可以“拥有”其孩子的不同部分。

create table hierarchy_test ( 
       id number -- "root" ID
     , parent_id number -- Parent of ID
     , holding_id number -- The ID can be split into multiple parts
     , percent_owned number (3, 2)
     , primary key (id, parent_id, holding_id) 
        );

还有一些示例数据:

insert all 
 into hierarchy_test values (1, 2, 1, 1) 
 into hierarchy_test values (2, 3, 1, 0.25)
 into hierarchy_test values (2, 4, 1, 0.25)
 into hierarchy_test values (2, 5, 1, 0.1)
 into hierarchy_test values (2, 4, 2, 0.4)
 into hierarchy_test values (4, 5, 1, 1)
 into hierarchy_test values (5, 6, 1, 0.3)
 into hierarchy_test values (5, 7, 1, 0.2)
 into hierarchy_test values (5, 8, 1, 0.5)
select * from dual;

SQL Fiddle

以下查询返回我想要进行的计算。由于 SYS_CONNECT_BY_PATH 的性质,据我所知,它不能自行执行计算。

 select a.*, level as lvl
      , '1' || sys_connect_by_path(percent_owned, ' * ') as calc
   from hierarchy_test a
  start with id = 1
connect by nocycle prior parent_id = id

数据中存在循环关系,只是本例中没有。

目前我将使用一个非常简单的函数将calc 列中的字符串转换为数字

create or replace function some_sum ( P_Sum in varchar2 ) return number is
   l_result number;
begin  
   execute immediate 'select ' || P_Sum || ' from dual'
     into l_result;
     
   return l_result;   
end;
/

这似乎是一种荒谬的做法,我宁愿避免花费额外的时间来解析动态 SQL1

理论上,我认为,我应该可以使用 MODEL 子句来计算这个。我的问题是由树的非唯一性引起的。我尝试使用 MODEL 子句来做到这一点的尝试之一是:

select *
  from ( select a.*, level as lvl
              , '1' || sys_connect_by_path(percent_owned, ' * ') as calc
           from hierarchy_test a
          start with id = 1
        connect by nocycle prior parent_id = id
                 )
 model
 dimension by (lvl ll, id ii)
 measures (percent_owned, parent_id )
 rules upsert all ( 
   percent_owned[any, any]
   order by ll, ii  = percent_owned[cv(ll), cv(ii)] * nvl( percent_owned[cv(ll) - 1, parent_id[cv(ll), cv(ii)]], 1)
               )

可以理解的是,这失败了:

ORA-32638:模型维度中的非唯一寻址

使用UNIQUE SINGLE REFERENCE 失败的原因类似,即 ORDER BY 子句不是唯一的。

tl;博士

有没有一种简单的方法可以仅使用 SQL 来计算其父级所拥有的树的根的百分比?如果我在 MODEL 的正确轨道上,我哪里错了?

1。我还想避免 PL/SQL SQL 上下文切换。我意识到这是很短的时间,但如果不每天增加几分钟,这将很难快速完成。

【问题讨论】:

  • 您是否考虑过使用递归子查询分解而不是连接方式?
  • @jonearles:我只是想问同样的问题 :-) 我实际上尝试使用这种方法获得解决方案,但没有成功:sqlfiddle.com/#!4/c3d5d/15
  • 我已经考虑过@jonearles 但这是代码的一小部分。我还需要弄清楚树的顶部是什么以及数据是否是周期性的,这些都非常容易通过 connect-by 实现。另一个问题是速度,它明显变慢了。我可能最终别无选择......
  • 我很想玩这个,因为它看起来很有趣,但我很困惑。如果暂时没有人回答,你能用你的样本数据画一张树的图片吗?
  • 当然@gloomy.penguin,给你:i.stack.imgur.com/r9Ziu.png。不是很复杂

标签: sql oracle hierarchy oracle11gr2 connect-by


【解决方案1】:

在 11g 中,可能类似于-

SELECT a.*, LEVEL AS lvl
      ,XMLQuery( substr( sys_connect_by_path( percent_owned, '*' ), 2 ) RETURNING CONTENT).getnumberval() AS calc
   FROM hierarchy_test a
  START WITH id = 1
CONNECT BY nocycle PRIOR parent_id = id;

SQL Fiddle.

或者,根据您的'1'|| 技巧-

SELECT a.*, LEVEL AS lvl
      , XMLQuery( ('1'|| sys_connect_by_path( percent_owned, '*' )) RETURNING CONTENT).getnumberval() AS calc
   FROM hierarchy_test a
  START WITH id = 1
CONNECT BY nocycle PRIOR parent_id = id;

不幸的是,在 10g 中,XMLQuery 不能接受函数,并且总是需要一个字符串字面量来评估示例-

select XMLQuery('1*0.25' RETURNING CONTENT).getnumberval() as val 
  from dual;

工作并返回0.25,但是

select XMLQuery(substr('*1*0.25',2) RETURNING CONTENT).getnumberval() as val
   from dual;

ORA-19102: XQuery string literal expected

随着XMLQuery 自身创建内部树的额外开销,随着树上的级别数增加,查询可能会变慢。实现结果的最佳方法仍然是 PL/SQL 函数,顺便说一下,它可以在 10g 和 11g 中工作。

【讨论】:

  • 非常感谢,这绝对是美丽的,我一定会记住它的未来。不幸的是,它也很多比使用 PL/SQL 慢。我在一棵大树上以慢 4 倍的速度进行了基准测试。
  • 是的,它不是为了将表达式评估为eval,但它确实可以工作。它比较慢,因为它需要在每个connect by 结果上创建一个树,所以你拥有的级别越多,它就越慢。我也觉得 PLSQL 是一种更好的方法,但是由于您正在寻找一种更简单的 SQL 方法来执行此操作,所以我只能想到这一点。 :)
  • 事实证明您是对的,我们现在将使用它。但是……我们遇到了一些小问题。
【解决方案2】:

这值得回答;不过要注意我们是在一些特殊情况下运行的。

首先要提到的是,按照 Daniel Hilgarth 和 jonearles 在 cmets 中的说法,最好的方法是使用递归子查询分解/递归 CTE:

with temp (id, parent_id, percent_owned, calc) as (
  select a.id, a.parent_id, a.percent_owned, percent_owned as calc
    from hierarchy_test a
   where id = 1
   union all
  select a.id, a.parent_id, a.percent_owned, a.percent_owned * t.calc as calc
    from temp t
    join hierarchy_test a
      on t.parent_id = a.id
         )
select * 
  from temp

Their SQL Fiddle..

不幸的是,查询的复杂性和我们正在处理的数据的大小使得这是不可能的。如果不每次都对一些过大的表进行全扫描,就没有办法做到这一点。

这并不一定意味着我们回到了CONNECT BY。有机会批量计算层次结构。不幸的是,这也被证明是不可能的。一个小时内数据库崩溃了。三次。我们使用了将近 100GB 的 UNDO,而服务器无法应付。

这些是特殊情况;我们最多只能在几个小时内计算出数十万个层次结构。平均一个深度约为 1.5 层,总共可能有 5-10 个叶子和 8-12 个节点。但是,异常值有 90k 个节点、27 个级别和多个循环关系。离群值还不够罕见。

所以,CONNECT BY。针对问题中建议的 PL/SQL EXECUTE IMMEDIATEAnnjawn's solution 进行基准测试表明,对于高于平均水平的树 XMLQuery() 的速度要慢 4 倍。太好了,有答案;没有其他选择;就这样吧。

不是。

因为我们要计算如此多的层次结构和如此多的节点,我们最终会因@中数十万个数学函数的不断硬解析而导致库缓存引脚锁定的等待时间过长987654332@.

对此没有明显的反应,所以回到 Annjawn 的解决方案,它最终快了 3 倍! 图书馆缓存针锁完全消失了,我们又回到了正轨。

不是。

不幸的是,当您组合CONNECT BYXMLQuery() 和 DBMS_SCHEDULER 时,似乎会出现 11.2 中的 Oracle 错误。在某些情况下,通常在较大的层次结构中,它会泄漏大量内存。丢失了数据库找到那个的服务器。已向 Oracle 提出报告,我们正在 12c 中进行测试;尽管内存泄漏的表现较少,但它们仍然出现,因此 12c 已用完。

解决方案?将 XMLQuery() 包装在 PL/SQL 函数中。内存泄漏已解决,不幸的是,这导致了该函数的大量争用,我们开始获得多小时 Library cache: mutex x 等待。查询x$kglob 确认它是XMLTYPE被锤打。

Andrey Nikolaev recommends 要么改变系统;当其他一切正常时不要这样做,或者使用DBMS_POOL.MARKHOT 过程告诉Oracle 你将经常访问这个对象。随便看看,这可能已经解决了这个问题,然而,大约 10 分钟后,并且经历了 Oracle 拥有的所有锁,我们最终有 5 个进程争用 CPU。显然不够用(测试盒上有 54GB 和 24 个核心)...

然后我们开始得到 Cursor pin: s 等待。 Burleson recommends 更多隐藏参数融资,Jonathan Lewis suggests 这取决于 SGA 调整大小。由于 DB 使用自动 SGA 大小调整,我们尝试逐渐增加 共享池,最高可达 30GB,并且只恢复了老朋友 Library cache: mutex x 等等。 p>

那么,解决方案是什么?谁知道这是诚实的答案,但到目前为止,Java 存储过程工作得非常出色,没有内存泄漏,没有等待,而且比其他任何事情都快得多。

我敢肯定还有更多……如果有人有任何想法,我真的很想让MODEL 子句起作用?

附:我不能为这一切声称功劳;大约 3 个人的工作让我们走到了这个阶段……

【讨论】: