Docs
View as markdown

Writing clauses

The grammar of CREATE, MERGE with ON CREATE and ON MATCH, SET, REMOVE, DELETE, FOREACH and LOAD CSV in relate, with what each returns and its errors.

cyphercli

Writing clauses change the graph: CREATE and INSERT add nodes and relationships, MERGE matches a pattern or creates it, SET and REMOVE change labels and properties, DELETE removes entities, FOREACH runs updates once per list element, and LOAD CSV reads rows from a local file. Each change lands in the store A relate database: a directory, a single .rl file, or :memory:, opened with --database. Read more in the glossary the statement runs against. A statement that writes commits whole, or fails and changes nothing; CALL { … } IN TRANSACTIONS is the exception, and commits each batch on its own. A write with no RETURN returns no rows.

Synopsis

CREATE <pattern> [, <pattern> …]
INSERT <pattern> [, <pattern> …]

MERGE <pattern>
  [ON CREATE SET <set item> [, …]]
  [ON MATCH SET <set item> [, …]]

SET <set item> [, …]
  <set item> = <variable>.<key> = <expression>
             | <variable>[<key expression>] = <expression>
             | <variable> = <map or entity>
             | <variable> += <map or entity>
             | <variable>:<Label>[:<Label> …]
             | <variable> IS <Label>
             | <variable>:$(<expression>)

REMOVE <variable>.<key> | <variable>[<key expression>] | <variable>:<Label>[:<Label> …] | <variable>:$(<expression>) [, …]

[DETACH | NODETACH] DELETE <expression> [, …]

FOREACH (<variable> IN <list> | <update clause> [<update clause> …])

LOAD CSV [WITH HEADERS] FROM <file> AS <variable> [FIELDTERMINATOR <character>]

FINISH

Clauses

ClauseDoesNotes
CREATECreates every node and relationship in the pattern that is not a bound variableEach relationship needs a direction and exactly one relationship type. A variable bound earlier in the statement cannot be redeclared with labels or properties. A variable created in one CREATE cannot be read in the same clause; a second CREATE can read it
INSERTThe same as CREATEGQL spelling
MERGEMatches the whole pattern; when it has no match, creates the whole patternOne pattern per MERGE. Properties in the pattern must not be null. Relationships need exactly one type; an undirected relationship matches either direction and is created left to right
ON CREATE SETRuns its items only on a row where MERGE created the patternSame items as SET
ON MATCH SETRuns its items only on a row where MERGE matchedSame items as SET
SET n.key = vSets one propertynull removes the property
SET n[expr] = vSets the property the string expression namesA dynamic key
SET n = mapReplaces every property with the map's entriesAnother node or relationship in place of the map copies its properties; labels are unchanged
SET n += mapAdds or overwrites the map's keys and keeps the restA null value in the map removes that key
SET n:Label, SET n IS LabelAdds labels$(<expression>) takes a string or a list of strings
REMOVERemoves properties or labelsRemoving what is absent is not an error
DELETEDeletes relationships, nodes with no relationships, or a path's relationships and nodesA node that still has relationships fails the statement
DETACH DELETEDeletes nodes and their relationships
NODETACH DELETEThe same as DELETEGQL spelling
FOREACHRuns its update clauses once per element of the listOnly update clauses inside (CREATE, MERGE, SET, REMOVE, DELETE, FOREACH); a null list runs nothing. Variables bound inside stay inside
LOAD CSVOne row per CSV record: a list of strings, or with WITH HEADERS a map keyed by the headerLocal files only, under the directory --load-csv-dir allows; denied by default. Every value is a string. The CSV guide has the file policy, quoting, and size limit
FINISHEnds the statement with no result columnsFor a statement run only for its writes

Property values

A property holds a scalar, a temporal or spatial value, a vector, or a list of one of those types. The types and values page lists them.

Value setResult
nullRemoves the property
A list of one type, such as ['rust', 'cypher']Stored
A list that mixes types, such as ['rust', 1]type error: InvalidPropertyType
A list holding nulltype error: InvalidPropertyType
A maptype error: InvalidPropertyType

What a write returns

SituationResult
No RETURNNo rows. The CLI prints nothing for it
RETURN after the writeThe rows, with the written values; a created node already has its element id
Nothing changedNo rows; the CLI prints note: write statement changed nothing; the graph is unchanged on stderr
CountersThe cells on this page show the counters the WebAssembly envelope carries (nodes created, properties set and the rest), except after FINISH; the CLI's envelope carries none
A failureThe statement changes nothing

