13

简而言之,我试图计算其父母拥有的树根的百分比,进一步向上。如何仅在 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小提琴

以下查询返回我想要进行的计算。由于 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;
/

这似乎是一种荒谬的方式,我宁愿避免解析动态 SQL 1所花费的额外时间。

从理论上讲,我认为,我应该能够使用 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 上下文切换。我意识到这是很短的时间,但如果不每天增加几分钟,这将很难快速完成。

4

2 回答 2

6

在 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 小提琴

或者,根据您的'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 中工作。

于 2012-12-10T20:41:07.833 回答
4

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

首先要提到的是,按照 Daniel Hilgarth 和 jonearles 在评论中的说法,最好的方法是使用递归子查询分解/递归 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

他们的 SQL Fiddle。.

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

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

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

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

不是。

因为我们要计算如此多的层次结构和如此多的节点,所以我们最终会从库缓存 pin 锁定中获得过长的等待,这是由于对EXECUTE IMMEDIATE.

对此没有明显的反应,所以回到 Annjawn 的解决方案,它最终快了 3 倍!图书馆缓存销锁完全消失,我们又回到了笔直狭窄的道路上。

不是。

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

解决方案?将 包装XMLQuery()在 PL/SQL 函数中。内存泄漏解决了,不幸的是,这导致了这个函数的大量争用,我们开始获得多小时的库缓存:互斥锁 x等待.. 查询x$kglob确认它正在XMLTYPE被锤击。

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

然后我们开始获取Cursor pin: s等待。Burleson 建议使用更多隐藏参数融资,Jonathan Lewis 建议这取决于 SGA 调整大小。由于数据库正在使用自动 SGA 大小调整,我们尝试逐渐增加共享池,最高可达 30GB,并且只恢复了老朋友库缓存:mutex x wait。

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

MODEL我敢肯定还有更多……如果有人有任何想法,我真的很想让该条款生效吗?

PS我不能为这一切申请功劳;大约 3 个人的工作让我们走到了这个阶段……

于 2013-07-29T20:44:54.700 回答