0

I wanted to implement a small support ticket system with ArangoDB. I created a collection ticketcategories and wanted to nest them using edges, so I created an edge collection and a graph.

To visualize this, here's an example of the structure: enter image description here

And I'd like to return this into a nested object so I can display it in a combobox later, i.e. like this:

{
  root: {
    categories: [
      bug: {
        categories: [...]
      },
      content: {
        categories: [
          product: {...}
        ]
      },
      feature: {...}
    ]
  }
}

From what I've seen this is not possible with pure AQL, but I don't have much experience with Arango, so I might be wrong.

I tried to perform a normal graph traversal like so:

FOR v, e, p IN 1..4 
   OUTBOUND 'ticketcategories/root'
   GRAPH TicketCategorySubcategories
   OPTIONS { order: 'bfs' }
RETURN p

Since that would give me all paths, but merging those together into a nested object in TypeScript seems dirty. And simply returning the vertices won't give me the relations, so I don't know which subcategories belong to which categories...

Hopefully someone has an idea.

Blade
  • 477
  • 1
  • 7
  • 18

1 Answers1

1

First, returning p (the path) will give you all edges and vertices, so it is possible to determine the relations in JS. However, AQL is good at finding and collecting data, but not great at building a specific JSON document, so the "inelegant" JS could actually be the easiest/cleanest method.

As with any declarative query language, creativity is often required.

My normal approach is to first do it the "stupid" way (very literal/static) and then iterate once you understand how everything fits together and your output is correct.

This example is very static (your graph schema won't change), stepping through the graph one hop at a time. Here, I'm using ZIP() to combine the attribute names to the content in a dynamic way. I know this won't replicate your example JSON schema (it may not even work properly), but it should be illustrative:

RETURN {
    root: {
        categories: (
            FOR vc,ec IN 1 OUTBOUND 'ticketcategories/root'
                GRAPH 'TicketCategorySubcategories'
                LET category = vc.type
                FOR va,ea IN 1 OUTBOUND vc
                    GRAPH 'TicketCategorySubcategories'
                    LET area = va.type
                    LET areas = (
                        FOR vs,es IN 1 OUTBOUND va
                            GRAPH 'TicketCategorySubcategories'
                            LET reasons = (
                                FOR vn,en IN 1 OUTBOUND vs
                                    GRAPH 'TicketCategorySubcategories'
                                    RETURN vn
                            )
                            RETURN ZIP([area],[{ categories: reasons }])
                    )
                    RETURN ZIP([category],[{ categories: areas }])
        )
    }
}

I've used an attribute (v.type) to determine the ticket category/area/reason, but this could be done via collection as well (different collections for each category):

LET category = PARSE_IDENTIFIER(v).collection

Or it could be done via an edge property:

LET category = e.type

If you have unique type values, another way to do this could be with the COLLECT statement and aggregation:

COLLECT category = v.type INTO categories
RETURN ZIP([category],[{categories}])

How you define different document types can change your query approach dramatically. For instance, if you have an attribute on each vertex/document, it makes it easy to filter for that. But if you have the definition baked into edges, then you have to query the edges before grabbing the vertices. Either approach isn't bad, just different.

Generally, I have found that when I need to produce a complex JSON document, it's best to use a Foxx service, or at least in-app code with several AQL queries. It's a LOT easier to understand the logic and offers much more control and flexibility.

kerry
  • 757
  • 6
  • 16
  • Thanks for this detailed answer! Right now I'm going with the JS way, but I also thought about using Foxx services in my app. I'd like to avoid complex queries like that if there are other approaches like foxx or JS Code from within my backend. Right now I decided to limit the amount of subcategories and then just fetch them iteratively, beginning with root. Is it correct that those queries are faster, if I use a root item as a start node? Because I heard that this was the case for graph traversals – Blade Jun 22 '22 at 13:27
  • @Blade - the less you have to traverse, the better (generally). ArangoDB makes heavy use of in-memory indexes, so it's pretty quick. But any time you look at a non-indexed attribute, it has to touch the document, which can mean disk access. I've always found that the best ways to speed things up are (1) indexing and (2) "prune" or otherwise limiting traversal. – kerry Jun 27 '22 at 21:43