Reading a deleted entity's properties later in the same statement fails; a value projected with WITH before the DELETE stays readable.

Errors

MessageCLI exit codeCause
syntax error: RequiresDirectedRelationship: CREATE relationships must have a direction6A CREATE relationship written -[:TYPE]-
syntax error: NoSingleRelationshipType: CREATE relationships must have exactly one type6No type, or :A|B; the MERGE form names MERGE
syntax error: CreatingVarLength: variable length relationships cannot be used in CREATE6A variable-length or quantified relationship in CREATE; the MERGE form names MERGE
syntax error: SyntaxError: Entity, '<name>', cannot be created and referenced in the same clause6CREATE (a {…}), (b {name: a.name})
syntax error: VariableAlreadyBound: variable '<name>' already declared6MATCH (a) CREATE (a:Person)
type error: InvalidPropertyType6A map, a mixed list or a list holding null as a property value
syntax error: InvalidClauseComposition: invalid FOREACH: invalid use of MATCH inside 'FOREACH'6A reading clause inside FOREACH
syntax error: WriteBeforeCallInTransactions: a write cannot precede CALL IN TRANSACTIONS6A write, or LOAD CSV, before CALL { … } IN TRANSACTIONS in the same statement
syntax error: parse error: MERGE requires exactly one pattern path at line 0, col 06Two comma-separated patterns in one MERGE
MergeReadOwnWrites: cannot MERGE with null properties1A MERGE pattern property that is null
DeleteConnectedNode: Cannot delete node with relationships unless DETACH1DELETE or NODETACH DELETE on a node with relationships
DeletedEntityAccess: cannot access properties of a deleted node1A property read after DELETE in the same statement
LOAD CSV file unavailable1The file is outside the allowed directory, missing, a URL, or no directory is allowed
semantic error: ConstraintValidationFailed: <constraint> <rule>6The write breaks a constraint (schema clauses)
semantic error: WriteOnReadOnlyAccessDatabase: …6The store is open read-only (errors)

Examples

The cells run in page order on one in-memory store. Each one writes, and says what before it runs. First, the shared example graph:

Cypher
CREATE (ada:Person {name: 'Ada', role: 'engineer'}),
       (grace:Person {name: 'Grace', role: 'engineer'}),
       (noor:Person {name: 'Noor', role: 'maintainer'}),
       (relate:Project {name: 'relate', language: 'Rust'}),
       (tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),
       (ada)-[:WORKS_ON {since: 2024}]->(relate),
       (grace)-[:WORKS_ON {since: 2023}]->(relate),
       (grace)-[:WORKS_ON {since: 2022}]->(tck),
       (noor)-[:MAINTAINS]->(relate),
       (ada)-[:KNOWS]->(grace)
Recorded result
(no rows) · nodes created: 5, relationships created: 5, properties set: 13, labels added: 5

CREATE adds a person, a project and the relationship between them, and returns them:

Cypher
CREATE (lin:Person {name: 'Lin', role: 'engineer'})-[w:WORKS_ON {since: 2026}]->(docs:Project {name: 'relate-docs', language: 'Markdown'})
RETURN lin.name AS person, type(w) AS type, docs.name AS project
Recorded result
persontypeproject
"Lin""WORKS_ON""relate-docs"

1 row · nodes created: 2, relationships created: 1, properties set: 5, labels added: 2

A relationship in CREATE needs a direction:

Cypher
CREATE (:Person {name: 'Kim'})-[:KNOWS]-(:Person {name: 'Sam'})
Expected error
error: syntax error: RequiresDirectedRelationship: CREATE relationships must have a direction

MERGE finds Ada, so only ON MATCH SET runs and sets seen:

Cypher
MERGE (p:Person {name: 'Ada'})
  ON CREATE SET p.created = true
  ON MATCH SET p.seen = true
RETURN p.name AS name, p.created AS created, p.seen AS seen
Recorded result
namecreatedseen
"Ada"nulltrue

1 row · properties set: 1

Merging a relationship between existing nodes starts by matching both ends. This creates Ada's WORKS_ON to the TCK project:

Cypher
MATCH (a:Person {name: 'Ada'}), (r:Project {name: 'opencypher-tck'})
MERGE (a)-[w:WORKS_ON]->(r)
  ON CREATE SET w.since = 2026
