From e11a1b49122d4120233c4442e92d9b7a01fa2195 Mon Sep 17 00:00:00 2001 From: imilinovic Date: Thu, 10 Sep 2026 12:47:03 +0200 Subject: [PATCH 1/2] docs: the bare pattern shorthand takes what a MATCH pattern takes The page stated two rules that no longer hold: that the shorthand's pattern cannot introduce names of its own, and that it holds a pattern and nothing else so a filter needs the `MATCH` form. It now takes a variable it declares itself, a trailing `WHERE`, a comma-separated list, a named path and a lone node. The worked example is the filter the old text used to send to the `MATCH` form, written as the shorthand, with output from the page's own dataset. `COLLECT` still has no shorthand. --- pages/querying/subquery-expressions.mdx | 35 ++++++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/pages/querying/subquery-expressions.mdx b/pages/querying/subquery-expressions.mdx index 229b931b8..7cbeae4bc 100644 --- a/pages/querying/subquery-expressions.mdx +++ b/pages/querying/subquery-expressions.mdx @@ -402,9 +402,8 @@ Output: ### The bare pattern shorthand `EXISTS` and `COUNT` accept a bare pattern in place of a full body, which is a -shorter way to write a check or a count over one pattern. The pattern cannot -introduce names of its own — a variable already bound outside may appear in it, -and acts as a join: +shorter way to write a check or a count over one pattern. A variable already +bound outside may appear in it, and acts as a join: ```cypher MATCH (p:Person) @@ -427,10 +426,32 @@ Output: +---------+-------+---------+ ``` -The shorthand holds a pattern and nothing else, so a filter or any other clause -needs the `MATCH` form — `COUNT { MATCH (p)-[r:KNOWS]->() WHERE r.since < 2015 }`. -`COLLECT` has no shorthand, since a bare pattern returns no column for it to -gather. +The shorthand takes everything a `MATCH` pattern takes: a variable it declares +itself, a trailing `WHERE`, several patterns separated by commas, a named path, +and a pattern holding only a node. + +```cypher +MATCH (p:Person) +RETURN p.name AS name, + COUNT { (p)-[r:KNOWS]->(f:Person) WHERE r.since < 2015 } AS early +ORDER BY name; +``` + +Output: + +```nocopy ++---------+-------+ +| name | early | ++---------+-------+ +| "Alice" | 1 | +| "Bob" | 0 | +| "Carol" | 0 | +| "Dave" | 0 | ++---------+-------+ +``` + +Any other clause still needs the `MATCH` form. `COLLECT` has no shorthand, since +a bare pattern returns no column for it to gather. ### Correlation with the enclosing query From 32696aa94a351a86a66e81e75813294910c8e1e4 Mon Sep 17 00:00:00 2001 From: imilinovic Date: Sun, 13 Sep 2026 21:10:00 +0200 Subject: [PATCH 2/2] docs: a pattern holding only a bound variable matches on its value A pattern that names a variable the query has already bound used to be ignored, so the row passed through whatever the variable held. It is now matched: a node is kept, a null matches nothing and drops the row, and a value that is neither raises. This applies to a top-level MATCH and a CALL {} subquery as well as to a subquery expression body, which the pages did not say. Also documents the two rules that came with it: a pattern name inside a subquery expression body may not repeat a name from outside the body, and a CREATE or MERGE node may not give labels or properties to an already-declared variable, whether they are written as a literal map or supplied as a parameter. Every output block was produced by running the query against its own page's dataset block. --- pages/querying/clauses/create.mdx | 39 +++++++++++++ pages/querying/clauses/match.mdx | 74 +++++++++++++++++++++++++ pages/querying/clauses/merge.mdx | 24 +++++++- pages/querying/subquery-expressions.mdx | 68 +++++++++++++++++++++++ 4 files changed, 204 insertions(+), 1 deletion(-) diff --git a/pages/querying/clauses/create.mdx b/pages/querying/clauses/create.mdx index 0dd62b22a..70be640dc 100644 --- a/pages/querying/clauses/create.mdx +++ b/pages/querying/clauses/create.mdx @@ -22,6 +22,7 @@ more details. 1.2. [Creating a node with properties](#12-creating-a-node-with-properties)
1.3. [Creating multiple nodes](#13-creating-multiple-nodes)
1.4. [Creating node labels dynamically](#14-creating-node-labels-dynamically)
+ 1.5. [Creating a node from an already-declared variable](#15-creating-a-node-from-an-already-declared-variable)
2. [Creating relationships](#2-creating-relationships)
2.1. [Creating a relationship between two nodes](#21-creating-a-relationship-between-two-nodes)
2.2. [Creating a relationship with properties](#22-creating-a-relationship-with-properties)
@@ -140,6 +141,44 @@ Output: This functionality can especially be useful when importing data from CSV or other sources, since at that point you can inject the arbitrary labels into the graph. +### 1.5. Creating a node from an already-declared variable + +A node in a `CREATE` pattern may reuse a variable the query has already bound, so +that a relationship can be attached to it. The queries below use the node created +in [1.2](#12-creating-a-node-with-properties): + +```cypher +MATCH (c:Country {name: 'San Marino'}) +CREATE (c)<-[:LIVING_IN]-(p:Person {name: 'Petra'}) +RETURN p.name AS name; +``` + +Output: +```nocopy ++---------+ +| name | ++---------+ +| "Petra" | ++---------+ +``` + +Such a node may not give the variable labels or properties, because the node it +names already exists: + +```cypher +MATCH (c:Country {name: 'San Marino'}) +CREATE (c {population: 34000}); +``` + +Output: +```nocopy +Cannot create node 'c' with labels or properties, because it is already declared. +``` + +The rule holds whether the properties are written as a literal map or supplied as +a parameter, and it applies to [`MERGE`](/querying/clauses/merge) as well. Use +[`SET`](/querying/clauses/set) to change a node that already exists. + ## 2. Creating relationships ### 2.1. Creating a relationship between two nodes diff --git a/pages/querying/clauses/match.mdx b/pages/querying/clauses/match.mdx index eeeb97b1f..2cf2f58f5 100644 --- a/pages/querying/clauses/match.mdx +++ b/pages/querying/clauses/match.mdx @@ -30,6 +30,7 @@ The `MATCH` clause is used to obtain data from the database by matching it to a 4. [Using multiple MATCH clauses](#4-using-multiple-match-clauses)
4.1. [Cartesian product of nodes](#41-cartesian-product-of-nodes)
4.2. [Creating a list](#42-creating-a-list)
+ 4.3. [Matching a variable that is already bound](#43-matching-a-variable-that-is-already-bound)
@@ -476,6 +477,79 @@ Output: The `OPTIONAL MATCH` clause bypasses the empty set and the query returns only non-empty sets. Therefore, the output of the query is a list containing only the results of the first `MATCH` clause. +### 4.3. Matching a variable that is already bound + +A pattern may hold nothing but a variable the query has already bound. Such a +pattern does not scan the graph — it matches on the value bound to that name: + +```cypher +MATCH (p:Person) +MATCH (p) +RETURN p.name AS name +ORDER BY name; +``` + +Output: + +```nocopy ++---------+ +| name | ++---------+ +| "Anna" | +| "Harry" | +| "John" | ++---------+ +``` + +A node matches and the row is kept. A `null`, which an `OPTIONAL MATCH` leaves +behind when nothing matched, matches nothing and the row is dropped: + +```cypher +MATCH (p:Person) +OPTIONAL MATCH (p)-[:LIVING_IN]->(c:Country {continent: 'Asia'}) +WITH p, c +MATCH (c) +RETURN p.name AS name, c.name AS country +ORDER BY name; +``` + +Output: + +`Empty set` + +No person lives in an Asian country, so `c` is `null` on every row and `MATCH (c)` +keeps none of them. Without the `MATCH (c)`, the query returns all three people +with a `null` country. + +A value that is neither a node nor `null` raises an error: + +```cypher +MATCH (p:Person) +WITH p.name AS p +MATCH (p) +RETURN p; +``` + +Output: + +```nocopy +Expected a node for 'p', but got string. +``` + + + +This applies wherever a pattern appears — a top-level `MATCH`, a `CALL {}` +subquery, and the body of a [subquery +expression](/querying/subquery-expressions). Before Memgraph 3.14 a pattern +holding only a bound variable was ignored, so the row passed through whatever the +variable held. Queries that carried a `null` or a non-node value through such a +pattern now return fewer rows or raise an error. + +A pattern that also states a label or a property, such as `MATCH (p:Person)`, is +unaffected — it already raised on a non-node value. + + + ## Dataset queries We encourage you to try out the examples by yourself. diff --git a/pages/querying/clauses/merge.mdx b/pages/querying/clauses/merge.mdx index 3b7529a00..1b4ef39af 100644 --- a/pages/querying/clauses/merge.mdx +++ b/pages/querying/clauses/merge.mdx @@ -34,7 +34,8 @@ more details. 3.4. [Merging with SET](#34-merging-with-set)
3.5. [Combination of clauses](#35-combination-of-clauses)
4. [Errors related to invalid MERGE usage](#4-errors-related-to-invalid-merge-usage)
- 4.1. [Merging a null property](#41-merging-a-null-property) + 4.1. [Merging a null property](#41-merging-a-null-property)
+ 4.2. [Merging a node from an already-declared variable](#42-merging-a-node-from-an-already-declared-variable) ## Data Set @@ -328,3 +329,24 @@ Due to Memgraph having the same behavior for null types, which also corresponds we do not support this behavior. This applies to both node and edge properties. The check is evaluated at runtime. To mitigate this issue, try modifying the query using `ON CREATE SET`, `ON MATCH SET`, or just `SET` after the `MERGE` clause. + +### 4.2. Merging a node from an already-declared variable + +A node in a `MERGE` pattern may reuse a variable the query has already bound, so +that a relationship can be merged onto it. Such a node may not give the variable +labels or properties, because the node it names already exists: + +```cypher +MATCH (c:Country {name: 'Germany'}) +MERGE (c:Visited); +``` +will result in the following error + +``` +Cannot create node 'c' with labels or properties, because it is already declared. +``` + +The rule holds whether the properties are written as a literal map or supplied as +a parameter, and it applies to [`CREATE`](/querying/clauses/create) as well. Use +`ON CREATE SET`, `ON MATCH SET` or [`SET`](/querying/clauses/set) to change a node +that already exists. diff --git a/pages/querying/subquery-expressions.mdx b/pages/querying/subquery-expressions.mdx index 7cbeae4bc..0ee56f896 100644 --- a/pages/querying/subquery-expressions.mdx +++ b/pages/querying/subquery-expressions.mdx @@ -500,6 +500,74 @@ Output: Variables introduced inside the body are scoped to it and are not available after the subquery expression. +A pattern in the body may hold nothing but a correlated variable. Such a pattern +matches on the value bound to that name rather than scanning the graph: a node +matches, a `null` matches nothing, and a value that is neither raises an error. + +```cypher +MATCH (p:Person) +OPTIONAL MATCH (p)-[:KNOWS]->(f:Person) +WITH p, f +RETURN p.name AS name, EXISTS { (f) } AS bound +ORDER BY name, bound; +``` + +Output: + +```nocopy ++---------+-------+ +| name | bound | ++---------+-------+ +| "Alice" | true | +| "Alice" | true | +| "Bob" | true | +| "Carol" | false | +| "Dave" | false | ++---------+-------+ +``` + +Carol and Dave know nobody, so `f` is `null` on their rows and `EXISTS { (f) }` +is `false`. This is the same rule a top-level [`MATCH`](/querying/clauses/match) +follows. + +A named path inside the body may not take its name from a variable declared +outside the body. The body writes into the frame it shares with its caller, so +one name cannot mean the caller's value in one place and the body's own path in +another: + +```cypher +MATCH (p:Person) +WITH p, p.name AS path +RETURN COUNT { MATCH path = (p)-[:KNOWS]->() } AS c; +``` + +Output: + +```nocopy +Cannot name a pattern 'path' in COUNT, because that variable is already declared outside it. +``` + +Give the path a name of its own, and it works: + +```cypher +MATCH (p:Person) +RETURN COUNT { MATCH q = (p)-[:KNOWS]->() } AS c +ORDER BY c; +``` + +Output: + +```nocopy ++---+ +| c | ++---+ +| 0 | +| 0 | +| 1 | +| 2 | ++---+ +``` + ### The body's RETURN decides the result The body is a query, and its final `RETURN` shapes the row set that the