evitaDB - Fast e-commerce database
logo
page-background

Hierarchy extra results

Hierarchy requirements allows you to compute data structures from tree oriented data relevant for menu rendering. In e-commerce projects, the hierarchy structure is represented by a category tree and the items that refer to it are usually products or some kind of "inventory".

There are many types of menus that can be found on e-commerce sites. Starting with mega menus of various types ...

Mega-menu exampleMega-menu example

... direct subcategory menus ...

Direct subcategories menuDirect subcategories menu

... over a highly dynamic menu that could be rolled out gradually using plus/minus signs without affecting the real item listing on the right side of the screen (it's updated by category after selection) ...

Highly dynamic tree exampleHighly dynamic tree example

... to a hybrid menu that partially opens to a currently selected category and displays only a direct sibling category on the parent axis:

Hybrid menu exampleHybrid menu example

There are a huge number of possible variations, and it is difficult to support all of them with a single constraint. That's why there is an extensible mechanism by which you can request the computation of multiple different parts of the hierarchy tree, as you actually need it for your particular user interface use case.

There are two type of top hierarchy requirements:

hierarchyOfSelf
realized by
and is used to compute data structures from the data of the directly queried hierarchical entity
hierarchyOfReference
realized by
and is used to compute data structures from the data of the entities referencing hierarchical entity

These top hierarchy requirements must have at least one of the following hierarchy sub-constraints:

Constraint to result association

There can be multiple sub-constraints, and each constraint can be duplicated (usually with different settings). Each hierarchy sub-constraint defines a argument with a named value that allows to associate the request constraint with the computed result data structure in
extra result.
The following code snippet contains a query that lists all (transitive) categories in the Audio category and also returns menu items that contain direct children of the Audio category and its direct parent category (which is Accessories):
Both menu components are stored in the
extra result data structure and are available under the labels that correspond to those used in request constraints.

Hierarchy of self

orderConstraint:any
optional ordering constraint that allows you to specify an order of
LevelInfo elements in the result hierarchy data structure
requireConstraint:(fromRoot|fromNode|siblings|children|parents)+

mandatory one or more constraints allowing you to instruct evitaDB to calculate menu components; one or all of the constraints may be present:

The requirement triggers the calculation of the
data structure for the hierarchy of which it is a part.
The hierarchy of self can still be combined with hierarchyOfReference if the queried entity is a hierarchical entity that is also connected to another hierarchical entity. Such situations are rather sporadic in reality.

Hierarchy of reference

argument:string+
specification of one or more reference names that identify the reference to the target hierarchical entity for which the menu calculation should be performed; usually only one reference name makes sense, but to adapt the constraint to the behavior of other similar constraints, evitaQL accepts multiple reference names for the case that the same requirements apply to different references of the queried entity.
argument:enum(LEAVE_EMPTY|REMOVE_EMPTY)
optional argument of type
enum allowing you to specify whether or not to return empty hierarchical entities (e.g., those that do not have any queried entities that satisfy the current query filter constraint assigned to them - either directly or transitively):
  • LEAVE_EMPTY: empty hierarchical nodes will remain in computed data structures
  • REMOVE_EMPTY: empty hierarchical nodes are omitted from computed data structures (default behaviour)
orderConstraint:any
optional ordering constraint that allows you to specify an order of
LevelInfo elements in the result hierarchy data structure
requireConstraint:(fromRoot|fromNode|siblings|children|parents)+

mandatory one or more constraints allowing you to instruct evitaDB to calculate menu components; one or all of the constraints may be present:

The requirement triggers the calculation of the
data structure for the hierarchies of the referenced entity type.
The hierarchy of reference can still be combined with hierarchyOfSelf if the queried entity is a hierarchical entity that is also connected to another hierarchical entity. Such situations are rather sporadic in reality.
The hierarchyOfReference can be repeated multiple times in a single query if you need different calculation settings for different reference types.

From root

argument:string!
mandatory argument specifying the output name for the calculated data structure (see constraint to result association)
requireConstraint:(entityFetch|stopAt|statistics)*

optional one or more constraints that allow you to define the completeness of the hierarchy entities, the scope of the traversed hierarchy tree, and the statistics computed along the way; any or all of the constraints may be present:

The fromRoot
requirement
computes the hierarchy tree starting from the "virtual" invisible top root of the hierarchy, regardless of the potential use of the hierarchyWithin constraint in the filtering part of the query. The scope of the calculated information can be controlled by the stopAt
constraint
. By default, the traversal goes all the way to the bottom of the hierarchy tree unless you tell it to stop anywhere. If you need to access statistical data, use
statistics constraint
. Calculated data is not affected by the hierarchyWithin filter constraint - the query can filter entities using hierarchyWithin from category Accessories, while still allowing you to correctly compute menu at root level.
Please keep in mind that the full statistic calculation can be particularly expensive in the case of the fromRoot requirement - it usually requires aggregation for the entire queried dataset (see more information about the calculation).
The following query lists products in category Audio and its subcategories. Along with the returned products, it also requires a computed megaMenu data structure that lists the top 2 levels of the Category hierarchy tree with a computed count of child categories for each menu item and an aggregated count of all filtered products that would fall into the given category.
The computed result of the megaMenu looks like this:
Top 2 categories visualizationTop 2 categories visualization

... and here is the data structure output in JSON format:

The calculated result for fromRoot is not affected by the hierarchyWithin pivot hierarchy node. If the hierarchyWithin contains inner constraints having or excluding, the fromRoot respects them. The reason is simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that apply to the hierarchyWithin so that the calculated number remains consistent for the end user.

From node

argument:string!
mandatory argument specifying the output name for the calculated data structure (see constraint to result association)
requireConstraint:node!
mandatory require constraint node that must match exactly one pivot hierarchical entity that represents the root node of the traversed hierarchy subtree.
requireConstraint:(entityFetch|stopAt|statistics)*

optional one or more constraints that allow you to define the completeness of the hierarchy entities, the scope of the traversed hierarchy tree, and the statistics computed along the way; any or all of the constraints may be present:

The fromNode
requirement
computes the hierarchy tree starting from the pivot node of the hierarchy, that is identified by the node
inner constraint
. The fromNode calculates the result regardless of the potential use of the hierarchyWithin constraint in the filtering part of the query. The scope of the calculated information can be controlled by the stopAt
constraint
. By default, the traversal goes all the way to the bottom of the hierarchy tree unless you tell it to stop anywhere. Calculated data is not affected by the hierarchyWithin filter constraint - the query can filter entities using hierarchyWithin from category Accessories, while still allowing you to correctly compute menu at different node defined in a fromNode requirement. If you need to access statistical data, use
statistics constraint
.
The following query lists products in category Audio and its subcategories. Along with the products returned, it also returns a computed sideMenu1 and sideMenu2 data structure that lists the flat category list for the categories Portables and Laptops with a computed count of child categories for each menu item and an aggregated count of all products that would fall into the given category.
The computed result both of the sideMenu1 and sideMenu2 looks like this:
From node query visualizationFrom node query visualization

... and here is the data structure output in JSON format:

The calculated result for fromNode is not affected by the hierarchyWithin pivot hierarchy node. If the hierarchyWithin contains inner constraints having or excluding, the fromNode respects them. The reason is simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that apply to the hierarchyWithin so that the calculated number remains consistent for the end user.

Children

argument:string!
mandatory argument specifying the output name for the calculated data structure (see constraint to result association)
requireConstraint:(entityFetch|stopAt|statistics)*

optional one or more constraints that allow you to define the completeness of the hierarchy entities, the scope of the traversed hierarchy tree, and the statistics computed along the way; any or all of the constraints may be present:

The children
requirement
computes the hierarchy tree starting at the same hierarchy node that is targeted by the filtering part of the same query using the hierarchyWithin or hierarchyWithinRoot constraints. The scope of the calculated information can be controlled by the stopAt
constraint
. By default, the traversal goes all the way to the bottom of the hierarchy tree unless you tell it to stop anywhere. If you need to access statistical data, use the
statistics constraint
.
The following query lists products in category Audio and its subcategories. Along with the products returned, it also returns a computed subcategories data structure that lists the flat category list the currently focused category Audio with a computed count of child categories for each menu item and an aggregated count of all products that would fall into the given category.
The computed result subcategories looks like this:
Children listing visualizationChildren listing visualization

... and here is the data structure output in JSON format:

The calculated result for children is connected with the hierarchyWithin pivot hierarchy node (or the "virtual" invisible top root referred to by the hierarchyWithinRoot constraint). If the hierarchyWithin contains inner constraints having or excluding, the children will respect them as well. The reason is simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that apply to the hierarchyWithin so that the calculated number remains consistent for the end user.

Parents

argument:string!
mandatory argument specifying the output name for the calculated data structure (see constraint to result association)
requireConstraint:(siblings|entityFetch|stopAt|statistics)*

optional one or more constraints that allow you to define the completeness of the hierarchy entities, the scope of the traversed hierarchy tree, and the statistics computed along the way; any or all of the constraints may be present:

The parents
requirement
computes the hierarchy tree starting at the same hierarchy node that is targeted by the filtering part of the same query using the hierarchyWithin constraint towards the root of the hierarchy. The scope of the calculated information can be controlled by the stopAt
constraint
. By default, the traversal goes all the way to the top of the hierarchy tree unless you tell it to stop at anywhere. If you need to access statistical data, use the
statistics constraint
.
The following query lists products in the category Audio and its subcategories. Along with the products returned, it also returns a computed parentAxis data structure that lists all the parent nodes of the currently focused category True wireless with a computed count of child categories for each menu item and an aggregated count of all products that would fall into the given category.
The computed result parentAxis looks like this:
Parents listing visualizationParents listing visualization

... and here is the data structure output in JSON format:

You can also list all siblings of the parent node as you move up the tree:

The computed result parentAxis with siblings now looks like this:
Parents with siblings listing visualizationParents with siblings listing visualization

... and here is the data structure output in JSON format:

If you need each of these siblings to fetch their child nodes as well (no matter if they are only one level deep or more), you can do this by adding a stopAt
constraint
to the siblings
constraint container
. However, this scenario is too complex to cover in this documentation.
The calculated result for parents is connected with the hierarchyWithin pivot hierarchy node. If the hierarchyWithin contains inner constraints having or excluding, the parents will respect them as well during child nodes / queried entities statistics calculation. The reason is simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that apply to the hierarchyWithin so that the calculated number remains consistent for the end user.

Siblings

Different siblings syntax when used within parents parent constraint
The siblings
constraint
can be used separately
as a child of hierarchyOfSelf or hierarchyOfReference
, or it can be used as a
child constraint
of the parents
constraint
.
In such a case, the siblings constraint lacks the first string argument that defines the name for the output data structure. The reason is that this name is already defined on the enclosing parents constraint, and the siblings constraint simply extends the data available in its data structure.
argument:string!
mandatory argument specifying the output name for the calculated data structure (see constraint to result association)
requireConstraint:(entityFetch|stopAt|statistics)*

optional one or more constraints that allow you to define the completeness of the hierarchy entities, the scope of the traversed hierarchy tree, and the statistics computed along the way; any or all of the constraints may be present:

The siblings
requirement
computes the hierarchy tree starting at the same hierarchy node that is targeted by the filtering part of the same query using the hierarchyWithin. It lists all sibling nodes to the node that is requested by hierarchyWithin constraint (that's why the siblings has no sense with hierarchyWithinRoot constraint - "virtual" top level node cannot have any siblings). Siblings will produce a flat list of siblings unless the stopAt
constraint
is used
as an inner constraint
. The stopAt
constraint
triggers a top-down hierarchy traversal from each of the sibling nodes until the stopAt is satisfied. If you need to access statistical data, use the
statistics constraint
.
The following query lists products in category Audio and its subcategories. Along with the products returned, it also returns a computed audioSiblings data structure that lists the flat category list the currently focused category Audio with a computed count of child categories for each menu item and an aggregated count of all products that would fall into the given category.
The computed result audioSiblings looks like this:
Siblings listing visualizationSiblings listing visualization

... and here is the data structure output in JSON format:

If you need to return all siblings and also the level below them (their children), just use the stopAt
constraint
and extend the default scope of the siblings
constraint
.
The computed result audioSiblings with their direct children looks like this (visualized in JSON format):
The calculated result for siblings is connected with the hierarchyWithin pivot hierarchy node. If the hierarchyWithin contains inner constraints having or excluding, the children will respect them as well. The reason is simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that apply to the hierarchyWithin so that the calculated number remains consistent for the end user.

Stop at

requireConstraint:(distance|level|node)!

mandatory constraint that defines the constraint that stops traversing the hierarchy tree when it's satisfied by a currently traversed node; one of the following constraints must be present

The stopAt
container constraint
is a service wrapping constraint container that only makes sense
in combination with one of the allowed nested constraints
. See the usage examples for specific
nested constraints
.

Distance

argument:int!

defines a maximum relative distance from the pivot node that can be traversed; the pivot node itself is at distance zero, its direct child or direct parent is at distance one, each additional step adds a one to the distance

The distance constraint can only be used within the stopAt
container
and limits the hierarchy traversal to stop when the number of levels traversed reaches the specified constant. The distance is always relative to the pivot node (the node where the hierarchy traversal starts) and is the same whether we are traversing the hierarchy top-down or bottom-up. The distance between any two nodes in the hierarchy can be calculated as abs(level(nodeA) - level(nodeB)). See the following figure when the pivot node is Audio:
The following query lists products in category Audio and its subcategories. Along with the products returned, it also returns a computed subcategories data structure that lists the flat category list the currently focused category Audio.

Which returns following output:

The following query lists products in the category Audio and its subcategories. Along with the products returned, it also returns a computed parent data structure that lists single direct parent category of the currently focused Audio category.

That returns simply:

Level

argument:int!

defines an absolute level number where the traversal should stop; if the level is equal to or less (for top-down traversals) / equal to or greater (for bottom-up traversals) than the level of the starting (pivot) node, the traversal stops immediately.

The level constraint can only be used within the stopAt
container
and limits the hierarchy traversal to stop when the actual level of the traversed node is equal to a specified constant. The "virtual" top invisible node has level zero, the top nodes (nodes with NULL parent) have level one, their children have level two, and so on. See the following figure:
The following query lists products in Audio category and its subcategories. Along with the products returned, it also returns a computed megaMenu data structure that lists top two levels of the entire hierarchy.

Which returns:

The following query lists products in the Audio category and its subcategories. Along with the products returned, it also returns a computed parent data structure that lists all the parents of the currently focused True wireless category up to level two.

... returns output:

Node

filterConstraint:any+

defines a criterion that determines the point in a hierarchical structure where the traversal should stop; the traversal stops at the first node that satisfies the filter condition specified in this container

The node filtering container is an alternative to the distance and level termination constraints, which is much more dynamic and can produce hierarchy trees of non-uniform depth. Because the filtering constraint can be satisfied by nodes of widely varying depths, traversal can be highly dynamic.
The situations where you'd need this dynamic behavior are few and far between. Unfortunately, we do not have a meaningful example of this in the demo dataset, so our example query will be slightly off. But for the sake of demonstration, let's list the entire Accessories hierarchy, but stop traversing at the nodes whose code starts with the letter w.
The computed result subMenu looks like this (visualized in JSON format):

Statistics

argument:enum(COMPLETE_FILTER|WITHOUT_USER_FILTER)
optional argument of type
enum allowing you to specify the base queried entity set that is the source for statistics calculations:
  • COMPLETE_FILTER: complete filtering query constraint
  • WITHOUT_USER_FILTER: filtering query constraint where the contents of optional userFilter are ignored
The calculation always ignores hierarchyWithin because the focused part of the hierarchy tree is defined on the requirement constraint level, but including having/excluding constraints. The having/excluding constraints are crucial for the calculation of queriedEntityCount (and therefore also affects the value of childrenCount transitively)
argument:enum(CHILDREN_COUNT|QUERIED_ENTITY_COUNT)+
mandatory argument of type
enum that specifies which statistics to compute for each node in the returned hierarchy:
  • CHILDREN_COUNT: triggers calculation of the count of child hierarchy nodes that exist in the hierarchy tree below the given node; the count is correct regardless of whether the children themselves are requested/traversed by the constraint definition, and respects hierarchyOfReference settings for automatic removal of hierarchy nodes that would contain empty result set of queried entities (REMOVE_EMPTY)
  • QUERIED_ENTITY_COUNT: triggers the calculation of the total number of queried entities that will be returned if the current query is focused on this particular hierarchy node using the hierarchyWithin filter constraint (the possible refining constraint in the form of directRelation and excluding-root is not taken into account).

one or all possible enum values can be used

The statistics constraint with CHILDREN_COUNT
allows you to easily render collapsed menu showing the nodes available for opening without actually requesting the child nodes from the database:
Accessories dynamic tree exampleAccessories dynamic tree example
As you can see, the Smart wearable, Audio, and Keyboards nodes have a plus sign next to them, indicating that the user can expand this category.
The statistics constraint with QUERIED_ENTITY_COUNT
allows you to display the number of items hidden behind the given hierarchy node (category):
Queried entity counts exampleQueried entity counts example

From the series listing, the end user can clearly see how many products make up the series category, no matter how branched the series category may be.

Computational complexity of statistical data calculation
The performance price paid for calculating statistics is not negligible. The calculation of
CHILDREN_COUNT
is cheaper because it allows to eliminate "dead branches" early and thus conserve the computation cycles. The calculation of the
QUERIED_ENTITY_COUNT
is more expensive because it requires counting items up to the last one and must be precise.
We strongly recommend that you avoid using
QUERIED_ENTITY_COUNT
for root hierarchy nodes for large datasets.

This query actually has to filter and aggregate all the records in the database, which is obviously quite expensive, even considering that all the indexes are in-memory. Caching is probably the only way out if you really need to crunch these numbers.

Author: Ing. Jan Novotný

Date updated: 5.5.2023

Documentation Source