mirror of https://gitee.com/bigwinds/arangodb
689 lines
19 KiB
Markdown
689 lines
19 KiB
Markdown
AQL Examples {#AqlExamples}
|
|
===========================
|
|
|
|
@NAVIGATE_AqlExamples
|
|
@EMBEDTOC{AqlExamplesTOC}
|
|
|
|
Simple queries {#AqlExamplesSimple}
|
|
===================================
|
|
|
|
This page contains some examples for how to write queries in AQL. For better
|
|
understandability, the query results are also included directly below each query.
|
|
|
|
A query that returns a string value. the result string is contained in a list
|
|
because the result of every valid query is a list:
|
|
|
|
RETURN "this will be returned"
|
|
|
|
[
|
|
"this will be returned"
|
|
]
|
|
|
|
|
|
Here's a query that creates the cross products of two lists, and runs a projection
|
|
on it, using a few of AQL's built-in functions:
|
|
|
|
FOR year in [ 2011, 2012, 2013 ]
|
|
FOR quarter IN [ 1, 2, 3, 4 ]
|
|
RETURN {
|
|
"y" : "year",
|
|
"q" : quarter,
|
|
"nice" : CONCAT(TO_STRING(quarter), "/", TO_STRING(year))
|
|
}
|
|
|
|
[
|
|
{ "y" : "year", "q" : 1, "nice" : "1/2011" },
|
|
{ "y" : "year", "q" : 2, "nice" : "2/2011" },
|
|
{ "y" : "year", "q" : 3, "nice" : "3/2011" },
|
|
{ "y" : "year", "q" : 4, "nice" : "4/2011" },
|
|
{ "y" : "year", "q" : 1, "nice" : "1/2012" },
|
|
{ "y" : "year", "q" : 2, "nice" : "2/2012" },
|
|
{ "y" : "year", "q" : 3, "nice" : "3/2012" },
|
|
{ "y" : "year", "q" : 4, "nice" : "4/2012" },
|
|
{ "y" : "year", "q" : 1, "nice" : "1/2013" },
|
|
{ "y" : "year", "q" : 2, "nice" : "2/2013" },
|
|
{ "y" : "year", "q" : 3, "nice" : "3/2013" },
|
|
{ "y" : "year", "q" : 4, "nice" : "4/2013" }
|
|
]
|
|
|
|
Collection-based queries {#AqlExamplesCollection}
|
|
=================================================
|
|
|
|
Normally you would want to run queries on data stored in collections. This section
|
|
will provide several examples for that.
|
|
|
|
|
|
Example data
|
|
------------
|
|
|
|
Some of the following example queries are executed on a collection `users`
|
|
with the following initial data:
|
|
|
|
[
|
|
{ "id" : 100, "name" : "John", "age" : 37, "active" : true, "gender" : "m" },
|
|
{ "id" : 101, "name" : "Fred", "age" : 36, "active" : true, "gender" : "m" },
|
|
{ "id" : 102, "name" : "Jacob", "age" : 35, "active" : false, "gender" : "m" },
|
|
{ "id" : 103, "name" : "Ethan", "age" : 34, "active" : false, "gender" : "m" },
|
|
{ "id" : 104, "name" : "Michael", "age" : 33, "active" : true, "gender" : "m" },
|
|
{ "id" : 105, "name" : "Alexander", "age" : 32, "active" : true, "gender" : "m" },
|
|
{ "id" : 106, "name" : "Daniel", "age" : 31, "active" : true, "gender" : "m" },
|
|
{ "id" : 107, "name" : "Anthony", "age" : 30, "active" : true, "gender" : "m" },
|
|
{ "id" : 108, "name" : "Jim", "age" : 29, "active" : true, "gender" : "m" },
|
|
{ "id" : 109, "name" : "Diego", "age" : 28, "active" : true, "gender" : "m" },
|
|
{ "id" : 200, "name" : "Sophia", "age" : 37, "active" : true, "gender" : "f" },
|
|
{ "id" : 201, "name" : "Emma", "age" : 36, "active" : true, "gender" : "f" },
|
|
{ "id" : 202, "name" : "Olivia", "age" : 35, "active" : false, "gender" : "f" },
|
|
{ "id" : 203, "name" : "Madison", "age" : 34, "active" : true, "gender": "f" },
|
|
{ "id" : 204, "name" : "Chloe", "age" : 33, "active" : true, "gender" : "f" },
|
|
{ "id" : 205, "name" : "Eva", "age" : 32, "active" : false, "gender" : "f" },
|
|
{ "id" : 206, "name" : "Abigail", "age" : 31, "active" : true, "gender" : "f" },
|
|
{ "id" : 207, "name" : "Isabella", "age" : 30, "active" : true, "gender" : "f" },
|
|
{ "id" : 208, "name" : "Mary", "age" : 29, "active" : true, "gender" : "f" },
|
|
{ "id" : 209, "name" : "Mariah", "age" : 28, "active" : true, "gender" : "f" }
|
|
]
|
|
|
|
For some of the examples, we'll also use a collection `relations` to store
|
|
relationships between users. The example data for `relations` are as follows:
|
|
|
|
[
|
|
{ "from" : 209, "to" : 205, "type" : "friend" },
|
|
{ "from" : 206, "to" : 108, "type" : "friend" },
|
|
{ "from" : 202, "to" : 204, "type" : "friend" },
|
|
{ "from" : 200, "to" : 100, "type" : "friend" },
|
|
{ "from" : 205, "to" : 101, "type" : "friend" },
|
|
{ "from" : 209, "to" : 203, "type" : "friend" },
|
|
{ "from" : 200, "to" : 203, "type" : "friend" },
|
|
{ "from" : 100, "to" : 208, "type" : "friend" },
|
|
{ "from" : 101, "to" : 209, "type" : "friend" },
|
|
{ "from" : 206, "to" : 102, "type" : "friend" },
|
|
{ "from" : 104, "to" : 100, "type" : "friend" },
|
|
{ "from" : 104, "to" : 108, "type" : "friend" },
|
|
{ "from" : 108, "to" : 209, "type" : "friend" },
|
|
{ "from" : 206, "to" : 106, "type" : "friend" },
|
|
{ "from" : 204, "to" : 105, "type" : "friend" },
|
|
{ "from" : 208, "to" : 207, "type" : "friend" },
|
|
{ "from" : 102, "to" : 108, "type" : "friend" },
|
|
{ "from" : 207, "to" : 203, "type" : "friend" },
|
|
{ "from" : 203, "to" : 106, "type" : "friend" },
|
|
{ "from" : 202, "to" : 108, "type" : "friend" },
|
|
{ "from" : 201, "to" : 203, "type" : "friend" },
|
|
{ "from" : 105, "to" : 100, "type" : "friend" },
|
|
{ "from" : 100, "to" : 109, "type" : "friend" },
|
|
{ "from" : 207, "to" : 109, "type" : "friend" },
|
|
{ "from" : 103, "to" : 203, "type" : "friend" },
|
|
{ "from" : 208, "to" : 104, "type" : "friend" },
|
|
{ "from" : 105, "to" : 104, "type" : "friend" },
|
|
{ "from" : 103, "to" : 208, "type" : "friend" },
|
|
{ "from" : 203, "to" : 107, "type" : "boyfriend" },
|
|
{ "from" : 107, "to" : 203, "type" : "girlfriend" },
|
|
{ "from" : 208, "to" : 109, "type" : "boyfriend" },
|
|
{ "from" : 109, "to" : 208, "type" : "girlfriend" },
|
|
{ "from" : 106, "to" : 205, "type" : "girlfriend" },
|
|
{ "from" : 205, "to" : 106, "type" : "boyfriend" },
|
|
{ "from" : 103, "to" : 209, "type" : "girlfriend" },
|
|
{ "from" : 209, "to" : 103, "type" : "boyfriend" },
|
|
{ "from" : 201, "to" : 102, "type" : "boyfriend" },
|
|
{ "from" : 102, "to" : 201, "type" : "girlfriend" },
|
|
{ "from" : 206, "to" : 100, "type" : "boyfriend" },
|
|
{ "from" : 100, "to" : 206, "type" : "girlfriend" }
|
|
]
|
|
|
|
Things to consider when running queries on collections
|
|
------------------------------------------------------
|
|
|
|
Note that all documents created in the two collections will automatically get the
|
|
following server-generated attributes:
|
|
|
|
`_id`: a unique id, consisting of collection name and a server-side sequence value
|
|
|
|
`_key`: the server sequence value
|
|
|
|
`_rev`: the document's revision id
|
|
|
|
|
|
Whenever you run queries on the documents in the two collections, don't be surprised if
|
|
these additional attributes are returned as well.
|
|
|
|
Please also note that with real-world data, you might want to create additional
|
|
indexes on the data (left out here for brevity). Adding indexes on attributes that are
|
|
used in `FILTER` statements may considerably speed up queries. Furthermore, instead of
|
|
using attributes such as `id`, `from`, and `to`, you might want to use the built-in
|
|
`_id`, `_from` and `to` attributes. Finally, edge collections provide a nice way of
|
|
establishing references / links between documents. These features have been left out here
|
|
for brevity as well.
|
|
|
|
|
|
Projections and Filters {#AqlExamplesProjection}
|
|
================================================
|
|
|
|
Returning unaltered documents
|
|
-----------------------------
|
|
|
|
To return 3 complete documents from collection `users`, the following query can be used:
|
|
|
|
FOR u IN users
|
|
LIMIT 0, 3
|
|
RETURN u
|
|
|
|
[
|
|
{
|
|
"_id" : "users/229886047207520",
|
|
"_rev" : "229886047207520",
|
|
"_key" : "229886047207520",
|
|
"active" : true,
|
|
"id" : 206,
|
|
"age" : 31,
|
|
"gender" : "f",
|
|
"name" : "Abigail"
|
|
},
|
|
{
|
|
"_id" : "users/229886045175904",
|
|
"_rev" : "229886045175904",
|
|
"_key" : "229886045175904",
|
|
"active" : true,
|
|
"id" : 101,
|
|
"age" : 36,
|
|
"name" : "Fred",
|
|
"gender" : "m"
|
|
},
|
|
{
|
|
"_id" : "users/229886047469664",
|
|
"_rev" : "229886047469664",
|
|
"_key" : "229886047469664",
|
|
"active" : true,
|
|
"id" : 208,
|
|
"age" : 29,
|
|
"name" : "Mary",
|
|
"gender" : "f"
|
|
}
|
|
]
|
|
|
|
Note that there is a `LIMIT` clause but no `SORT` clause. In this case it is not guaranteed
|
|
which of the user documents are returned. Effectively the document return order is unspecified
|
|
if no `SORT` clause is used, and you should not rely on the order in such queries.
|
|
|
|
Projections
|
|
-----------
|
|
|
|
To return a projection from the collection `users`, use a modified `RETURN` instruction:
|
|
|
|
FOR u IN users
|
|
LIMIT 0, 3
|
|
RETURN {
|
|
"user" : {
|
|
"isActive" : u.active ? "yes" : "no",
|
|
"name" : u.name
|
|
}
|
|
}
|
|
|
|
[
|
|
{
|
|
"user" : {
|
|
"isActive" : "yes",
|
|
"name" : "John"
|
|
}
|
|
},
|
|
{
|
|
"user" : {
|
|
"isActive" : "yes",
|
|
"name" : "Anthony"
|
|
}
|
|
},
|
|
{
|
|
"user" : {
|
|
"isActive" : "yes",
|
|
"name" : "Fred"
|
|
}
|
|
}
|
|
]
|
|
|
|
Filters
|
|
-------
|
|
|
|
To return a filtered projection from collection `users`, you can use the
|
|
`FILTER` keyword. Additionally, a `SORT` clause is used to have the result
|
|
returned in a specific order:
|
|
|
|
FOR u IN users
|
|
FILTER u.active == true && u.age >= 30
|
|
SORT u.age DESC
|
|
LIMIT 0, 5
|
|
RETURN {
|
|
"age" : u.age,
|
|
"name" : u.name
|
|
}
|
|
|
|
[
|
|
{
|
|
"age" : 37,
|
|
"name" : "Sophia"
|
|
},
|
|
{
|
|
"age" : 37,
|
|
"name" : "John"
|
|
},
|
|
{
|
|
"age" : 36,
|
|
"name" : "Emma"
|
|
},
|
|
{
|
|
"age" : 36,
|
|
"name" : "Fred"
|
|
},
|
|
{
|
|
"age" : 34,
|
|
"name" : "Madison"
|
|
}
|
|
]
|
|
|
|
Joins {#AqlExamplesJoins}
|
|
=========================
|
|
|
|
So far we have only dealt with one collection (`users`) at a time. We also have a
|
|
collection `relations` that stores relationships between users. We will now use
|
|
this extra collection to create a result from two collections.
|
|
|
|
First of all, we'll query a few users together with their friends' ids. For that,
|
|
we'll use all `relations` that have a value of `friend` in their `type` attribute.
|
|
Relationships are established by using the `from` and `to` attributes in the
|
|
`relations` collection, which point to the `id` values in the `users` collection.
|
|
|
|
Join tuples
|
|
-----------
|
|
|
|
We'll start with an SQL-ish result set and return each tuple (user name, friend id)
|
|
separately. The AQL query to generate such result is:
|
|
|
|
FOR u IN users
|
|
FILTER u.active == true
|
|
LIMIT 0, 4
|
|
FOR f IN relations
|
|
FILTER f.type == "friend" && f.from == u.id
|
|
RETURN {
|
|
"user" : u.name,
|
|
"friendId" : f.to
|
|
}
|
|
|
|
[
|
|
{
|
|
"user" : "Abigail",
|
|
"friendId" : 108
|
|
},
|
|
{
|
|
"user" : "Abigail",
|
|
"friendId" : 102
|
|
},
|
|
{
|
|
"user" : "Abigail",
|
|
"friendId" : 106
|
|
},
|
|
{
|
|
"user" : "Fred",
|
|
"friendId" : 209
|
|
},
|
|
{
|
|
"user" : "Mary",
|
|
"friendId" : 207
|
|
},
|
|
{
|
|
"user" : "Mary",
|
|
"friendId" : 104
|
|
},
|
|
{
|
|
"user" : "Mariah",
|
|
"friendId" : 203
|
|
},
|
|
{
|
|
"user" : "Mariah",
|
|
"friendId" : 205
|
|
}
|
|
]
|
|
|
|
Horizontal lists
|
|
----------------
|
|
|
|
Note that in the above result, a user might be returned multiple times. This is the
|
|
SQL way of returning data. If this is not desired, the friends' ids of each user
|
|
can be returned in a horizontal list. This will each user at most once.
|
|
|
|
The AQL query for doing so is:
|
|
|
|
FOR u IN users
|
|
FILTER u.active == true LIMIT 0, 4
|
|
RETURN {
|
|
"user" : u.name,
|
|
"friendIds" : (
|
|
FOR f IN relations
|
|
FILTER f.from == u.id && f.type == "friend"
|
|
RETURN f.to
|
|
)
|
|
}
|
|
|
|
[
|
|
{
|
|
"user" : "Abigail",
|
|
"friendIds" : [
|
|
108,
|
|
102,
|
|
106
|
|
]
|
|
},
|
|
{
|
|
"user" : "Fred",
|
|
"friendIds" : [
|
|
209
|
|
]
|
|
},
|
|
{
|
|
"user" : "Mary",
|
|
"friendIds" : [
|
|
207,
|
|
104
|
|
]
|
|
},
|
|
{
|
|
"user" : "Mariah",
|
|
"friendIds" : [
|
|
203,
|
|
205
|
|
]
|
|
}
|
|
]
|
|
|
|
In this query, we're still iterating over the users in the `users` collection,
|
|
and for each matching user we are executing a sub-query to create the matching
|
|
list of related users.
|
|
|
|
Self joins
|
|
----------
|
|
|
|
To not only return friend ids but also the names of friends, we could "join" the
|
|
`users` collection once more (something like a "self join"):
|
|
|
|
FOR u IN users
|
|
FILTER u.active == true
|
|
LIMIT 0, 4
|
|
RETURN {
|
|
"user" : u.name,
|
|
"friendIds" : (
|
|
FOR f IN relations
|
|
FILTER f.from == u.id && f.type == "friend"
|
|
FOR u2 IN users
|
|
FILTER f.to == u2.id
|
|
RETURN u2.name
|
|
)
|
|
}
|
|
|
|
[
|
|
{
|
|
"user" : "Abigail",
|
|
"friendIds" : [
|
|
"Jim",
|
|
"Jacob",
|
|
"Daniel"
|
|
]
|
|
},
|
|
{
|
|
"user" : "Fred",
|
|
"friendIds" : [
|
|
"Mariah"
|
|
]
|
|
},
|
|
{
|
|
"user" : "Mary",
|
|
"friendIds" : [
|
|
"Isabella",
|
|
"Michael"
|
|
]
|
|
},
|
|
{
|
|
"user" : "Mariah",
|
|
"friendIds" : [
|
|
"Madison",
|
|
"Eva"
|
|
]
|
|
}
|
|
]
|
|
|
|
Grouping {#AqlExamplesGrouping}
|
|
===============================
|
|
|
|
To group results by arbitrary criteria, AQL provides the `COLLECT` keyword.
|
|
`COLLECT` will perform a grouping, but no aggregation. Aggregation can still be
|
|
added in the query if required.
|
|
|
|
Grouping by criteria
|
|
--------------------
|
|
|
|
To group users by age, and result the names of the users with the highest ages,
|
|
we'll issue a query like this:
|
|
|
|
FOR u IN users
|
|
FILTER u.active == true
|
|
COLLECT age = u.age INTO usersByAge
|
|
SORT age DESC LIMIT 0, 5
|
|
RETURN {
|
|
"age" : age,
|
|
"users" : usersByAge[*].u.name
|
|
}
|
|
|
|
[
|
|
{
|
|
"age" : 37,
|
|
"users" : [
|
|
"John",
|
|
"Sophia"
|
|
]
|
|
},
|
|
{
|
|
"age" : 36,
|
|
"users" : [
|
|
"Fred",
|
|
"Emma"
|
|
]
|
|
},
|
|
{
|
|
"age" : 34,
|
|
"users" : [
|
|
"Madison"
|
|
]
|
|
},
|
|
{
|
|
"age" : 33,
|
|
"users" : [
|
|
"Chloe",
|
|
"Michael"
|
|
]
|
|
},
|
|
{
|
|
"age" : 32,
|
|
"users" : [
|
|
"Alexander"
|
|
]
|
|
}
|
|
]
|
|
|
|
The query will put all users together by their `age` attribute. There will be one
|
|
result document per distinct `age` value (let aside the `LIMIT`). For each group,
|
|
we have access to the matching document via the `usersByAge` variable introduced in
|
|
the `COLLECT` statement.
|
|
|
|
[*] list expander
|
|
-----------------
|
|
|
|
The `usersByAge` variable contains the full documents found, and as we're only
|
|
interested in user names, we'll use the list expander (`[*]`) to extract just the
|
|
`name` attribute of all user documents in each group.
|
|
|
|
The `[*]` expander is just a handy short-cut. Instead of `usersByAge[*].u.name` we
|
|
could also write:
|
|
|
|
FOR temp IN usersByAge
|
|
RETURN temp.u.name
|
|
|
|
Grouping by multiple criteria
|
|
-----------------------------
|
|
|
|
To group by multiple criteria, we'll use multiple arguments in the `COLLECT` clause.
|
|
For example, to group users by `ageGroup` (a derived value we need to calculate first)
|
|
and then by `gender`, we'll do:
|
|
|
|
FOR u IN users
|
|
FILTER u.active == true
|
|
COLLECT ageGroup = FLOOR(u.age / 5) * 5,
|
|
gender = u.gender INTO group
|
|
SORT ageGroup DESC
|
|
RETURN {
|
|
"ageGroup" : ageGroup,
|
|
"gender" : gender
|
|
}
|
|
|
|
[
|
|
{
|
|
"ageGroup" : 35,
|
|
"gender" : "f"
|
|
},
|
|
{
|
|
"ageGroup" : 35,
|
|
"gender" : "m"
|
|
},
|
|
{
|
|
"ageGroup" : 30,
|
|
"gender" : "f"
|
|
},
|
|
{
|
|
"ageGroup" : 30,
|
|
"gender" : "m"
|
|
},
|
|
{
|
|
"ageGroup" : 25,
|
|
"gender" : "f"
|
|
},
|
|
{
|
|
"ageGroup" : 25,
|
|
"gender" : "m"
|
|
}
|
|
]
|
|
|
|
Aggregation
|
|
-----------
|
|
|
|
So far we only grouped data without aggregation. Adding aggregation is simple in AQL,
|
|
as all that needs to be done is to run an aggregate function on the list created by
|
|
the `INTO` clause of a `COLLECT` statement:
|
|
|
|
FOR u IN users
|
|
FILTER u.active == true
|
|
COLLECT ageGroup = FLOOR(u.age / 5) * 5,
|
|
gender = u.gender INTO group
|
|
SORT ageGroup DESC
|
|
RETURN {
|
|
"ageGroup" : ageGroup,
|
|
"gender" : gender,
|
|
"numUsers" : LENGTH(group)
|
|
}
|
|
|
|
[
|
|
{
|
|
"ageGroup" : 35,
|
|
"gender" : "f",
|
|
"numUsers" : 2
|
|
},
|
|
{
|
|
"ageGroup" : 35,
|
|
"gender" : "m",
|
|
"numUsers" : 2
|
|
},
|
|
{
|
|
"ageGroup" : 30,
|
|
"gender" : "f",
|
|
"numUsers" : 4
|
|
},
|
|
{
|
|
"ageGroup" : 30,
|
|
"gender" : "m",
|
|
"numUsers" : 4
|
|
},
|
|
{
|
|
"ageGroup" : 25,
|
|
"gender" : "f",
|
|
"numUsers" : 2
|
|
},
|
|
{
|
|
"ageGroup" : 25,
|
|
"gender" : "m",
|
|
"numUsers" : 2
|
|
}
|
|
]
|
|
|
|
We have used the function `LENGTH` (returns the length of a list) here. This is the
|
|
equivalent to SQL's `SELECT g, COUNT(*) FROM ... GROUP BY g`.
|
|
In addition to `LENGTH`, AQL also provides `MAX`, `MIN`, `SUM`, and `AVERAGE` as
|
|
basic aggregation functions.
|
|
|
|
In AQL, all aggregation functions can be run on lists only. If an aggregation function
|
|
is run on anything that is not a list, an error will be thrown and the query will fail.
|
|
|
|
Post-filtering aggregated data ("Having")
|
|
-----------------------------------------
|
|
|
|
To filter on the results of a grouping or aggregation operation (i.e. something
|
|
similar to `HAVING` in SQL), simply add another `FILTER` clause after the `COLLECT`
|
|
statement.
|
|
|
|
For example, to get the 3 `ageGroup`s with the most users in them:
|
|
|
|
FOR u IN users
|
|
FILTER u.active == true
|
|
COLLECT ageGroup = FLOOR(u.age / 5) * 5 INTO group
|
|
LET numUsers = LENGTH(group)
|
|
FILTER numUsers > 2 /* group must contain at least 3 users in order to qualify */
|
|
SORT numUsers DESC
|
|
LIMIT 0, 3
|
|
RETURN {
|
|
"ageGroup" : ageGroup,
|
|
"numUsers" : numUsers,
|
|
"users" : group[*].u.name
|
|
}
|
|
|
|
[
|
|
{
|
|
"ageGroup" : 30,
|
|
"numUsers" : 8,
|
|
"users" : [
|
|
"Abigail",
|
|
"Madison",
|
|
"Anthony",
|
|
"Alexander",
|
|
"Isabella",
|
|
"Chloe",
|
|
"Daniel",
|
|
"Michael"
|
|
]
|
|
},
|
|
{
|
|
"ageGroup" : 25,
|
|
"numUsers" : 4,
|
|
"users" : [
|
|
"Mary",
|
|
"Mariah",
|
|
"Jim",
|
|
"Diego"
|
|
]
|
|
},
|
|
{
|
|
"ageGroup" : 35,
|
|
"numUsers" : 4,
|
|
"users" : [
|
|
"Fred",
|
|
"John",
|
|
"Emma",
|
|
"Sophia"
|
|
]
|
|
}
|
|
]
|
|
|
|
To increase readability, the repeated expression `LENGTH(group)` was put into a variable
|
|
`numUsers`. The `FILTER` on `numUsers` is the SQL HAVING equivalent.
|
|
|