RETURN w.since AS since
Recorded result
since
2026

1 row · relationships created: 1, properties set: 1

MERGE on a whole pattern creates the whole pattern when any part is missing. Zed does not exist, so this creates Zed, the relationship, and a second Grace:

Cypher
MERGE (:Person {name: 'Zed'})-[:KNOWS]->(:Person {name: 'Grace'});
MATCH (g:Person {name: 'Grace'}) RETURN count(g) AS graces
Recorded result
graces
2

1 row · nodes created: 2, relationships created: 1, properties set: 2, labels added: 2

A null property in a MERGE pattern fails:

Cypher
MERGE (p:Person {name: null}) RETURN p
Expected error
error: MergeReadOwnWrites: cannot MERGE with null properties

DETACH DELETE removes Zed, the second Grace and the relationship between them:

Cypher
MATCH (z:Person {name: 'Zed'})-[:KNOWS]->(g:Person)
DETACH DELETE z, g
Recorded result
(no rows) · nodes deleted: 2, relationships deleted: 1

SET with += adds a key, :Lead adds a label, and null removes Ada's role and the seen flag:

Cypher
MATCH (p:Person {name: 'Ada'})
SET p += {level: 3, seen: null}, p:Lead, p.role = null
RETURN labels(p) AS labels, properties(p) AS props
Recorded result
labelsprops
["Person", "Lead"]{level: 3, name: "Ada"}

1 row · properties set: 3, labels added: 1

SET with = replaces every property and keeps the labels:

Cypher
MATCH (p:Person {name: 'Ada'})
SET p = {name: 'Ada', role: 'engineer'}
RETURN labels(p) AS labels, properties(p) AS props
Recorded result
labelsprops
["Person", "Lead"]{name: "Ada", role: "engineer"}

1 row · properties set: 3

A list property holds values of one type:

Cypher
MATCH (p:Person {name: 'Ada'}) SET p.tags = ['rust', 1]
Expected error
error: type error: InvalidPropertyType

REMOVE takes the label back off:

Cypher
MATCH (p:Person {name: 'Ada'})
REMOVE p:Lead
RETURN labels(p) AS labels
Recorded result
labels
["Person"]

1 row · labels removed: 1

DELETE refuses a node that still has relationships:

Cypher
MATCH (p:Person {name: 'Noor'}) DELETE p
Expected error
error: DeleteConnectedNode: Cannot delete node with relationships unless DETACH

FOREACH creates two people who work on relate, one per list element:

Cypher
MATCH (r:Project {name: 'relate'})
FOREACH (name IN ['Kim', 'Sam'] |
  CREATE (:Person {name: name, role: 'engineer'})-[:WORKS_ON {since: 2026}]->(r))
Recorded result
(no rows) · nodes created: 2, relationships created: 2, properties set: 6, labels added: 2

FINISH ends a statement that only writes. This removes the two again:

Cypher
MATCH (p:Person) WHERE p.name IN ['Kim', 'Sam']
DETACH DELETE p
FINISH
Recorded result
(no rows)

LOAD CSV reads files from disk, which the in-browser cells cannot reach, so its examples run on the CLI. This setup step writes a file into a directory named import:

mkdir import
printf 'name;role\nKim;engineer\nSam;maintainer\n' > import/people.csv

WITH HEADERS makes each record a map, and FIELDTERMINATOR names the separator. No --database means an in-memory store, so nothing is kept:

relate --load-csv-dir ./import --mode table "LOAD CSV WITH HEADERS FROM 'people.csv' AS row FIELDTERMINATOR ';' RETURN row.name AS name, row.role AS role"
+-------+--------------+
| name  | role         |
+-------+--------------+
| "Kim" | "engineer"   |
| "Sam" | "maintainer" |
+-------+--------------+

LOAD CSV counts as a write before CALL { … } IN TRANSACTIONS, so the batched form fails before it runs:

relate --load-csv-dir ./import --mode table "LOAD CSV WITH HEADERS FROM 'people.csv' AS row FIELDTERMINATOR ';' CALL (row) { CREATE (:Person {name: row.name}) } IN TRANSACTIONS OF 1 ROWS"
syntax error: WriteBeforeCallInTransactions: a write cannot precede CALL IN TRANSACTIONS

See also