CALL clause

The CALL clause is used to call a subquery inside the existing query.

MAGE procedures are also run with a query with the CALL clause at the beginning. Switch to MAGE documentation if you want to CALL a graph algorithm or some other procedure from the MAGE library.

  1. Uses of CALL subquery
    1.1. Cartesian products
    1.2. Cartesian products with bounded symbols
    1.3. Post-union processing
    1.4. Observing changes from previous executions
    1.5. Unit subqueries
    1.6. Scoped variables with CALL (...)
    1.7. Optional subqueries with OPTIONAL CALL
    1.8. Conditional subqueries with WHEN

  2. Invalid uses of CALL subquery
    2.1. Returning variables with the same name as those in the outer scope
    2.2. Returning non-aliased expressions
    2.3. Referencing outer scope variables that don’t exist
    2.4. Using OPTIONAL with a procedure call

1. Uses of CALL subquery

1.1. Cartesian products

CALL subquery is executed once for each incoming row. If multiple rows are produced from the CALL subquery, the result is a Cartesian product of results. It is an output combined from 2 branches, one being called the input branch (rows produced before calling the subquery), and the subquery branch (rows produced by the subquery).
Imagine the data includes two :Person nodes, one named John and one named Alice, as well as two :Animal nodes, one named Rex and one named Lassie.

Running the following query would produce the output below:

MATCH (p:Person)
CALL {
  MATCH (a:Animal)
  RETURN a.name as animal_name
}
RETURN p.name as person_name, animal_name

Output:

+-------------+-------------+
| person_name | animal_name |
+---------------------------+
| 'John'      | 'Rex'       |
| 'John'      | 'Lassie'    |
| 'Alice'     | 'Rex'       |
| 'Alice'     | 'Lassie'    |
+---------------------------+

1.2. Cartesian products with bounded symbols

To reference variables from the outer scope in the subquery, start the subquery with the WITH clause. It allows using the same symbols to expand on the neighborhood of the referenced nodes or relationships. Otherwise, the subquery will behave as it sees the variable for the first time.

In the following query, the WITH clause expanded the meaning of the variable person to the node with the label :Person matched in the outer scope of the subquery:

MATCH (person:Person)
CALL {
  WITH person
  MATCH (person)-[:HAS_PARENT]->(parent:Parent)
  RETURN parent
}
RETURN person.name, parent.name

Output:

+-------------+-------------+
| person_name | parent_name |
+---------------------------+
| 'John'      | 'John Sr.'  |
| 'John'      | 'Anna'      |
| 'Alice'     | 'Roxanne'   |
| 'Alice'     | 'Bill'      |
+---------------------------+

1.3. Post-union processing

Output from all UNION queries inside a subquery can be combined and forwarded as a single output to make the queries more expressive:

CALL {
	MATCH (n:Person)
	RETURN n.name AS name, n.ssn AS ID_number
		UNION
	MATCH (n:Company)
	RETURN n.name AS name, n.corporate_id AS ID_number
}
RETURN name, ID_number

Output:

+------------+-------------+
| name       | ID_number   |
+--------------------------+
| 'John'     | '123456789' |
| 'Memgraph' | '555555555' |
+--------------------------+

1.4. Observing changes from previous executions

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

Output:

+------------+-------------+
| innerCount | totalCount  |
+--------------------------+
| 1          | 3           |
| 2          | 3           |
| 3          | 3           |
+--------------------------+

1.5. Unit subqueries

Unit subqueries are used to perform a single action for every node from the input branch. If the starting state of the database is that there is only one :Person node in the graph, the following query will clone the node with desired preferences defined in the FOREACH clause.

MATCH (p:Person)
CALL {
	FOREACH (i IN range(1, 5) | CREATE(:Person {id: i}))
}
 
MATCH (n) RETURN COUNT(n) AS no_created_nodes;

Output:

+------------------+
| no_created_nodes |
+------------------+
| 6                |
+------------------+

1.6. Scoped variables with CALL (...)

Instead of importing outer-scope variables with a leading WITH clause, you can list them in parentheses directly after CALL. Three forms are supported:

  • CALL () { ... } — no outer variables are imported. The subquery sees only what it produces internally.
  • CALL (v1, v2, ...) { ... } — only the listed variables are imported.
  • CALL (*) { ... } — every variable currently in the outer scope is imported.

Importing only p from the outer scope:

MATCH (p:Player)-[:PLAYS_FOR]->(t:Team)
CALL (p) {
  WITH p.age / 100.0 AS rating
  SET p.rating = rating
  RETURN p.rating AS rating
}
RETURN p.name AS playerName, rating, t AS team;

Importing every outer variable with CALL (*):

MATCH (p:Player)-[:PLAYS_FOR]->(t:Team)
CALL (*) {
  SET p.lastUpdated = timestamp()
  SET t.lastUpdated = timestamp()
}
RETURN p.name, t.name;

Running a subquery that does not depend on the outer row with CALL ():

UNWIND [0, 1, 2] AS x
CALL () {
  RETURN 'hello' AS innerReturn
}
RETURN x, innerReturn;

The scoped form is equivalent to using WITH to import variables, but narrows the subquery’s scope explicitly at the boundary, which makes the import list visible at a glance.

1.7. Optional subqueries with OPTIONAL CALL

CALL drops an input row when the subquery returns no rows for it. OPTIONAL CALL keeps that row and sets the columns the subquery returns to null, which is the same contract as OPTIONAL MATCH.

Imagine the data from 1.2, plus a third :Person named Carol who has no :HAS_PARENT relationship:

