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 example
... direct subcategory menus ...
Direct 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 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 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 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
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
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.
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:
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
.
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 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.
mandatory require constraint node that must match exactly one pivot hierarchical entity that
represents the root node of the traversed hierarchy subtree.
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:
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
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 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.
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:
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
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 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.
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:
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
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 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 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
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.
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:
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
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 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
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):
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)
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 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 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.