Подтвердить что ты не робот

Рассчитать процент корня, принадлежащего его родителям.

В упрощенных выражениях я пытаюсь рассчитать процент корня дерева, принадлежащего его родителям, далее по дереву. Как я могу сделать это только в 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;
/

Кажется, это смешной способ обойти это, и я предпочел бы избежать дополнительного времени, которое будет принято при анализе динамического 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;DR

Есть ли простой способ рассчитать процент корня дерева, принадлежащего его родителям, используя только SQL? Если я нахожусь на правильном пути с MODEL, где я ошибаюсь?

<суб > 1. Я также хотел бы избежать контекстного переключателя PL/SQL SQL. Я понимаю, что это крошечное количество времени, но это будет достаточно сложно сделать быстро, не добавляя еще несколько минут в день.

4b9b3361

Ответ 1

Это заслуживает ответа; хотя будьте осторожны, мы работаем в нескольких особых обстоятельствах.

В первую очередь следует упомянуть, что наилучшим способом сделать это является рекурсивный факторинг подзапроса/рекурсивный CTE в соответствии с Даниэлем Хильгарт и jonearles в комментариях:

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..

К сожалению, сложность запроса и размер данных, с которыми мы работаем, таковы, что это оказалось невозможным. Невозможно было сделать это без полного сканирования некоторых чрезмерно больших таблиц каждый раз.

Это не обязательно означает, что мы вернулись к CONNECT BY. Существует возможность рассчитать иерархии навалом. К сожалению, это оказалось невозможным; час в базе данных разбился. Трижды. Мы использовали почти 100 ГБ UNDO, и сервер просто не мог справиться.

Это особые обстоятельства; мы должны вычислить сотни тысяч иерархий за несколько часов, максимум. Среднее значение составляет около 1,5 уровней глубиной, возможно, 5-10 листьев и всего 8-12 узлов. Однако выбросы имеют 90k узлов, 27 уровней и множественные циклические отношения. Выбросы не встречаются достаточно редко.

Итак, CONNECT BY. Бенчмаркинг решение Annjawn против PL/SQL EXECUTE IMMEDIATE, предложенное в вопросе, показало, что для выше среднего дерева XMLQuery() было в 4 раза медленнее. Отлично, был ответ; нет другого варианта; оставьте это на этом.

нет.

Поскольку мы вычисляем так много иерархий с таким количеством узлов, мы закончили тем, что слишком долго ждали блокировки буфера библиотеки, вызванные постоянным жестким анализом сотен тысяч математических функций в EXECUTE IMMEDIATE.

Нет очевидного ответа на это, поэтому, возвращаясь слишком назад, решение Annjawn заканчивается в 3 раза быстрее! Блокировка булавки кеш-памяти полностью исчезает, и мы снова находимся на прямой и узкой.

нет.

К сожалению, похоже, что ошибка Oracle в 11.2 появляется, когда вы объединяете CONNECT BY, XMLQuery() и DBMS_SCHEDULER. В определенных обстоятельствах, обычно в больших иерархиях, он течет огромные объемы памяти. Потерял базу данных и сервер, обнаружив, что один из них. Был подготовлен отчет с Oracle, и мы тестируем его в 12c; хотя утечки памяти показывают меньше, они все еще появляются, так что 12c не работает.

Решение? Оберните XMLQuery() функцией PL/SQL. Утечка памяти была решена, к сожалению, это вызвало большой резонанс в этой функции, и мы начали получать многочасовой кеш библиотеки: mutex x ждет. Запрос x$kglob подтвердил, что он был XMLTYPE, который забивался.

Андрей Николаев рекомендует изменять систему; скорее не делайте этого, когда все остальное работает нормально, или используя процедуру DBMS_POOL.MARKHOT, чтобы сообщить Oracle, что вы будете получать доступ к этому объекту много, Для случайного взгляда это, возможно, решило проблему, однако, примерно через 10 минут, и, пройдя то, что, казалось, было каждой блокировкой, имеющейся у Oracle, мы закончили с 5 процессами, соперничающими за процессор. По-видимому, не было достаточно (54 ГБ и 24 ядра на тестовой коробке)...

Затем мы начали получать курсорный вывод: s ждет. Берлсон рекомендует более скрытый параметр finangling, Джонатан Льюис предлагает что он уменьшился до SGA. Поскольку БД использовала автоматическую калибровку SGA, мы старались постепенно увеличивать общий пул, до 30 ГБ и только возвращали старого друга в кеш библиотеки: mutex x wait.

Итак, какое решение? Кто знает, это честный ответ, но хранимая процедура Java работает блестяще до сих пор, утечек памяти, нет ожиданий и значительно быстрее, чем все остальное.

Я уверен, что там больше... и мне очень хотелось бы заставить предложение MODEL работать, если у кого есть какие-то идеи?

P.S. Я не могу претендовать на все это; это работа около 3 человек, чтобы довести нас до этой стадии...

Ответ 2

В 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.