Ayuda a calcular la suma compleja en el conjunto de datos jerárquicos
-
24-10-2019 - |
Pregunta
Tengo un problema SQL interesante. Tengo una tabla jerárquica de piezas que hacen una factura de material. similar a ésto:
ASSEMBLY
---------
parent_part_id
part_id
quantity
Tengo la jerarquía de esta estructura con una consulta como esta:
SELECT level, part_id, quantity
from assembly
start with parent_part_id = 1
connect by parent_part_id = prior part_id;
La salida puede verse así:
level part_id quantity
----- ------- ---------
1 2 2
2 3 10
1 4 2
2 5 1
3 3 5
Hasta ahora, todo bien.
La pregunta es esta: ¿cómo calculo el número total de cada parte requerida para hacer el ensamblaje de nivel superior (Parte 1)?
Agrupar este resultado establecido por parte y sumar la cantidad no es correcta, ya que la cantidad debe multiplicarse por la cantidad de la parte inmediatamente por encima de la parte actual en la jerarquía, recursivamente en el árbol.
Estoy pensando que esta es una función de retraso, pero tener problemas para visualizarla.
Editar: resultados esperados:
part_id quantity
------- --------
2 2
3 30
4 2
5 2
más edición: obtengo resultados interesantes con esta consulta
SELECT rownum, level lvl, part_id, quantity, unit_of_measure
, connect_by_isleaf || sys_connect_by_path(quantity,'*') math
from assembly
start with parent_part_id = 1
connect by parent_part_id = prior part_id
La columna matemática devuelve una representación de cadena del cálculo que quiero realizar :) Por ejemplo, puede decir:
1*1*2*10
O algo similar y apropiado ... quizás hacer una función para analizar esto y devolver el resultado resolverá el problema. ¿Alguien piensa que esto es indignante?
Solución
En Oracle 11 R2 es posible con un common table expression
:
Los datos de prueba:
-- drop table assembly;
create table assembly (
part_id number,
parent_part_id number,
quantity number
);
insert into assembly values (2, 1, 2);
insert into assembly values (3, 2, 10);
insert into assembly values (4, 1, 2);
insert into assembly values (5, 4, 1);
insert into assembly values (3, 5, 5);
La declaración de selección:
select
part_id,
sum(quantity_used) as quantity
from (
with assembly_hier (lvl, part_id, quantity, quantity_used) as (
select
1 lvl,
part_id,
quantity ,
quantity quantity_used
from
assembly
where
parent_part_id = 1
union all
select
assembly_hier.lvl + 1 lvl,
assembly .part_id,
assembly .quantity,
assembly_hier.quantity_used * assembly.quantity quantity_used
from
assembly_hier, assembly
where
assembly_hier.part_id = assembly.parent_part_id
)
select * from assembly_hier
)
group by part_id
order by part_id;
Editar Antes de Ora11R2, podría funcionar con un model clause
:
select
part_id,
sum(quantity) quantity
from (
select
lvl
parent_part_id,
part_id,
quantity
from (
select
lvl,
parent_part_id,
part_id,
quantity
from (
select
rownum r,
level lvl,
parent_part_id,
part_id,
quantity
from
assembly
start with parent_part_id = 1
connect by parent_part_id = prior part_id
)
)
model
dimension by (lvl, part_id)
measures (quantity, parent_part_id)
rules upsert (
quantity[ any, any ] order by lvl, part_id = quantity[cv(lvl) , cv(part_id)] *
nvl( quantity[cv(lvl)-1, parent_part_id[cv(lvl), cv(part_id)] ], 1)
)
)
group by part_id
order by part_id;
Editar II Otra posibilidad sería sumar los logaritmos de cantidad y luego tomar el exponente de la suma:
select
part_id,
sum(quantity) quantity
from (
select
part_id,
exp(sum(quantity_ln) over (partition by new_start order by r)) quantity
from (
select
r,
lvl,
part_id,
quantity_ln,
sum(new_start) over(order by r) new_start
from (
select
rownum r,
level lvl,
part_id,
ln(quantity) quantity_ln,
nvl(lag(connect_by_isleaf,1) over (order by rownum),0) new_start
from assembly
start with parent_part_id = 1
connect by parent_part_id = prior part_id
)
)
)
group by part_id
order by part_id
;
Otros consejos
Terminé aquí: esto funciona en Oracle 10 y 11, el Connect_By_isleaf se puede usar para ajustar la lógica si desea sumar solo las hojas o todos los nodos.
select part_id, new_rec.quantity*sum(math_calc( math,2)) m, unit_of_measure
from ( SELECT rownum, level lvl, part_id, quantity, unit_of_measure
, connect_by_isleaf || sys_connect_by_path(quantity,'*') math
from assembly
start with parent_part_id = new_rec.part_id
connect by parent_part_id = prior part_id ) p
group by part_id, unit_of_measure