MATCH (person:Person)
OPTIONAL CALL (person) {
  MATCH (person)-[:HAS_PARENT]->(parent:Parent)
  RETURN parent.name AS parent_name
}
RETURN person.name AS person_name, parent_name

Output:

+-------------+-------------+
| person_name | parent_name |
+---------------------------+
| 'John'      | 'John Sr.'  |
| 'John'      | 'Anna'      |
| 'Alice'     | 'Roxanne'   |
| 'Alice'     | 'Bill'      |
| 'Carol'     | null        |
+---------------------------+

Without OPTIONAL, Carol does not appear in the result at all.

OPTIONAL can be combined with every scope form (CALL (v1, v2), CALL (*), CALL (), or no scope clause), with a UNION subquery, with nested subqueries, and with IN TRANSACTIONS.

Variables imported from the outer scope keep their values; only the columns the subquery itself introduces are set to null. A unit subquery returns no columns, so OPTIONAL has nothing to set to null and the number of rows is the same either way.

1.8. Conditional subqueries with WHEN

A conditional subquery runs, for each input row, the first branch whose predicate is true:

CALL (...) {
  WHEN predicate THEN body
  [WHEN predicate THEN body ...]
  [ELSE body]
}
UNWIND [{name: 'Ana', age: 65}, {name: 'Ivan', age: 25}, {name: 'Marko', age: 39}] AS person
CALL (person) {
  WHEN person.age > 60 THEN RETURN 'senior' AS bracket
  WHEN person.age > 30 THEN RETURN 'adult' AS bracket
  ELSE RETURN 'young' AS bracket
}
RETURN person.name AS name, bracket;

Output:

+---------+----------+
| name    | bracket  |
+---------+----------+
| "Ana"   | "senior" |
| "Ivan"  | "young"  |
| "Marko" | "adult"  |
+---------+----------+

A body is a single query, or a query in braces that may contain UNION or another WHEN:

UNWIND [1, 2] AS i
CALL (i) {
  WHEN i = 1 THEN {
    RETURN 'a' AS x
    UNION
    RETURN 'b' AS x
  }
  ELSE {
    WHEN i > 5 THEN RETURN 'big' AS x
    ELSE RETURN 'small' AS x
  }
}
RETURN i, x;

Output:

+---+---------+
| i | x       |
+---+---------+
| 1 | "a"     |
| 1 | "b"     |
| 2 | "small" |
+---+---------+

When no branch matches a row, the result depends on the body:

  • A body that returns rows drops the input row, as a CALL subquery that returns no rows does. OPTIONAL CALL keeps the row and sets the returned columns to null.
  • A body without RETURN, such as one that only updates the graph, keeps every input row, as a unit subquery does.
UNWIND [1, 2, 3] AS i
CALL (i) {
  WHEN i = 1 THEN CREATE (:Tier {i: i, level: 'gold'})
  WHEN i = 2 THEN CREATE (:Tier {i: i, level: 'silver'})
}
RETURN i;

The query returns the rows 1, 2 and 3, and creates two :Tier nodes.

The branches of one conditional subquery must agree:

  • All branches return rows, all branches update the graph without RETURN, or all branches are a standalone procedure call.
  • Branches that return rows return the same column names. The order of the columns can differ.

A conditional subquery needs a scope clause: CALL { WHEN ... } is an error, and CALL () { WHEN ... } is valid. A branch cannot set a query memory limit or start with USING. The whole conditional subquery can run IN TRANSACTIONS.

2. Invalid uses of CALL subquery

2.1. Returning variables with the same name as those in the outer scope

Invalid use:

MATCH (n:Person)
CALL {
  MATCH (n:Parent)
  RETURN n
}
RETURN n;

The above query results in a semantic exception because the variable n has already been used in the outer scope of the query. The query will successfully execute by renaming either the outer scope variable or the subquery variable.

Valid use:

MATCH (n:Person)
CALL {
  MATCH (p:Parent)
  RETURN p
}
RETURN n, p;

2.2. Returning non-aliased expressions

Invalid use:

MATCH (n:Person)
CALL {
  WITH n
  MATCH (n)-[:HAS_PARENT]->(parent:Parent)
  RETURN parent.age
}
RETURN n, parent.age;

The above query results in a semantic exception since the expression returned in the subquery has not been aliased and can not be interpreted correctly. By aliasing the returned expression upon exiting the subquery, it can be used in the outer scope.

Valid use:

MATCH (n:Person)
CALL {
  WITH n
  MATCH (n)-[:HAS_PARENT]->(parent:Parent)
  RETURN parent.age AS parent_age
}
RETURN n, parent_age;

2.3. Referencing outer scope variables that don’t exist

Invalid use:

MATCH (n:Person)
CALL {
  WITH o
  MATCH (o)-[:HAS_CHILD]->(child:Parent)
  RETURN child
}
RETURN DISTINCT n;

The above query results in a semantic exception because the variable from the outer scope does not exist. Queries can be executed only by referencing variables bounded in the input branch to the subquery. By renaming the variable to the already bounded variable n, the query will be correctly executed.

Valid use:

MATCH (n:Person)
CALL {
  WITH n
  MATCH (n)-[:HAS_CHILD]->(child:Parent)
  RETURN child
}
RETURN DISTINCT n;

2.4. Using OPTIONAL with a procedure call

OPTIONAL applies to a CALL subquery, not to a procedure call:

MATCH (p:Person)
OPTIONAL CALL algo.procedure(p)
YIELD result
RETURN p.name, result;

The above query results in an error. Wrap the procedure call in a subquery instead:

MATCH (p:Person)
OPTIONAL CALL (p) {
  CALL algo.procedure(p) YIELD result
  RETURN result
}
RETURN p.name, result;