3
votes

I have a bill of materials represented in ArangoDB as a directed acyclic graph. The quantity of each part in the bill of materials is represented on the edges while the part names are represented by the keys of the nodes. I'd like to write a query which traverses down the DAG from an ancestor node and sums the quantities of each part by its part name. For example, consider the following graph:

       Qty: 2          Qty: 1         
Widget +------> Gadget +------> Stuff 
    +               +  Qty: 4         
    |  Qty: 1       +---------> Thing 
    +----------------------------^    

Widget contains two Gadgets, which each contains one Stuff and four Things. Widget also contains one Thing. Thus I'd like to write an AQL query which traverses the graph starting at widget and returns:

{
  "Gadget": 2,
  "Stuff": 2,
  "Thing": 9
}

I believe collect aggregate may be my friend here, but I haven't quite found the right incantation yet. Part of the challenge is that all descendant quantities of a part need to be multiplied by their parent quantities. What might such a query look like that efficiently performs this summation on DAGs of depths around 10 layers?

1

1 Answers

2
votes

Three possible options come to mind:

1.- return the values from the path and then summarize the data in the app server:

FOR v,e,p IN 1..2 OUTBOUND 'test/4719491' 
testRel
RETURN {v:v.name, p:p.edges[*].qty}

This returns Gadget 2, Stuff [2,1], Thing [2,4], Thing [ 1 ]

2.- enumerate the edges on the path, to get the results directly :

FOR v,e,p IN 1..2 OUTBOUND 'test/4719491' 
testRel
   let e0 = p.edges[0].qty
   let e1 = NOT_NULL(p.edges[1].qty,1)
   collect itemName = v.name aggregate items = sum(e0 * e1)
Return {itemName: itemName, items: items}

This correctly returns Gadget 2, Stuff 2, Thing 9.

This obviously requires that you know the number of levels before hand.

3.- Write a custom function "multiply" similar to the existing "SUM" function so that you can multiply values of an array. The query would be similar to this :

let vals = (FOR v,e,p IN 1..2 OUTBOUND 'test/4719491' 
            testRel
            RETURN {itemName:v.name, items:SUM(p.edges[*].qty)})
for val in vals
    collect itemName = val.itemName Aggregate items = sum(val.items)
return {itemName: itemName, items: items}

So your function would replace the SUM in the inner sub-select. Here is the documentation on custom functions