Match
MATCH is the clause where you define a “graph pattern”, i.e., a join of node or relationship records, to find in the database1. MATCH is often accompanied by WHERE (equivalent to SQL’s WHERE clause) to define more predicates on the patterns that are matched.
We will use the example database for demonstration, whose schema and data import commands are given here.
Match nodes
Match nodes with a single label
The query below matches variable “a” to nodes with label User and returns “a”, which is a shortcut in openCypher to return all properties of the node together with label and internal ID that variable “a” matches.
MATCH (a:User)RETURN a;
Output:
----------------------------------------------------| a |----------------------------------------------------| {_ID: 0:0, _LABEL: User, name: Adam, age: 30} |----------------------------------------------------| {_ID: 0:1, _LABEL: User, name: Karissa, age: 40} |----------------------------------------------------| {_ID: 0:2, _LABEL: User, name: Zhang, age: 50} |----------------------------------------------------| {_ID: 0:3, _LABEL: User, name: Noura, age: 25} |----------------------------------------------------
Match nodes with multiple labels
The query below matches variable “a” to nodes with label User or label City. “Return a” will return all properties of the node together with label and internal ID. Properties not exist in a label will be returned as NULL value (e.g. “population” not exists in “User”). Properties exists in multiple labels are expected to have the same data type (e.g. “name” has STRING data type in “User” and “City” ).
MATCH (a:User:City)RETURN a;
Output:
-----------------------------------------------------------------| a |-----------------------------------------------------------------| {_ID: 1:0, _LABEL: City, name: Waterloo, population: 150000} |-----------------------------------------------------------------| {_ID: 1:1, _LABEL: City, name: Kitchener, population: 200000} |-----------------------------------------------------------------| {_ID: 1:2, _LABEL: City, name: Guelph, population: 75000} |-----------------------------------------------------------------| {_ID: 0:0, _LABEL: User, name: Adam, age: 30, } |-----------------------------------------------------------------| {_ID: 0:1, _LABEL: User, name: Karissa, age: 40, } |-----------------------------------------------------------------| {_ID: 0:2, _LABEL: User, name: Zhang, age: 50, } |-----------------------------------------------------------------| {_ID: 0:3, _LABEL: User, name: Noura, age: 25, } |-----------------------------------------------------------------
Match nodes with any label
Below query matches variable “a” to nodes with any label. In example database, it is equivalent to MATCH (a:User:City) RETURN a;
.
MATCH (a)RETURN a;
Output:
-----------------------------------------------------------------| a |-----------------------------------------------------------------| {_ID: 1:0, _LABEL: City, name: Waterloo, population: 150000} |-----------------------------------------------------------------| {_ID: 1:1, _LABEL: City, name: Kitchener, population: 200000} |-----------------------------------------------------------------| {_ID: 1:2, _LABEL: City, name: Guelph, population: 75000} |-----------------------------------------------------------------| {_ID: 0:0, _LABEL: User, name: Adam, age: 30, } |-----------------------------------------------------------------| {_ID: 0:1, _LABEL: User, name: Karissa, age: 40, } |-----------------------------------------------------------------| {_ID: 0:2, _LABEL: User, name: Zhang, age: 50, } |-----------------------------------------------------------------| {_ID: 0:3, _LABEL: User, name: Noura, age: 25, } |-----------------------------------------------------------------
Match relationships
Match directed relationships with a label
Similar to binding variables to node records, you can bind variables to relationship records and return them. You can specify the direction of relationship by <-
or ->
. The following query finds all “a” Users that follow a “b” User through an outgoing relationship from “a”, and returns name of “a”, relationship “e”, and name of “b”, where “e” will match the relationship from “a” to “b”.
MATCH (a:User)-[e:Follows]->(b:User)RETURN a.name, e, b.name;
Output:
-----------------------------------------------------------------------------| a.name | e | b.name |-----------------------------------------------------------------------------| Adam | (0:0)-{_LABEL: Follows, _ID: 2:0, since: 2020}->(0:1) | Karissa |-----------------------------------------------------------------------------| Adam | (0:0)-{_LABEL: Follows, _ID: 2:1, since: 2020}->(0:2) | Zhang |-----------------------------------------------------------------------------| Karissa | (0:1)-{_LABEL: Follows, _ID: 2:2, since: 2021}->(0:2) | Zhang |-----------------------------------------------------------------------------| Zhang | (0:2)-{_LABEL: Follows, _ID: 2:3, since: 2022}->(0:3) | Noura |-----------------------------------------------------------------------------
The following query matches all the relationships through an incoming relationship from “a” (so “a” and “b” are swapped in output):
MATCH (a:User)<-[e:Follows]-(b:User)RETURN a.name, e, b.name;
Output:
-----------------------------------------------------------------------------| a.name | e | b.name |-----------------------------------------------------------------------------| Karissa | (0:0)-{_LABEL: Follows, _ID: 2:0, since: 2020}->(0:1) | Adam |-----------------------------------------------------------------------------| Zhang | (0:0)-{_LABEL: Follows, _ID: 2:1, since: 2020}->(0:2) | Adam |-----------------------------------------------------------------------------| Zhang | (0:1)-{_LABEL: Follows, _ID: 2:2, since: 2021}->(0:2) | Karissa |-----------------------------------------------------------------------------| Noura | (0:2)-{_LABEL: Follows, _ID: 2:3, since: 2022}->(0:3) | Zhang |-----------------------------------------------------------------------------
Match relationships with multi labels
Similar to matching nodes with multiple labels, you can bind variables to relationships with multiple labels. Below query finds all “a” User that Follows “b” User or LivesIn “b” City.
MATCH (a:User)-[e:Follows|:LivesIn]->(b:User:City)RETURN a.name, e, b.name;
Output:
-------------------------------------------------------------------------------| a.name | e | b.name |-------------------------------------------------------------------------------| Adam | (0:0)-{_LABEL: Follows, _ID: 2:0, since: 2020}->(0:1) | Karissa |-------------------------------------------------------------------------------| Adam | (0:0)-{_LABEL: Follows, _ID: 2:1, since: 2020}->(0:2) | Zhang |-------------------------------------------------------------------------------| Adam | (0:0)-{_LABEL: LivesIn, _ID: 3:0, }->(1:0) | Waterloo |-------------------------------------------------------------------------------| Karissa | (0:1)-{_LABEL: Follows, _ID: 2:2, since: 2021}->(0:2) | Zhang |-------------------------------------------------------------------------------| Karissa | (0:1)-{_LABEL: LivesIn, _ID: 3:1, }->(1:0) | Waterloo |-------------------------------------------------------------------------------| Zhang | (0:2)-{_LABEL: Follows, _ID: 2:3, since: 2022}->(0:3) | Noura |-------------------------------------------------------------------------------| Zhang | (0:2)-{_LABEL: LivesIn, _ID: 3:2, }->(1:1) | Kitchener |-------------------------------------------------------------------------------| Noura | (0:3)-{_LABEL: LivesIn, _ID: 3:3, }->(1:2) | Guelph |-------------------------------------------------------------------------------
Match relationships with any label
Similar to matching nodes with any label, you can bind variables to relationships with any label by not specifying a label. Below query finds all relationships in the database.
MATCH ()-[e]->()RETURN e;
Output:
---------------------------------------------------------| e |---------------------------------------------------------| (0:0)-{_LABEL: Follows, _ID: 2:0, since: 2020}->(0:1) |---------------------------------------------------------| (0:0)-{_LABEL: Follows, _ID: 2:1, since: 2020}->(0:2) |---------------------------------------------------------| (0:0)-{_LABEL: LivesIn, _ID: 3:0, }->(1:0) |---------------------------------------------------------| (0:1)-{_LABEL: Follows, _ID: 2:2, since: 2021}->(0:2) |---------------------------------------------------------| (0:1)-{_LABEL: LivesIn, _ID: 3:1, }->(1:0) |---------------------------------------------------------| (0:2)-{_LABEL: Follows, _ID: 2:3, since: 2022}->(0:3) |---------------------------------------------------------| (0:2)-{_LABEL: LivesIn, _ID: 3:2, }->(1:1) |---------------------------------------------------------| (0:3)-{_LABEL: LivesIn, _ID: 3:3, }->(1:2) |---------------------------------------------------------
Match undirected relationships
Users can match a relationship in both directions by not specifying a relationship direction (i.e. -
). The following query finds all “b” users who either follows or being followed by “Karissa”.
MATCH (a:User)-[e:Follows]-(b:User) Where a.name = 'Karissa' RETURN b.name;
Output:
----------| b.name |----------| Adam |----------| Zhang |----------
Omit binding variables to nodes or relationships
You can also omit binding a variable to a node or relationship in your graph patterns if you will not use them in somewhere else in your query (e.g., WHERE or RETURN). For example, below, we query for 2-hop paths searching for “the cities of Users that “a” Follows”. Because we do not need to return the Users that “a” Users follows or the properties of the Follows and LivesIn edges that form these 2-paths, we can omit giving variable names to them.
MATCH (a:User)-[:Follows]->(:User)-[:LivesIn]->(c:City)WHERE a.name = "Adam"RETURN a, c.name, c.population;
Output:
----------------------------------------------------------------------------| a | c.name | c.population |----------------------------------------------------------------------------| {_ID: 0:0, _LABEL: User, name: Adam, age: 30} | Waterloo | 150000 |----------------------------------------------------------------------------| {_ID: 0:0, _LABEL: User, name: Adam, age: 30} | Kitchener | 200000 |----------------------------------------------------------------------------
Match multiple patterns
Although paths can be matched in a single pattern, some patterns, in particular cyclic patterns, require specifying multiple patterns/paths that form the pattern. These multiple paths are separated by a comma. The following is a (directed) triangle query and returns the only triangle in the database between Adam, Karissa, and Zhang.
MATCH (a:User)-[:Follows]->(b:User)-[:Follows]->(c:User), (a)-[:Follows]->(c)RETURN a.name, b.name, c.name;
Output:
-----------------------------| a.name | b.name | c.name |-----------------------------| Adam | Karissa | Zhang |-----------------------------
Note that in the query node variables a and c appear twice, once on each of the 2 paths in the query. In such cases, their labels need to be specified only the first time they appear in the pattern. In the above query a and c’s labels are defined on the first/left path, so you don’t have to specify them on the right path (though you still can).
Equality predicates on node/relationship properties
The WHERE clause is the main clause to specify arbitrary predicates on the nodes and relationships in your patters (e.g., a.age < b.age in where “a” and “b” bind to User nodes).
As a syntactic sugar openCypher allows equality predicates to be matched on
nodes and edges using the {prop1 : value1, prop2 : value2, ...}
syntax. For example:
MATCH (a:User)-[e:Follows {since: 2020}]->(b:User {name: "Zhang"})RETURN a, e.since, b.name;
is syntactic sugar for:
MATCH (a:User)-[e:Follows]->(b:User)WHERE e.since = 2020 AND b.name = "Zhang"RETURN a, e.since, b.name;
and both queries output:
--------------------------------------------------------------------| a | e.since | b.name |--------------------------------------------------------------------| {_ID: 0:0, _LABEL: User, name: Adam, age: 30} | 2020 | Zhang |--------------------------------------------------------------------
Match recursive relationships
You can also find recursive relationships (that are of variable length) between node records.
Specifically, you can find variable-length
connections between nodes by specifying in the relationship patterns,
e.g., -[:Label*min..max]->
, where min
and max
specify the minimum and the maximum number of hops2.
The following query finds all Users that “Adam” follows within 1 to 2 hops and returns their names as well as length of the path.
MATCH (a:User)-[e:Follows*1..2]->(b:User)WHERE a.name = 'Adam'RETURN b.name, length(e) AS length;
Output:
--------------------| b.name | length |--------------------| Karissa | 1 |--------------------| Zhang | 2 |--------------------| Zhang | 1 |--------------------| Noura | 2 |--------------------
- Karissa is found through
Adam -> Karissa
- Zhang is found through
Adam -> Zhang
andAdam -> Karissa -> Zhang
- Noura is found through
Adam -> Zhang -> Noura
Similar to matching relationships, you can match undirected relationships or relationship with multiple labels. The following query finds all Nodes excluding “Noura” that connects to “Noura” in both directions through any relationship with 2 hops.
MATCH (a:User)-[e*2..2]-(b)WHERE a.name = 'Noura' AND b.name <> 'Noura'RETURN b.name, length(e) AS length;
Output:
----------------------| b.name | length |----------------------| Adam | 2 |----------------------| Karissa | 2 |----------------------| Kitchener | 2 |----------------------
- Adam is found through
Noura <- User -> Adam
- Karissa is found through
Noura <- User -> Karissa
- Kitchener is found through
Noura <- User -> Kitchener
Return recursive relationships
A recursive relationship has the logical data type RECURSIVE_REL
and is physically represented as STURCT{LIST[NODE], LIST[REL]}
. Returning a recursive relationship will return all properties
MATCH (a:User)-[e:Follows*1..2]->(b:User)WHERE a.name = 'Adam'RETURN b.name, e;
Output:
----------------------------------------------------------------------------------------------| b.name | e |----------------------------------------------------------------------------------------------| Karissa | {_NODES: [], _RELS: [(0:0)-{_LABEL: Follows, _ID: 2:0, since: 2020}->(0:1)]} |----------------------------------------------------------------------------------------------| Zhang | {_NODES: [{_ID: 0:1, _LABEL: User, name: Karissa, age: 40}], _RELS: [(0:0)-{_... |----------------------------------------------------------------------------------------------| Zhang | {_NODES: [], _RELS: [(0:0)-{_LABEL: Follows, _ID: 2:1, since: 2020}->(0:2)]} |----------------------------------------------------------------------------------------------| Noura | {_NODES: [{_ID: 0:2, _LABEL: User, name: Zhang, age: 50}], _RELS: [(0:0)-{_LA... |----------------------------------------------------------------------------------------------
Filter recursive relationships
We also support running predicates on recursive relationships to constrain the relationship being traversed.
The following query finds name of users and the number of path that are followed between 1-2 hops from Adam by person with age more than 45 and before 2022.
MATCH p = (a:User)-[:Follows*1..2 (r, n | WHERE r.since < 2022 AND n.age > 45) ]->(b:User)WHERE a.name = 'Adam'RETURN b.name, COUNT(*);
Output:
--------------------------| b.name | COUNT_STAR() |--------------------------| Karissa | 1 |--------------------------| Zhang | 1 |--------------------------
Our filter grammar is similar to that used by Memgraph for example, in Cypher list comprehensions. The first variable represents intermediate relationships and the second one represents intermediate nodes.
Project properties of intermediate nodes/relationships
You can project a subset of properties for the intermediate nodes and relationships that bind within a recursive
relationship. You can define the projection list of intermediate nodes and relationships within two curly brackets {}
{}
at
the end of the recursive relationship. The first {}
is used for projecting relationship properties and the second {}
for
node properties. Currently, Kùzu only supports directly projecting properties and not of expressions using
the properties. Projecting properties of intermediate nodes and relationships can improve both performance and memory footprint.
Below is an example that projects only the since
property of the intermediate relationship and the name
property of the
intermediate nodes that will bind to the variable length relationship pattern of e
. Readers can assume
that there are other properties than since
on the Follow
relationship table for our purposes (in our running example, the User
nodes already have a second property age
, which will be removed from the output as shown below).
MATCH (a:User)-[e:Follows*1..2 (r, n | WHERE r.since > 2020 | {r.since}, {n.name})]->(b:User)RETURN nodes(e), rels(e);
Returns:
------------------------------------------------------------------------------------------------------------------------------| NODES(e) | RELS(e) |------------------------------------------------------------------------------------------------------------------------------| [] | [(0:1)-{_LABEL: Follows, _ID: 2:2, since: 2021}->(0:2)] |------------------------------------------------------------------------------------------------------------------------------| [{_ID: 0:2, _LABEL: User, name: Zhang}] | [(0:1)-{_LABEL: Follows, _ID: 2:2, since: 2021}->(0:2),(0:2)-{_LABEL: Follows... |------------------------------------------------------------------------------------------------------------------------------| [] | [(0:2)-{_LABEL: Follows, _ID: 2:3, since: 2022}->(0:3)] |------------------------------------------------------------------------------------------------------------------------------
As can be seen in the output, the nodes that bind to e
contain only the name
property and the relationships that
bind to e
contain only the since
property.
Single shortest path
On top of variable length relationships, users can search for single shortest path by specifying SHORTEST
key word in relationship, e.g. -[:Label* SHORTEST 1..max]
.
The following query finds a shortest path between Adam
and any city and returns city name as well as length of the path.
MATCH (a:User)-[e* SHORTEST 1..4]->(b:City)WHERE a.name = 'Adam'RETURN b.name, length(e) AS length;
Output:
----------------------| b.name | length |----------------------| Waterloo | 1 |----------------------| Kitchener | 2 |----------------------| Guelph | 3 |----------------------
All shortest paths
You can also search for all shortest paths with ALL SHORTEST
key word, e.g. -[:Label* ALL SHORTEST 1..max]
The following query finds all shortest paths between Zhang
and Waterloo
.
MATCH p = (a)-[* ALL SHORTEST 1..3 ]-(b)WHERE a.name = 'Zhang' AND b.name = 'Waterloo'RETURN COUNT(*) AS num_shortest_path;
Output:
---------------------| num_shortest_path |---------------------| 2 |---------------------
Named paths
Kùzu treats paths as a first-class citizen, so users can assign a named variable to a path (i.e. connected graph ) and use it later on.
The following query returns all paths between Adam
and Karissa
.
MATCH p = (a:User)-[:Follows]->(b:User)WHERE a.name = 'Adam' AND b.name = 'Karissa'RETURN p;
Output:
------------------------------------------------------------------------------------| p |------------------------------------------------------------------------------------| {_NODES: [{_ID: 0:0, _LABEL: User, name: Adam, age: 30},{_ID: 0:1, _LABEL: Us... |------------------------------------------------------------------------------------
Named paths can also be assigned to recursive graph patterns.
MATCH p = (a:User)-[:Follows*1..2]->(:User)-[:LivesIn]->(:City)WHERE a.name = 'Adam'RETURN p;
Output:
------------------------------------------------------------------------------------| p |------------------------------------------------------------------------------------| {_NODES: [{_ID: 0:0, _LABEL: User, name: Adam, age: 30, },{_ID: 0:1, _LABEL: ... |------------------------------------------------------------------------------------| {_NODES: [{_ID: 0:0, _LABEL: User, name: Adam, age: 30, },{_ID: 0:2, _LABEL: ... |------------------------------------------------------------------------------------| {_NODES: [{_ID: 0:0, _LABEL: User, name: Adam, age: 30, },{_ID: 0:1, _LABEL: ... |------------------------------------------------------------------------------------| {_NODES: [{_ID: 0:0, _LABEL: User, name: Adam, age: 30, },{_ID: 0:2, _LABEL: ... |------------------------------------------------------------------------------------
Multiple named path can appear in a single MATCH
clause.
MATCH p1 = (a:User)-[:Follows]->(b:User), p2 = (b)-[:LivesIn]->(:City)WHERE a.name = 'Adam'RETURN p1, p2;
Output:
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| p1 | p2 |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| {_NODES: [{_ID: 0:0, _LABEL: User, name: Adam, age: 30},{_ID: 0:1, _LABEL: Us... | {_NODES: [{_ID: 0:1, _LABEL: User, name: Karissa, age: 40, },{_ID: 1:0, _LABE... |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| {_NODES: [{_ID: 0:0, _LABEL: User, name: Adam, age: 30},{_ID: 0:2, _LABEL: Us... | {_NODES: [{_ID: 0:2, _LABEL: User, name: Zhang, age: 50, },{_ID: 1:1, _LABEL:... |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Extracting nodes and relationships from a path
A named path has the logical data type RECURSIVE_REL
. You can access nodes and relationships within a named path through nodes(p)
and rels(p)
function calls.
MATCH p = (a:User)-[:Follows*1..2]->(:User)WHERE a.name = 'Adam'RETURN nodes(p), (rels(p)[1]).since AS since;
Output:
--------------------------------------------------------------------------------------------| NODES(p) | since |--------------------------------------------------------------------------------------------| [{_ID: 0:0, _LABEL: User, name: Adam, age: 30},{_ID: 0:1, _LABEL: User, name:... | 2020 |--------------------------------------------------------------------------------------------| [{_ID: 0:0, _LABEL: User, name: Adam, age: 30},{_ID: 0:1, _LABEL: User, name:... | 2020 |--------------------------------------------------------------------------------------------| [{_ID: 0:0, _LABEL: User, name: Adam, age: 30},{_ID: 0:2, _LABEL: User, name:... | 2020 |--------------------------------------------------------------------------------------------| [{_ID: 0:0, _LABEL: User, name: Adam, age: 30},{_ID: 0:2, _LABEL: User, name:... | 2020 |--------------------------------------------------------------------------------------------
More recursive relationship functions can be found here.