Writing graphs
CREATE, MERGE, SET, REMOVE, DELETE, and DETACH DELETE.
CREATE
CREATE inserts nodes and relationships.
Create a node with a label and properties:
CREATE (:Person {name:"Alice", age:30})
Create two nodes in one statement:
CREATE (:Person {name:"Alice"}), (:Person {name:"Bob"})
Create a relationship between two existing nodes:
MATCH (a:Person {name:"Alice"}), (b:Person {name:"Bob"})
CREATE (a)-[:KNOWS {since:2022}]->(b)
Create a node and a relationship in one pattern:
CREATE (a:Person {name:"Alice"})-[:KNOWS]->(b:Person {name:"Bob"})
Return what you created:
CREATE (p:Person {name:"Carol", age:28})
RETURN p
MERGE
MERGE is find-or-create.
It matches the pattern and creates it only if nothing matches:
MERGE (p:Person {name:"Alice"})
If a Person node with name:"Alice" already exists, MERGE returns it.
If it does not exist, MERGE creates it.
Use ON CREATE SET to set properties only when creating:
MERGE (p:Person {name:"Alice"})
ON CREATE SET p.age = 30, p.created = timestamp()
ON MATCH SET p.lastSeen = timestamp()
RETURN p
MERGE is often used on indexed properties so it can find existing nodes efficiently.
Without an index, MERGE scans every node with the given label.
SET
SET updates properties or adds labels:
MATCH (p:Person {name:"Alice"})
SET p.age = 31
Set multiple properties:
MATCH (p:Person {name:"Alice"})
SET p.age = 31, p.city = "Berlin"
Replace all properties at once:
MATCH (p:Person {name:"Alice"})
SET p = {name:"Alice", age:31, city:"Berlin"}
Add a label:
MATCH (p:Person {name:"Alice"})
SET p:Admin
SET p += {city:"Berlin"} merges the map into the existing properties without removing others.
REMOVE
REMOVE removes a property or a label:
MATCH (p:Person {name:"Alice"})
REMOVE p.city
Remove a label:
MATCH (p:Person {name:"Alice"})
REMOVE p:Admin
DELETE
DELETE removes nodes and relationships.
You must delete a node's relationships before deleting the node:
MATCH (p:Person {name:"Alice"})-[r]-()
DELETE r
Then:
MATCH (p:Person {name:"Alice"})
DELETE p
DETACH DELETE does both in one step:
MATCH (p:Person {name:"Alice"})
DETACH DELETE p
This deletes the node and all relationships connected to it.
FOREACH
FOREACH iterates over a list and applies an update clause to each element:
MATCH p = (start:Person {name:"Alice"})-[:KNOWS*]->(end:Person)
FOREACH (n IN nodes(p) | SET n.visited = true)
FOREACH accepts only write clauses (SET, CREATE, MERGE, DELETE, REMOVE, FOREACH) in its body.
Transactions and atomicity
Every write query runs in a transaction.
If you call db.Exec without opening an explicit transaction, gr wraps the statement in an auto-commit transaction.
For a sequence of writes that must succeed or fail together, open an explicit transaction:
tx, err := db.BeginTx(ctx, gr.TxOptions{})
if err != nil {
return err
}
defer tx.Rollback()
if _, err := tx.Exec(ctx, `CREATE (:Person {name:$name})`, map[string]any{"name": "Alice"}); err != nil {
return err
}
if _, err := tx.Exec(ctx, `CREATE (:Person {name:$name})`, map[string]any{"name": "Bob"}); err != nil {
return err
}
return tx.Commit()
See the transactions guide for the full API.
Constraint violations
If a write violates a uniqueness or existence constraint, gr returns *engine.ConstraintError.
The transaction is rolled back automatically.
_, err = db.Exec(ctx, `CREATE (:Person {email:"[email protected]"})`, nil)
if ce := (*engine.ConstraintError)(nil); errors.As(err, &ce) {
fmt.Println("constraint violated:", ce.Constraint)
}
Running a write in Go
summary, err := db.Exec(ctx, `
CREATE (:Person {name:$name, age:$age})
`, map[string]any{"name": "Alice", "age": 30})
if err != nil {
return err
}
fmt.Printf("created %d node(s)\n", summary.NodesCreated())