CALL {} (subquery)
The
CALL {}
clause evaluates a subquery that returns some values.
CALL
allows to execute subqueries, i.e. queries inside of other queries.
Subqueries allow you to compose queries, which is especially useful when working with UNION
or aggregations.
The |
Subqueries which end in a RETURN
statement are called returning subqueries while subqueries without such a return statement are called unit subqueries.
A subquery is evaluated for each incoming input row. Every output row of a returning subquery is combined with the input row to build the result of the subquery. That means that a returning subquery will influence the number of rows. If the subquery does not return any rows, there will be no rows available after the subquery.
Unit subqueries on the other hand are called for their side-effects and not for their results and do therefore not influence the results of the enclosing query.
There are restrictions on how subqueries interact with the enclosing query:
-
A subquery can only refer to variables from the enclosing query if they are explicitly imported.
-
A subquery cannot return variables with the same names as variables in the enclosing query.
-
All variables that are returned from a subquery are afterwards available in the enclosing query.
The following graph is used for the examples below:
To recreate the graph, run the following query in an empty Neo4j database:
CREATE
(a:Person:Child {age: 20, name: 'Alice'}),
(b:Person {age: 27, name: 'Bob'}),
(c:Person:Parent {age: 65, name: 'Charlie'}),
(d:Person {age: 30, name: 'Dora'})
CREATE (a)-[:FRIEND_OF]->(b)
CREATE (a)-[:CHILD_OF]->(c)
CREATE (:Counter {count: 0})
Semantics
A CALL
clause is executed once for each incoming row.
The CALL
clause executes three times, one for each row that the UNWIND
clause outputs.
UNWIND [0, 1, 2] AS x
CALL {
RETURN 'hello' AS innerReturn
}
RETURN innerReturn
innerReturn |
---|
|
|
|
Rows:3 |
Each execution of a CALL
clause can observe changes from previous executions.
UNWIND [0, 1, 2] AS x
CALL {
MATCH (n:Counter)
SET n.count = n.count + 1
RETURN n.count AS innerCount
}
WITH innerCount
MATCH (n:Counter)
RETURN
innerCount,
n.count AS totalCount
innerReturn | totalCount |
---|---|
1 |
3 |
2 |
3 |
3 |
3 |
Rows:3 |
Importing variables into subqueries
Variables are imported into a subquery using an importing WITH
clause.
As the subquery is evaluated for each incoming input row, the imported variables get bound to the corresponding values from the input row in each evaluation.
UNWIND [0, 1, 2] AS x
CALL {
WITH x
RETURN x * 10 AS y
}
RETURN x, y
x | y |
---|---|
|
|
|
|
|
|
Rows: 3 |
An importing WITH
clause must:
-
Consist only of simple references to outside variables - e.g.
WITH x, y, z
. Aliasing or expressions are not supported in importingWITH
clauses - e.g.WITH a AS b
orWITH a+1 AS b
. -
Be the first clause of a subquery (or the second clause, if directly following a
USE
clause).
The order in which subqueries are executed is not defined.
If a query result depends on the order of execution of subqueries, an |
This query creates a linked list of all :Person
nodes in order of ascending age.
The CALL
clause is relying on the incoming row ordering to ensure that a correctly linked list is created, thus the incoming rows must be ordered with a preceding ORDER BY
clause.
MATCH (person:Person)
WITH person ORDER BY person.age ASC LIMIT 1
SET person:ListHead
WITH *
MATCH (next: Person)
WHERE NOT next:ListHead
WITH next ORDER BY next.age
CALL {
WITH next
MATCH (current:ListHead)
REMOVE current:ListHead
SET next:ListHead
CREATE(current)-[r:IS_YOUNGER_THAN]->(next)
RETURN current AS from, next AS to
}
RETURN
from.name AS name,
from.age AS age,
to.name AS closestOlderName,
to.age AS closestOlderAge
name | age | closestOlderName | closestOlderAge |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 3 |
Post-union processing
Subqueries can be used to process the results of a UNION
query further.
This example query finds the youngest and the oldest person in the database and orders them by name.
CALL {
MATCH (p:Person)
RETURN p
ORDER BY p.age ASC
LIMIT 1
UNION
MATCH (p:Person)
RETURN p
ORDER BY p.age DESC
LIMIT 1
}
RETURN p.name, p.age
ORDER BY p.name
p.name | p.age |
---|---|
|
|
|
|
Rows: 2 |
If different parts of a result should be matched differently, with some aggregation over the whole results, subqueries need to be used. This example query finds friends and/or parents for each person. Subsequently the number of friends and parents are counted together.
MATCH (p:Person)
CALL {
WITH p
OPTIONAL MATCH (p)-[:FRIEND_OF]->(other:Person)
RETURN other
UNION
WITH p
OPTIONAL MATCH (p)-[:CHILD_OF]->(other:Parent)
RETURN other
}
RETURN DISTINCT p.name, count(other)
p.name | count(other) |
---|---|
|
|
|
|
|
|
|
|
Rows: 4 |
Aggregations
Returning subqueries change the number of results of the query: The result of the CALL
clause is the combined result of evaluating the subquery for each input row.
The following example finds the name of each person and the names of their friends:
MATCH (p:Person)
CALL {
WITH p
MATCH (p)-[:FRIEND_OF]-(c:Person)
RETURN c.name AS friend
}
RETURN p.name, friend
p.name | friend |
---|---|
|
|
|
|
Rows: 2 |
The number of results of the subquery changed the number of results of the enclosing query: Instead of 4 rows, one for each node), there are now 2 rows which were found for Alice and Bob respectively. No rows are returned for Charlie and Dora since they have no friends in our example graph.
We can also use subqueries to perform isolated aggregations. In this example we count the number of relationships each person has.
As we get one row from each evaluation of the subquery, the number of rows is the same, before and after the CALL
clause:
MATCH (p:Person)
CALL {
WITH p
MATCH (p)--(c)
RETURN count(c) AS numberOfConnections
}
RETURN p.name, numberOfConnections
p.name | numberOfConnections |
---|---|
|
|
|
|
|
|
|
|
Rows: 4 |
Unit subqueries and side-effects
Unit subqueries do not return any rows and are therefore used for their side effects.
This example query creates five clones of each existing person. As the subquery is a unit subquery, it does not change the number of rows of the enclosing query.
MATCH (p:Person)
CALL {
WITH p
UNWIND range (1, 5) AS i
CREATE (:Person {name: p.name})
}
RETURN count(*)
count(*) |
---|
|
Rows: 1 |
Aggregation on imported variables
Aggregations in subqueries are scoped to the subquery evaluation, also for imported variables. The following example counts the number of younger persons for each person in the graph:
MATCH (p:Person)
CALL {
WITH p
MATCH (other:Person)
WHERE other.age < p.age
RETURN count(other) AS youngerPersonsCount
}
RETURN p.name, youngerPersonsCount
p.name | youngerPersonsCount |
---|---|
|
|
|
|
|
|
|
|
Rows: 4 |
Subqueries in transactions
Subqueries can be made to execute in separate, inner transactions, producing intermediate commits.
This can come in handy when doing large write operations, like batch updates, imports, and deletes.
To execute a subquery in separate transactions, you add the modifier IN TRANSACTIONS
after the subquery.
The following example uses a CSV file and the LOAD CSV
clause to import more data to the example graph.
It creates nodes in separate transactions using CALL { ... } IN TRANSACTIONS
:
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
LOAD CSV FROM 'file:///friends.csv' AS line
CALL {
WITH line
CREATE (:PERSON {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS
|
Rows: 0 |
As the size of the CSV file in this example is small, only a single separate transaction is started and committed.
|
Deleting a large volume of nodes
Using CALL { ... } IN TRANSACTIONS
is the recommended way of deleting a large volume of nodes.
MATCH (n)
CALL {
WITH n
DETACH DELETE n
} IN TRANSACTIONS
|
Rows: 0 |
The |
The CALL { ... } IN TRANSACTIONS
subquery should not be modified.
Any necessary filtering can be done before the subquery.
MATCH (n:Label) WHERE n.prop > 100
CALL {
WITH n
DETACH DELETE n
} IN TRANSACTIONS
|
Rows: 0 |
Batching
The amount of work to do in each separate transaction can be specified in terms of how many input rows
to process before committing the current transaction and starting a new one.
The number of input rows is set with the modifier OF n ROWS
(or ROW
).
If omitted, the default batch size is 1000
rows.
The following is the same example but with one transaction every 2
input rows:
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
LOAD CSV FROM 'file:///friends.csv' AS line
CALL {
WITH line
CREATE (:Person {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS OF 2 ROWS
|
Rows: 0 |
The query now starts and commits three separate transactions:
-
The first two executions of the subquery (for the first two input rows from
LOAD CSV
) take place in the first transaction. -
The first transaction is then committed before proceeding.
-
The next two executions of the subquery (for the next two input rows) take place in a second transaction.
-
The second transaction is committed.
-
The last execution of the subquery (for the last input row) takes place in a third transaction.
-
The third transaction is committed.
You can also use CALL { ... } IN TRANSACTIONS OF n ROWS
to delete all your data in batches in order to avoid a huge garbage collection or an OutOfMemory
exception.
For example:
MATCH (n)
CALL {
WITH n
DETACH DELETE n
} IN TRANSACTIONS OF 2 ROWS
|
Rows: 0 |
Up to a point, using a larger batch size will be more performant.
The batch size of |
Errors
If an error occurs in CALL { ... } IN TRANSACTIONS
the entire query fails and
both the current inner transaction and the outer transaction are rolled back.
On error, any previously committed inner transactions remain committed, and are not rolled back. |
In the following example, the last subquery execution in the second inner transaction fails due to division by zero.
UNWIND [4, 2, 1, 0] AS i
CALL {
WITH i
CREATE (:Example {num: 100/i})
} IN TRANSACTIONS OF 2 ROWS
RETURN i
/ by zero (Transactions committed: 1)
When the failure occurred, the first transaction had already been committed, so the database contains two example nodes.
MATCH (e:Example)
RETURN e.num
e.num |
---|
|
|
Rows: 2 |
Restrictions
These are the restrictions on queries that use CALL { ... } IN TRANSACTIONS
:
-
A nested
CALL { ... } IN TRANSACTIONS
inside aCALL { ... }
clause is not supported. -
A
CALL { ... } IN TRANSACTIONS
in aUNION
is not supported. -
A
CALL { ... } IN TRANSACTIONS
after a write clause is not supported, unless that write clause is inside aCALL { ... } IN TRANSACTIONS
.
Additionally, there are some restrictions that apply when using an importing WITH
clause in a CALL
subquery:
-
Only variables imported with the importing
WITH
clause can be used. -
No expressions or aliasing are allowed within the importing
WITH
clause. -
It is not possible to follow an importing
WITH
clause with any of the following clauses:DISTINCT
,ORDER BY
,WHERE
,SKIP
, andLIMIT
.
Attempting any of the above, will throw an error.
For example, the following query using a WHERE
clause after an importing WITH
clause will throw an error:
UNWIND [[1,2],[1,2,3,4],[1,2,3,4,5]] AS l
CALL {
WITH l
WHERE size(l) > 2
RETURN l AS largeLists
}
RETURN largeLists
Importing WITH should consist only of simple references to outside variables.
WHERE is not allowed.
A solution to this restriction, necessary for any filtering or ordering of an importing WITH
clause, is to declare a second WITH
clause after the importing WITH
clause.
This second WITH
clause will act as a regular WITH
clause.
For example, the following query will not throw an error:
UNWIND [[1,2],[1,2,3,4],[1,2,3,4,5]] AS l
CALL {
WITH l
WITH size(l) AS size, l AS l
WHERE size > 2
RETURN l AS largeLists
}
RETURN largeLists
largeLists |
---|
|
|
Rows: 2 |
Was this page helpful?