UNION#
GQL clause
Combines the results of two or more queries into one. INTERSECT and EXCEPT
are the same construct with the other two set operations.
Synopsis#
bold keywords · italic placeholders you supply · [ ] optional · ... repeatable. See Syntax conventions.
Description#
A set operator joins two complete queries, each with its own RETURN, into a
single result:
Operator |
Result |
|---|---|
|
Rows in either query. |
|
Rows in both queries. |
|
Rows in the first query and not in the second. |
Rules that apply to all three:
Every query must return the same number of columns, and corresponding columns must be comparable. Their names need not match; the result takes the names from the first query.
DISTINCTis the default: duplicate rows are removed.ALLkeeps them.UNIONandUNION DISTINCTmean the same thing.Operators chain, and a chain is evaluated left to right.
Row order is not defined. An
ORDER BYwritten after a set operation does not sort the combined result. See Notes for what does.Each query must be a complete query ending in
RETURN. An arm cannot end in a write clause or a bareMATCH.
Parameters#
- query
A complete query ending in a
RETURNwith the same number of columns as every other query in the chain.- ALL
Keep duplicate rows.
- DISTINCT
Remove duplicate rows. This is the default.
Examples#
Every example runs against the example dataset.
Combine two result sets#
Everyone connected to Inception, whichever way they are connected:
MATCH (p:person)-[:directed]->(:movie {title: 'Inception'}) RETURN p.name AS name
UNION
MATCH (p:person)-[:acted_in]->(:movie {title: 'Inception'}) RETURN p.name AS name;
name
---------------------
"Christopher Nolan"
"Leonardo DiCaprio"
(2 rows)
Keep or drop the duplicates#
Both arms here return the same 18 genres, one per film. UNION reduces them to
three:
MATCH (m:movie) RETURN m.genre AS genre
UNION
MATCH (m:movie) RETURN m.genre AS genre;
genre
-------------
"Action"
"Sci-Fi"
"Adventure"
(3 rows)
UNION DISTINCT is the same statement written out in full. UNION ALL keeps
every row from both arms:
MATCH (m:movie {genre: 'Adventure'}) RETURN m.title AS film
UNION ALL
MATCH (m:movie) WHERE m.year = 2021 RETURN m.title AS film;
film
----------------------------
"Jurassic Park"
"The Matrix Resurrections"
(2 rows)
Those two rows are distinct anyway, so ALL shows only in its cost. It skips
the deduplication.
Take the rows in both queries#
MATCH (m:movie {genre: 'Action'}) RETURN m.title AS film
INTERSECT
MATCH (m:movie) WHERE m.year < 1995 RETURN m.title AS film;
film
------------------------------
"The Terminator"
"Terminator 2: Judgment Day"
(2 rows)
Subtract one query from another#
The people who directed something but never acted:
MATCH (p:person)-[:directed]->(m:movie) RETURN p.name AS name
EXCEPT
MATCH (p:person)-[:acted_in]->(m:movie) RETURN p.name AS name;
name
---------------------
"Andrew Niccol"
"Neill Blomkamp"
"Lana Wachowski"
"Lilly Wachowski"
"Joss Whedon"
"James Cameron"
"Luc Besson"
"Denis Villeneuve"
"Christopher Nolan"
"Paul Verhoeven"
"Ridley Scott"
"Steven Spielberg"
(12 rows)
The rows come back in no particular order.
Sort and page the combined result#
Put the set operation in a NEXT chain and sort the second half of the
chain, which receives the combined rows:
MATCH (m:movie) WHERE m.genre = 'Adventure' RETURN m.title AS film
UNION
MATCH (m:movie) WHERE m.genre = 'Sci-Fi' RETURN m.title AS film
NEXT ORDER BY film LIMIT 3 RETURN film;
film
---------------------
"Avatar"
"Blade Runner"
"Blade Runner 2049"
(3 rows)
A CALL { subquery } wrapper does the same job:
CALL { MATCH (m:movie) WHERE m.genre = 'Adventure' RETURN m.title AS film
UNION
MATCH (m:movie) WHERE m.genre = 'Sci-Fi' RETURN m.title AS film }
RETURN film ORDER BY film LIMIT 3;
film
---------------------
"Avatar"
"Blade Runner"
"Blade Runner 2049"
(3 rows)
Reject a mismatched column count#
MATCH (m:movie) RETURN m.title AS t
UNION
MATCH (p:person) RETURN p.name AS n, p.born AS b;
ERROR: each UNION query must have the same number of columns
The message names UNION for all three operators.
Notes#
An ORDER BY, SKIP or LIMIT written after a set operation attaches to the
last query in the chain, not to the combined result. It therefore sorts or
pages that one arm before the sets are combined. LIMIT 1 here does not produce
one row:
MATCH (m:movie) WHERE m.genre = 'Adventure' RETURN m.title AS film
UNION
MATCH (m:movie) WHERE m.genre = 'Sci-Fi' RETURN m.title AS film
LIMIT 1;
film
-----------------
"Inception"
"Jurassic Park"
(2 rows)
One row survived the LIMIT in the Sci-Fi arm, which returns 10 films without
it. The single Adventure film joined that row afterwards. Alias resolution
follows the same rule, so a sort key must name a column of the last arm:
MATCH (m:movie) RETURN m.title AS first_col
UNION
MATCH (p:person) RETURN p.name AS second_col
ORDER BY first_col LIMIT 2;
ERROR: variable does not exist
LINE 4: ORDER BY first_col LIMIT 2;
^
Use the NEXT or CALL form shown above whenever you need to sort or page the
combined rows. In SQL, a SELECT over the set operation does it too:
SELECT * FROM (
MATCH (m:movie) WHERE m.genre = 'Adventure' RETURN m.title AS film
UNION
MATCH (m:movie) WHERE m.genre = 'Sci-Fi' RETURN m.title AS film
) q ORDER BY film LIMIT 3;
film
---------------------
"Avatar"
"Blade Runner"
"Blade Runner 2049"
(3 rows)
Compatibility#
Set operators (ISO/IEC 39075 §14.2)#
The standard’s <set operator> is
UNION [ <set quantifier> ] | EXCEPT [ <set quantifier> ] | INTERSECT [ <set quantifier> ],
with DISTINCT implicit when no quantifier is given. AgensGraph implements all
three operators and both quantifiers, on exactly those rules. openCypher has only
UNION and UNION ALL, so INTERSECT and EXCEPT are available here and not
there.
The standard’s OTHERWISE conjunction, which takes the second query’s rows only
when the first returns none, is not implemented.
Sorting a combined result (ISO/IEC 39075 §14.9)#
The standard applies an <order by and page statement> to the current working
table, which after a composite query is the combined result. AgensGraph attaches
it to the last query of the chain instead, as shown in
Notes. Wrap the set operation in NEXT, in a CALL { }, or in a SQL
SELECT to sort or page what the operation produced.
See also#
RETURN— every arm of a set operation ends with oneNEXTGQL — chain a further query onto the combined rowsCALL { subquery }— the other way to wrap a set operationORDER BY,SKIPandLIMIT— what they attach to hereCombining SQL and Cypher — sorting a set operation from SQL