Conditional queries (WHEN)Cypher 25 onlyIntroduced in Neo4j 2025.06
WHEN, together with THEN and ELSE, enables different branches of a query to execute based on certain conditions.
In this way, it performs similar a control-flow mechanism to the IF statement in other programming languages.
For information about using WHEN branches in expressions, see Conditional expressions (CASE)
|
Example graph
The following graph is used for the examples below:
To recreate the graph, run the following query against an empty Neo4j database:
CREATE (alice:Person {name:'Alice', age: 65}),
(bob:Person {name: 'Bob', age: 25}),
(charlie:Person {name: 'Charlie', age: 61}),
(daniel:Person {name: 'Daniel', age: 39}),
(eskil:Person {name: 'Eskil', age: 39}),
(bob)-[:WORKS_FOR]->(alice),
(alice)-[:WORKS_FOR]->(daniel),
(charlie)-[:WORKS_FOR]->(daniel),
(bob)-[:LOVES]->(eskil),
(charlie)-[:LOVES]->(alice)
Standalone WHEN branches
WHEN branchesWHEN predicate THEN [{]
<conditionalQuery>
[}]
[WHEN ...]*
[ELSE [{]
<conditionalQuery>
[}]]
The first branch with a predicate that evaluates to true will be executed.
If no WHEN branches are executed and an ELSE branch exists, it is executed.
If no WHEN branches evaluates to true and no ELSE branch is present, no branches are executed and no rows are produced.
The following examples demonstrates this logic:
WHEN false THEN RETURN 1 AS x
WHEN true THEN RETURN 2 AS x
WHEN true THEN RETURN 3 AS x
ELSE RETURN 3 AS x
Since the second WHEN branch is true, it will execute, while the preceding branch (which is false) and the succeeding WHEN branch (which is true) as well as the ELSE branch will be skipped.
| x |
|---|
|
Rows: 1 |
WHEN branchesWHEN true THEN {
MATCH (n:Person) WHERE n.name STARTS WITH "A"
RETURN n.name AS name
}
ELSE {
MATCH (n:Person)
RETURN n.name AS name
}
The enclosing {} in the above example and the below examples are not required (unless explicitly stated), but they clarify the different conditional branches of the query.
|
| name |
|---|
|
Rows: 1 |
Rules
Similar to UNION, the number and names of the columns must be identical in all parts of a WHEN/ELSE construct.
This means that expressions returned must be aliased using AS.
WHEN true THEN RETURN 2
ELSE RETURN 3
WHEN true THEN RETURN 2 AS x
ELSE RETURN 3 AS y
WHEN true THEN RETURN 2 AS x, 3 AS y
ELSE RETURN 3 AS x
WHEN can also not be positioned as a regular clause in a query.
For example, it cannot immediately succeed a MATCH clause.
WHEN constructs as regular clausesMATCH (n)-[:WORKS_FOR]->(manager:Person)
WHEN manager IS NULL THEN {
MERGE (n)-[:WORKS_FOR]->(newManager: Person {name: 'Peter', age: 36})
}
RETURN n.name AS employees,
m.name AS manager,
newManager.name AS newManagerNode
Instead, if WHEN constructs are part of a larger query, they must either be placed within a subquery and/or on different sides of combined UNION queries.
Conditional subqueries
WHEN can be used inside one or several CALL subqueries to execute a set of operations only when a specified condition evaluates to true.
CALL subqueries[<outerQuery>]
<callSubquery> {
WHEN predicate THEN [{]
<conditionalQuery>
[}]
[WHEN ...]*
[ELSE [{]
<conditionalQuery>
[}]]
}
[<callSubquery> ...]*
[<outerQuery>]
CALL subqueryIn this example, WHEN is used to execute a CALL subquery for each row that the condition (manager IS NULL) evaluates to true.
CALL subqueryMATCH (n:Person)
OPTIONAL MATCH (n)-[:WORKS_FOR]->(manager:Person)
CALL (*) {
WHEN manager IS NULL THEN {
MERGE (newManager: Person {name: 'Peter', age: 36})
MERGE (n)-[:WORKS_FOR]->(newManager)
RETURN newManager, n.name AS employee
}
}
RETURN newManager.name AS newManager,
collect(employee) AS employees
Because only Daniel and Eskil had no outgoing WORKS_FOR relationships, they have now been connected as employees of the new Peter node.
| newManager | employees |
|---|---|
|
|
Rows: 1 |
|
CALL subqueriesDisjointed conditional CALL subqueries be chained in succession, each executing depending on a set of defined conditions.
This example uses conditional logic to assign age groups to individuals.
The second CALL then collects the names and age groupings of their managers (who they WORK_FOR), but only for individuals whose manager is older than them.
CALL subqueriesMATCH (n:Person)
OPTIONAL MATCH (n)-[r:WORKS_FOR]->(m:Person)
CALL (*) {
WHEN n.age > 60 THEN {
SET n.ageGroup = 'Veteran'
RETURN n.ageGroup AS ageGroup
}
WHEN n.age >= 35 AND n.age <= 59 THEN {
SET n.ageGroup = 'Senior'
RETURN n.ageGroup AS ageGroup
}
ELSE {
SET n.ageGroup = 'Junior'
RETURN n.ageGroup AS ageGroup
}
}
CALL (*) {
WHEN m.age > n.age THEN {
RETURN collect([m.name, m.ageGroup]) AS manager
}
}
RETURN n.name AS name, ageGroup, manager
Bob is returned because he is the only person in the graph with an older manager.
| name | ageGroup | manager |
|---|---|---|
|
|
|
Rows: 1 |
||
EXISTS, COLLECT, and COUNT subqueries[<outerExpression>]
EXISTS|COUNT|COLLECT {
WHEN predicate THEN [{]
<conditionalQuery>
[}]
[WHEN ...]*
[ELSE [{]
<conditionalQuery>
[}]]
}
[<outerExpression>]
EXISTS subqueryIn this example, WHEN is used inside an EXISTS subquery to conditionally execute different branches based on the evaluation of the predicate (n.age > 40).
Unlike CALL subqueries, variables returned in an EXISTS subquery are not available to the outer scope (the same is true for COUNT and COLLECT subqueries).
|
WHEN inside an EXISTS subqueryMATCH (n:Person)
WHERE EXISTS {
WHEN n.age > 40 THEN {
RETURN n.name AS x
}
ELSE {
MATCH (n)-[:LOVES]->(x:Person)
RETURN x
}
}
RETURN n.name AS name,
n.age AS age
Alice and Charlie are both older than 40, so they are returned by the WHEN branch, while Bob is returned by the ELSE branch.
Note that some Person nodes in the graph are not matched in either branch of the conditional subquery, and are therefore not returned.
| name | age |
|---|---|
|
|
|
|
|
|
Rows: 3 |
|
Combining conditional queries with UNION
The results of multiple conditional queries can also be combined using UNION [DISTINCT] or UNION ALL; the former alternative removes duplicates from the result set, the latter does not (for more information, see Combined queries (UNION)).
If the conditional query begins with WHEN and involves UNION, the WHEN branches must be enclosed within curly braces, {} (the same {} can also be used to combine UNION and UNION ALL).
WHEN branches with UNION using {}{
WHEN predicate THEN [{]
<conditionalQuery>
[}]
[WHEN ...]*
[ELSE [{]
<conditionalQuery>
[}]]
}
UNION [DISTINCT|ALL]
{
WHEN predicate THEN [{]
<conditionalQuery>
[}]
[WHEN ...]*
[ELSE [{]
<conditionalQuery>
[}]]
}
[UNION [DISTINCT|ALL] ...]*
UNION using {}{
WHEN true THEN RETURN 1 AS x
WHEN false THEN RETURN 2 AS x
ELSE RETURN 3 AS x
}
UNION
{
WHEN false THEN RETURN 4 AS x
WHEN false THEN RETURN 5 AS x
ELSE RETURN 6 AS x
}
| x |
|---|
|
|
Rows: 2 |
If the queries combined by the UNION do not begin with a WHEN branch, then enclosing curly braces are not necessary.
UNIONIn the example below, UNION is used to combine the results of two conditional subqueries (the first CALL, enclosing the whole query, is necessary to enable the collection for each person from each conditional subquery part in the final results).
The query categorizes people based on two conditions: whether they have a LOVES relationship and whether they are younger or older than 40.
The final result provides a list of status messages for each person, showing the combined outcome of both conditions.
UNIONMATCH (n:Person)
CALL (n) {
OPTIONAL MATCH (n)-[r:LOVES]->(m:Person)
CALL (*) {
WHEN r IS NULL THEN {
RETURN n.name AS person, "Loves no one" AS message
}
ELSE {
RETURN n.name AS person, "Loves somebody" AS message
}
}
RETURN person, message
UNION
CALL (*) {
WHEN n.age < 40 THEN {
RETURN n.name AS person, "Under 40" AS message
}
ELSE {
RETURN n.name AS person, "40 or older" AS message
}
}
RETURN person, message
}
RETURN person, collect(message) AS status
| person | status |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 6 |
|