Hi
It may not be the most elegant solution but....works!

with recursive pizza (name, step, ingredient, quantity, unit, rel_qty,
path, weight)
as (
        select
                name, step, ingredient, quantity, unit
        ,       quantity::numeric(10,2)
        ,       step::text
        ,       case when unit = 'g' then quantity::numeric(10,2) else null
end
          from recipe
         where name = 'pizza'
        union all
        select
                recipe.name, recipe.step, recipe.ingredient,
recipe.quantity, recipe.unit
        ,       (pizza.rel_qty * recipe.quantity)::numeric(10,2)
        ,       pizza.path || '.' || recipe.step
        ,       case when recipe.unit = 'g' then (pizza.rel_qty *
recipe.quantity)::numeric(10,2) else null end
          from pizza
          join recipe on (recipe.name = pizza.ingredient)
), parcial_weight as
(
select a.path,sum(b.weight) as sum_weight
from pizza as a cross join pizza as b
where b.path ilike a.path || '%'
group by 1
order by path

)
select a.path, a.ingredient, a.quantity, a.rel_qty, a.unit,
a.weight,b.sum_weight as partial_ weigh,sum(a.weight) over() as total_weight
  from pizza as a
  left join parcial_weight as b on a.path=b.path
 order by a.path;



 path  |  ingredient  | quantity | rel_qty | unit  | weight |
partial_weight | total_weight
-------+--------------+----------+---------+-------+--------+------------+--------------
 1     | tomato sauce |     1.00 |    1.00 | pcs   |        |     113.00 |
     315.50
 1.1   | tomato       |   100.00 |  100.00 | g     | 100.00 |     100.00 |
     315.50
 1.2   | basil        |    10.00 |   10.00 | g     |  10.00 |      10.00 |
     315.50
 1.3   | salt         |     3.00 |    3.00 | g     |   3.00 |       3.00 |
     315.50
 2     | pizza bottom |     1.00 |    1.00 | pcs   |        |     202.50 |
     315.50
 2.2   | dough        |     1.00 |    1.00 | pcs   |        |     200.00 |
     315.50
 2.2.1 | flour        |   150.00 |  150.00 | g     | 150.00 |     150.00 |
     315.50
 2.2.2 | water        |    50.00 |   50.00 | g     |  50.00 |      50.00 |
     315.50
 2.2.3 | salt         |     1.00 |    1.00 | pinch |        |            |
     315.50
 2.3   | carbon       |     2.50 |    2.50 | g     |   2.50 |       2.50 |
     315.50
(10 rows)




El mié., 20 de jun. de 2018 a la(s) 10:54, Alban Hertroys (
haram...@gmail.com) escribió:

