# OPTIONAL CALL
{bdg-primary-line}`GQL clause` {bdg-secondary-line}`Added in 2.18`
Keeps an input row whose call produced no rows, binding everything the call would
have returned to null. It is the `CALL` counterpart of `OPTIONAL MATCH`.
## Synopsis
OPTIONAL CALL [ variable_scope ] { subquery }
OPTIONAL CALL function( [ argument [, argument ] ... ] ) YIELD yield_list
bold keywords · italic placeholders you supply · [ ] optional · ... repeatable. See Syntax conventions.
## Description
`OPTIONAL` applies to both forms of [`CALL`](call.md): the inline subquery and the
named function. A plain `CALL` joins the call's rows onto the pipeline, so an input
row that produced none drops out. `OPTIONAL CALL` lowers to a **left join**
instead, which preserves that row.
- Preservation is decided **per input row**. A correlated call that finds nothing
for one row nulls only that row and leaves the others alone.
- Every variable the call would have bound is nulled together, whatever its type:
vertex, edge, path, list, map or scalar.
- The binding is a real SQL null, not a jsonb `null`, so `IS NULL` is the way to
test it.
- `OPTIONAL` preserves rows; it does not invent them. An empty input still produces
no output.
- A preserved row is an ordinary row afterwards, so a later clause multiplies it
like any other.
## Parameters
variable_scope
: Which outer variables the subquery may see, exactly as for
[`CALL { subquery }`](call-subquery.md): `(v, w)`, `(*)`, `()`, or an importing
`WITH` inside the braces. The import rule applies unchanged. A bare
`OPTIONAL CALL { … }` imports nothing.
subquery
: A read-only query ending in `RETURN`.
function, argument, yield_list
: As for [`CALL … YIELD`](call-yield.md). `YIELD` is still mandatory.
## Examples
Every example runs against [the example dataset](../../get_started/example-dataset.md).
Three people: Keanu Reeves has four acting credits, Ryan Gosling one, Christopher
Nolan none. `OPTIONAL CALL` keeps the director:
```cypher
MATCH (p:person) WHERE p.name IN ['Keanu Reeves', 'Ryan Gosling', 'Christopher Nolan']
OPTIONAL CALL (p) { MATCH (p)-[:acted_in]->(m:movie) RETURN m.title AS film }
RETURN p.name AS person, film ORDER BY person, film;
```
```
person | film
---------------------+----------------------------
"Christopher Nolan" |
"Keanu Reeves" | "The Matrix"
"Keanu Reeves" | "The Matrix Reloaded"
"Keanu Reeves" | "The Matrix Resurrections"
"Keanu Reeves" | "The Matrix Revolutions"
"Ryan Gosling" | "Blade Runner 2049"
(6 rows)
```
Drop the `OPTIONAL` and the same body loses that row entirely, while the other two
people are unaffected:
```cypher
MATCH (p:person) WHERE p.name IN ['Keanu Reeves', 'Ryan Gosling', 'Christopher Nolan']
CALL (p) { MATCH (p)-[:acted_in]->(m:movie) RETURN m.title AS film }
RETURN p.name AS person, film ORDER BY person, film;
```
```
person | film
----------------+----------------------------
"Keanu Reeves" | "The Matrix"
"Keanu Reeves" | "The Matrix Reloaded"
"Keanu Reeves" | "The Matrix Resurrections"
"Keanu Reeves" | "The Matrix Revolutions"
"Ryan Gosling" | "Blade Runner 2049"
(5 rows)
```
Four rows, one row and one preserved row: the multiplicity is per input row, not a
property of the query.
### Preserve a row a function yielded nothing for
`OPTIONAL` sits in the same place on the named form, where it covers a function
that returns an empty set for some rows:
```cypher
MATCH (m:movie) WHERE m.title IN ['The Matrix', 'Avatar']
OPTIONAL CALL generate_series(1, CASE WHEN m.title = 'Avatar' THEN 0 ELSE 2 END)
YIELD generate_series AS n
RETURN m.title AS film, n ORDER BY film, n;
```
```
film | n
--------------+---
"Avatar" |
"The Matrix" | 1
"The Matrix" | 2
(3 rows)
```
`generate_series(1, 0)` is empty, so Avatar's row survives with `n` null. Without
`OPTIONAL` that row is gone and only The Matrix's two rows remain.
### Test a preserved binding
Every variable the body would have returned is null on a preserved row, and it is
a SQL null:
```cypher
MATCH (p:person {name: 'Christopher Nolan'})
OPTIONAL CALL (p) { MATCH (p)-[:acted_in]->(m:movie) RETURN m.title AS film, m.year AS year }
RETURN film IS NULL AS film_is_null, year IS NULL AS year_is_null;
```
```
film_is_null | year_is_null
--------------+--------------
t | t
(1 row)
```
### Confirm that no row is invented
`OPTIONAL` preserves input rows. With no input row there is nothing to preserve:
```cypher
MATCH (p:person {name: 'Nobody'})
OPTIONAL CALL (p) { MATCH (p)-[:acted_in]->(m:movie) RETURN m.title AS film }
RETURN p.name AS person, film;
```
```
person | film
--------+------
(0 rows)
```
## Notes
The plan states the mechanism outright. The `generate_series` scan is the nullable
side of a `Nested Loop Left Join`. That join preserves Avatar's row:
```cypher
EXPLAIN (COSTS OFF)
MATCH (m:movie) WHERE m.title IN ['The Matrix', 'Avatar']
OPTIONAL CALL generate_series(1, CASE WHEN m.title = 'Avatar' THEN 0 ELSE 2 END)
YIELD generate_series AS n
RETURN m.title AS film, n;
```
```
QUERY PLAN
-----------------------------------------------------------------------------------------
Nested Loop Left Join
-> Seq Scan on movie m
Filter: (properties.'title' = ANY ('{"\"The Matrix\"","\"Avatar\""}'::jsonb[]))
-> Function Scan on generate_series
(4 rows)
```
Some bodies always yield at least one row, and for those `OPTIONAL` changes
nothing. An ungrouped aggregate is the common case: it always produces one row,
so a person with no credits gets `0`, not null. Compare the `films` column in
[`CALL { subquery }`](call-subquery.md#aggregate-per-row-without-collapsing-the-pipeline).
`OPTIONAL` relaxes nothing else. A write clause in the body is still a syntax
error, a body that returns an outer name is still rejected, and a bare
`OPTIONAL CALL { … }` still imports nothing.
## Compatibility
```{admonition} GQL conformance
:class: gql-conformance
Implements the `OPTIONAL` prefix of the GQL *call procedure statement* (ISO/IEC
39075:2024 §15.1), which covers both the *inline procedure call* of §15.2 and the
*named procedure call* of §15.3.
- **Supported** — `OPTIONAL` on both call forms, with per-input-row preservation
and null binding of every returned variable.
- **Not supported** — `OPTIONAL` on a data-modifying call (§13.6), since a call
cannot write to the graph at all.
Track status in the [GQL conformance matrix](../../appendix/gql-conformance.md).
```
### openCypher
`OPTIONAL CALL` is a GQL construct; openCypher has only `OPTIONAL MATCH`. The two
agree wherever both can express the same thing, because the standard defines
`OPTIONAL MATCH p` as `OPTIONAL CALL (imports) { MATCH p RETURN vars }`. So
`OPTIONAL CALL` is the more general primitive, not a second mechanism.
## See also
- [`OPTIONAL MATCH`](optional-match.md) — the same preservation, on a pattern
- [`CALL`](call.md) {bdg-secondary-line}`GQL` — both call forms
- [`CALL { subquery }`](call-subquery.md) {bdg-secondary-line}`GQL` — the inline form and its import list
- [`CALL … YIELD`](call-yield.md) {bdg-secondary-line}`GQL` — the named-function form
- [GQL conformance matrix](../../appendix/gql-conformance.md)