Dynamic Attribute Names in AQL
You can use expressions as attribute names or use subqueries and ZIP()
to create objects with varying attribute names
You might want an AQL query to return results with attribute names assembled by a function, or with a variable number of attributes.
This will not work by specifying the result using a regular object literal, as object literals require the names and numbers of attributes to be fixed at query compile time.
There are two solutions to getting dynamic attribute names to work:
- Using expressions as attribute names (fixed amount of attributes)
- Using subqueries and the
ZIP()
function (variable amount of attributes)
Using expressions as attribute names
This solution works in cases where the number of dynamic attributes to return is known in advance, and only the attribute names need to be calculated using an expression.
Using expressions as attribute names instead of fixed attribute names in object
literals requires enclosing the expression in extra [
and ]
to disambiguate
them from regular, unquoted attribute names.
Let us create a result that returns the original document data contained in
a dynamically named attribute. We will be using the expression doc.type
for the attribute name. We will also return some other attributes from the
original documents, but prefix them with the documents’ _key
attribute values.
For this we also need attribute name expressions.
Here is a query showing how to do this. The attribute name expressions all
required to be enclosed in [
and ]
in order to make this work:
LET documents = [
{ "_key" : "3231748397810", "gender" : "f", "status" : "active", "type" : "user" },
{ "_key" : "3231754427122", "gender" : "m", "status" : "inactive", "type" : "unknown" }
]
FOR doc IN documents
RETURN {
[ doc.type ] : {
[ CONCAT(doc._key, "_gender") ] : doc.gender,
[ CONCAT(doc._key, "_status") ] : doc.status
}
}
This will return:
[
{
"user": {
"3231748397810_gender": "f",
"3231748397810_status": "active"
}
},
{
"unknown": {
"3231754427122_gender": "m",
"3231754427122_status": "inactive"
}
}
]
Note: Attribute name expressions and regular, unquoted attribute names can be mixed.
Subquery solution
A generalized solution is to let a subquery or another function produce the
dynamic attribute names, and finally pass them through the ZIP()
function to
create an object from them.
Let us assume we want to process the following input documents:
{ "name": "test", "gender": "f", "status": "active", "type": "user" }
{ "name": "dummy", "gender": "m", "status": "inactive", "type": "unknown", "magicFlag": 23 }
Let us also assume our goal for each of these documents is to return only the
attribute names that contain the letter a
, together with their respective
values.
To extract the attribute names and values from the original documents, we can use a subquery as follows:
LET documents = [
{ "name": "test"," gender": "f", "status": "active", "type": "user" },
{ "name": "dummy", "gender": "m", "status": "inactive", "type": "unknown", "magicFlag": 23 }
]
FOR doc IN documents
RETURN (
FOR name IN ATTRIBUTES(doc)
FILTER LIKE(name, '%a%')
RETURN {
name: name,
value: doc[name]
}
)
The subquery will only let attribute names pass that contain the letter a
.
The results of the subquery are then made available to the main query and will
be returned. But the attribute names in the result are still name
and value
,
so we’re not there yet.
So let us also employ AQL’s ZIP() function, which can create an object from two arrays:
- the first parameter to
ZIP()
is an array with the attribute names - the second parameter to
ZIP()
is an array with the attribute values
Instead of directly returning the subquery result, we first capture it in a
variable, and pass the variable’s name
and value
components into ZIP()
like this:
LET documents = [
{ "name" : "test"," gender" : "f", "status" : "active", "type" : "user" },
{ "name" : "dummy", "gender" : "m", "status" : "inactive", "type" : "unknown", "magicFlag" : 23 }
]
FOR doc IN documents
LET attributes = (
FOR name IN ATTRIBUTES(doc)
FILTER LIKE(name, '%a%')
RETURN {
name: name,
value: doc[name]
}
)
RETURN ZIP(attributes[*].name, attributes[*].value)
Note that we have to use the expansion operator ([*]
) on attributes
because
attributes
itself is an array, and we want either the name
attribute or the
value
attribute of each of its members.
To prove this is working, here is the above query’s result:
[
{
"name": "test",
"status": "active"
},
{
"name": "dummy",
"status": "inactive",
"magicFlag": 23
}
]
As can be seen, the two results have a different amount of result attributes.
We can also make the result a bit more dynamic by prefixing each attribute
with the value of the name
attribute:
LET documents = [
{ "name": "test"," gender": "f", "status": "active", "type": "user" },
{ "name": "dummy", "gender": "m", "status": "inactive", "type": "unknown", "magicFlag": 23 }
]
FOR doc IN documents
LET attributes = (
FOR name IN ATTRIBUTES(doc)
FILTER LIKE(name, '%a%')
RETURN {
name: CONCAT(doc.name, '-', name),
value: doc[name]
}
)
RETURN ZIP(attributes[*].name, attributes[*].value)
That will give us document-specific attribute names like this:
[
{
"test-name": "test",
"test-status": "active"
},
{
"dummy-name": "dummy",
"dummy-status": "inactive",
"dummy-magicFlag": 23
}
]