> On 19 June 2018 at 21:14, Hellmuth Vargas <hiv...@gmail.com> wrote:
> >
> > Hi
> >
> > with partial sum:
> >
> > with recursive pizza (name, step, ingredient, quantity, unit, rel_qty,
> path,
> > weight)
> > as (
> >         select
> >                 name, step, ingredient, quantity, unit
> >         ,       quantity::numeric(10,2)
> >         ,       step::text
> >         ,       case when unit = 'g' then quantity::numeric(10,2) else
> null
> > end
> >           from recipe
> >          where name = 'pizza'
> >         union all
> >         select
> >                 recipe.name, recipe.step, recipe.ingredient,
> > recipe.quantity, recipe.unit
> >         ,       (pizza.rel_qty * recipe.quantity)::numeric(10,2)
> >         ,       pizza.path || '.' || recipe.step
> >         ,       case when recipe.unit = 'g' then (pizza.rel_qty *
> > recipe.quantity)::numeric(10,2) else null end
> >           from pizza
> >           join recipe on (recipe.name = pizza.ingredient)
> > )
> > select path, ingredient, quantity, rel_qty, unit, weight,sum(weight)
> > over(partition by split_part(path,'.',1)) as parcial_weight, sum(weight)
> > over() as total_weight
> >   from pizza
> >  order by path;
> >
> >  path  |  ingredient  | quantity | rel_qty | unit  | weight |
> parcial_weight
> > | total_weight
> >
> -------+--------------+----------+---------+-------+--------+----------------+--------------
> >  1     | tomato sauce |     1.00 |    1.00 | pcs   |        |
>  113.00
> > |       313.00
> >  1.1   | tomato       |   100.00 |  100.00 | g     | 100.00 |
>  113.00
> > |       313.00
> >  1.2   | basil        |    10.00 |   10.00 | g     |  10.00 |
>  113.00
> > |       313.00
> >  1.3   | salt         |     3.00 |    3.00 | g     |   3.00 |
>  113.00
> > |       313.00
> >  2     | pizza bottom |     1.00 |    1.00 | pcs   |        |
>  200.00
> > |       313.00
> >  2.2   | dough        |     1.00 |    1.00 | pcs   |        |
>  200.00
> > |       313.00
> >  2.2.1 | flour        |   150.00 |  150.00 | g     | 150.00 |
>  200.00
> > |       313.00
> >  2.2.2 | water        |    50.00 |   50.00 | g     |  50.00 |
>  200.00
> > |       313.00
> >  2.2.3 | salt         |     1.00 |    1.00 | pinch |        |
>  200.00
> > |       313.00
> > (9 rows)
>
> I just realized that this solution contains a problem. It only adds
> weights at the top-level of the ingredients.
> That works for this particular example, but not if, for example, a
> 'pizza bottom' contains 200.00g of 'dough' and 2.50g of 'carbon'.
>
> The correct values in that case would be:
> 2: Pizza Bottom: 150 + 50 + 2.50 = 202.50g
> 2.2: Dough: 150 + 50 = 200 g
> 2.2.1: flour: 150 g
> 2.2.2: water: 50 g
> 2.2.3: salt: (null) g
> 2.3: Carbon: 2.50 g
>
> What probably would work is to keep the path in an array and track the
> (cumulative) sum at each depth in the path in another array. After
> that, we can take the MAX of each array element using a similar window
> function as in the original post, I think. The level of the tree would
> then be the index into the array.
>
> ...Let's look at that tomorrow, before my head explodes ;)
>
> > El mar., 19 de jun. de 2018 a la(s) 11:49, Hellmuth Vargas
> > (hiv...@gmail.com) escribió:
> >>
> >> Hi
> >>
> >> with recursive pizza (name, step, ingredient, quantity, unit, rel_qty,
> >> path, weight)
> >> as (
> >>         select
> >>                 name, step, ingredient, quantity, unit
> >>         ,       quantity::numeric(10,2)
> >>         ,       step::text
> >>         ,       case when unit = 'g' then quantity::numeric(10,2) else
> >> null end
> >>           from recipe
> >>          where name = 'pizza'
> >>         union all
> >>         select
> >>                 recipe.name, recipe.step, recipe.ingredient,
> >> recipe.quantity, recipe.unit
> >>         ,       (pizza.rel_qty * recipe.quantity)::numeric(10,2)
> >>         ,       pizza.path || '.' || recipe.step
> >>         ,       case when recipe.unit = 'g' then (pizza.rel_qty *
> >> recipe.quantity)::numeric(10,2) else null end
> >>           from pizza
> >>           join recipe on (recipe.name = pizza.ingredient)
> >> )
> >> select path, ingredient, quantity, rel_qty, unit, weight, sum(weight)
> >> over() as total_weight
> >>   from pizza
> >>  order by path;
> >>
> >>  path  |  ingredient  | quantity | rel_qty | unit  | weight |
> total_weight
> >>
> >>
> -------+--------------+----------+---------+-------+--------+--------------
> >>  1     | tomato sauce |     1.00 |    1.00 | pcs   |        |
>  313.00
> >>  1.1   | tomato       |   100.00 |  100.00 | g     | 100.00 |
>  313.00
> >>  1.2   | basil        |    10.00 |   10.00 | g     |  10.00 |
>  313.00
> >>  1.3   | salt         |     3.00 |    3.00 | g     |   3.00 |
>  313.00
> >>  2     | pizza bottom |     1.00 |    1.00 | pcs   |        |
>  313.00
> >>  2.2   | dough        |     1.00 |    1.00 | pcs   |        |
>  313.00
> >>  2.2.1 | flour        |   150.00 |  150.00 | g     | 150.00 |
>  313.00
> >>  2.2.2 | water        |    50.00 |   50.00 | g     |  50.00 |
>  313.00
> >>  2.2.3 | salt         |     1.00 |    1.00 | pinch |        |
>  313.00
> >> (9 rows)
> >>
> >>
> >>
> >>
> >>
> >> El mar., 19 de jun. de 2018 a la(s) 08:39, Alban Hertroys
> >> (haram...@gmail.com) escribió:
> >>>
> >>> Hi all,
> >>>
> >>> I'm struggling with a hierarchical query where I'm tasked to calculate
> >>> weights of items in an (exploded) Bill of Materials, based on the
> weights of
> >>> their components. Not all components are measured with a weight,
> sometimes
> >>> there are pieces, meters, areas, etc, and the hierarchy is of varying
> levels
> >>> of depth.
> >>>
> >>> It would help if I could track a sum() throughout the explosion that
> >>> would write back onto parent rows when the recursion returns: postorder
> >>> traversal.
> >>>
> >>> I created a simplified example about making pizza:
> >>>
> >>> CREATE TABLE ingredient (
> >>>     name text NOT NULL
> >>> );
> >>>
> >>> CREATE TABLE recipe (
> >>>     name text NOT NULL,
> >>>     ingredient text NOT NULL,
> >>>     quantity numeric(6,2) NOT NULL,
> >>>     unit text NOT NULL,
> >>>     step integer NOT NULL
> >>> );
> >>>
> >>> COPY ingredient (name) FROM stdin;
> >>> tomato
> >>> basil
> >>> salt
> >>> tomato sauce
> >>> flour
> >>> water
> >>> yeast
> >>> dough
> >>> pizza bottom
> >>> pizza
> >>> \.
> >>>
> >>> COPY recipe (name, ingredient, quantity, unit, step) FROM stdin;
> >>> tomato sauce    tomato  100.00  g       1
> >>> dough   flour   150.00  g       1
> >>> tomato sauce    basil   10.00   g       2
> >>> pizza   pizza bottom    1.00    pcs     2
> >>> tomato sauce    salt    3.00    g       3
> >>> dough   salt    1.00    pinch   3
> >>> pizza   tomato sauce    1.00    pcs     1
> >>> pizza bottom    dough   1.00    pcs     2
> >>> dough   water   50.00   g       2
> >>> \.
> >>>
> >>> ALTER TABLE ONLY ingredient
> >>>     ADD CONSTRAINT ingredient_pkey PRIMARY KEY (name);
> >>>
> >>> ALTER TABLE ONLY recipe
> >>>     ADD CONSTRAINT recipe_pkey PRIMARY KEY (name, ingredient);
> >>>
> >>> ALTER TABLE ONLY recipe
> >>>     ADD CONSTRAINT recipe_ingredient_fkey FOREIGN KEY (ingredient)
> >>> REFERENCES ingredient(name);
> >>>
> >>> ALTER TABLE ONLY recipe
> >>>     ADD CONSTRAINT recipe_name_fkey FOREIGN KEY (name) REFERENCES
> >>> ingredient(name);
> >>>
> >>>
> >>> A query listing the recipe for 'pizza' would be as follows:
> >>> development=> with recursive pizza (name, step, ingredient, quantity,
> >>> unit, rel_qty, path, weight)
> >>> as (
> >>>         select
> >>>                 name, step, ingredient, quantity, unit
> >>>         ,       quantity::numeric(10,2)
> >>>         ,       step::text
> >>>         ,       case when unit = 'g' then quantity::numeric(10,2) else
> >>> null end
> >>>           from recipe
> >>>          where name = 'pizza'
> >>>         union all
> >>>         select
> >>>                 recipe.name, recipe.step, recipe.ingredient,
> >>> recipe.quantity, recipe.unit
> >>>         ,       (pizza.rel_qty * recipe.quantity)::numeric(10,2)
> >>>         ,       pizza.path || '.' || recipe.step
> >>>         ,       case when recipe.unit = 'g' then (pizza.rel_qty *
> >>> recipe.quantity)::numeric(10,2) else null end
> >>>           from pizza
> >>>           join recipe on (recipe.name = pizza.ingredient)
> >>> )
> >>> select path, ingredient, quantity, rel_qty, unit, weight
> >>>   from pizza
> >>>  order by path;
> >>>
> >>>  path  |  ingredient  | quantity | rel_qty | unit  | weight
> >>> -------+--------------+----------+---------+-------+--------
> >>>  1     | tomato sauce |     1.00 |    1.00 | pcs   |
> >>>  1.1   | tomato       |   100.00 |  100.00 | g     | 100.00
> >>>  1.2   | basil        |    10.00 |   10.00 | g     |  10.00
> >>>  1.3   | salt         |     3.00 |    3.00 | g     |   3.00
> >>>  2     | pizza bottom |     1.00 |    1.00 | pcs   |
> >>>  2.2   | dough        |     1.00 |    1.00 | pcs   |
> >>>  2.2.1 | flour        |   150.00 |  150.00 | g     | 150.00
> >>>  2.2.2 | water        |    50.00 |   50.00 | g     |  50.00
> >>>  2.2.3 | salt         |     1.00 |    1.00 | pinch |
> >>> (9 rows)
> >>>
> >>>
> >>> With these results, I somehow need to calculate that the weights of
> >>> 'tomato sauce', 'dough' and 'pizza bottom' are 113 g, 200 g and 200 g
> >>> respectively, bringing the total weight of 'pizza' to 313 g.
> >>>
> >>> My first thought was to traverse the result of this recursive CTE using
> >>> another one, but in the opposite direction. But since this tends to be
> kept
> >>> as a temporary materialized result set with no indices, that's not
> >>> performing great and it adds a fair amount of complexity to the query
> too.
> >>>
> >>> Then I realised that if we somehow could track the sum() of 'weight'
> >>> throughout exploding these recipe items, by using a postorder tree
> >>> traversal, the desired result would be readily available to pick up
> when the
> >>> recursive CTE travels up through the hierarchy.
> >>>
> >>> In above example; When the CTE would reach '1.3 salt', it would write
> the
> >>> summed 'weight' value 113 back on the result for '1 tomato sauce' and
> when
> >>> it reached '2.2.2 salt' it would write back 200 to '2.2 dough' and
> then 200
> >>> to '2 pizza bottom'.
> >>>
> >>> Is that possible?
> >>>
> >>> I've seen a couple of "solutions" on the internet that just summed up
> the
> >>> results of the CTE, but that won't do as it would not put the correct
> >>> weights onto intermediate levels of the tree as far as I can see (in
> above,
> >>> the weight of 'dough').
> >>>
> >>>
> >>> Regards,
> >>>
> >>> Alban Hertroys
> >>>
> >>>
> >>> PS. Don't try to make pizza using this recipe, it probably won't
> succeed.
> >>> I forgot the yeast, for one thing, and quantities are probably way
> off. Not
> >>> to mention that there are probably more ingredients missing…
> >>>
> >>> PS2. In my real case the ingredients have a base quantity and unit,
> which
> >>> makes adjusting to relative quantities actually viable. Those aren't
> >>> necessary to describe the problem though.
> >>> --
> >>> If you can't see the forest for the trees,
> >>> cut the trees and you'll find there is no forest.
> >>>
> >>>
> >>
> >>
> >> --
> >> Cordialmente,
> >>
> >> Ing. Hellmuth I. Vargas S.
> >> Esp. Telemática y Negocios por Internet
> >> Oracle Database 10g Administrator Certified Associate
> >> EnterpriseDB Certified PostgreSQL 9.3 Associate
> >>
> >
> >
> > --
> > Cordialmente,
> >
> > Ing. Hellmuth I. Vargas S.
> > Esp. Telemática y Negocios por Internet
> > Oracle Database 10g Administrator Certified Associate
> > EnterpriseDB Certified PostgreSQL 9.3 Associate
> >
>
>
>
> --
> If you can't see the forest for the trees,
> Cut the trees and you'll see there is no forest.
>


-- 
Cordialmente,

Ing. Hellmuth I. Vargas S.
Esp. Telemática y Negocios por Internet
Oracle Database 10g Administrator Certified Associate
EnterpriseDB Certified PostgreSQL 9.3 Associate

Reply via email to