Mutations¶
Cypher supports mutations for creating, updating and deleting graph data.
Creating Nodes¶
-- Create a node
CREATE (p:Person {name: 'Alix', age: 30})
RETURN p
-- Create multiple nodes
CREATE (a:Person {name: 'Alix'})
CREATE (b:Person {name: 'Gus'})
-- Create with multiple labels
CREATE (e:Person:Employee {name: 'Harm'})
Creating Relationships¶
-- Create a relationship between existing nodes
MATCH (a:Person {name: 'Alix'}), (b:Person {name: 'Gus'})
CREATE (a)-[:KNOWS]->(b)
-- Create relationship with properties
MATCH (a:Person {name: 'Alix'}), (b:Person {name: 'Gus'})
CREATE (a)-[:KNOWS {since: 2020, strength: 'close'}]->(b)
-- Create nodes and relationships together
CREATE (a:Person {name: 'Alix'})-[:KNOWS]->(b:Person {name: 'Gus'})
RETURN a, b
Updating Properties¶
-- Set a property
MATCH (p:Person {name: 'Alix'})
SET p.age = 31
-- Set multiple properties
MATCH (p:Person {name: 'Alix'})
SET p.age = 31, p.city = 'New York'
-- Set from another property
MATCH (p:Person)
SET p.displayName = p.firstName + ' ' + p.lastName
-- Replace all properties
MATCH (p:Person {name: 'Alix'})
SET p = {name: 'Alix', age: 31, city: 'NYC'}
-- Add to existing properties
MATCH (p:Person {name: 'Alix'})
SET p += {city: 'NYC', active: true}
Removing Properties¶
-- Remove a property
MATCH (p:Person {name: 'Alix'})
REMOVE p.temporaryField
-- Set to null (equivalent)
MATCH (p:Person {name: 'Alix'})
SET p.temporaryField = null
Deleting Nodes¶
-- Delete a node (must have no relationships)
MATCH (p:Person {name: 'Alix'})
DELETE p
-- Delete node and all its relationships
MATCH (p:Person {name: 'Alix'})
DETACH DELETE p
Deleting Relationships¶
-- Delete specific relationship
MATCH (a:Person {name: 'Alix'})-[r:KNOWS]->(b:Person {name: 'Gus'})
DELETE r
-- Delete all relationships of a type from a node
MATCH (p:Person {name: 'Alix'})-[r:KNOWS]->()
DELETE r
UNWIND (List Expansion)¶
Expand a list into individual rows. Useful for batch operations.
-- Unwind a literal list
UNWIND [1, 2, 3] AS x
RETURN x
-- Unwind with parameters (Python: db.execute_cypher(query, {'names': ['Alix', 'Gus']}))
UNWIND $names AS name
RETURN name
-- Batch create relationships from a parameter list
UNWIND $edges AS e
MATCH (a:Person {name: e.from}), (b:Person {name: e.to})
CREATE (a)-[:KNOWS]->(b)
Merge (Upsert)¶
-- Create if not exists, match if exists
MERGE (p:Person {email: 'alix@example.com'})
SET p.lastSeen = timestamp()
RETURN p
-- Merge with different actions
MERGE (p:Person {email: 'alix@example.com'})
ON CREATE SET p.created = timestamp()
ON MATCH SET p.lastSeen = timestamp()
RETURN p
-- Merge relationships
MATCH (a:Person {name: 'Alix'}), (b:Person {name: 'Gus'})
MERGE (a)-[:KNOWS]->(b)
-- Merge with inline relationship SET
MERGE (a:Person {id: 1})-[r:KNOWS]->(b:Person {id: 2})
SET r.weight = 0.5
FOREACH¶
Iterate over a list and execute mutations for each element:
-- Create nodes from a list
FOREACH (name IN ['Alix', 'Gus', 'Vincent'] |
CREATE (:Person {name: name})
)
-- Update nodes from a list
MATCH (p:Person)
WITH collect(p) AS people
FOREACH (person IN people |
SET person.updated = true
)
CALL Subqueries¶
Run a subquery for each input row. Variables from the outer query are visible inside the block.
-- Per-person friend count via subquery
MATCH (p:Person)
CALL {
WITH p
MATCH (p)-[:KNOWS]->(friend)
RETURN count(friend) AS friend_count
}
RETURN p.name, friend_count
-- Subquery with mutations
MATCH (p:Person)
CALL {
WITH p
MATCH (p)-[:OWNS]->(item:Item)
WHERE item.expired = true
DETACH DELETE item
RETURN count(*) AS deleted
}
RETURN p.name, deleted
UNION¶
Combine results from multiple queries:
-- UNION (deduplicates rows)
MATCH (p:Person) WHERE p.city = 'Amsterdam'
RETURN p.name AS name
UNION
MATCH (p:Person) WHERE p.city = 'Berlin'
RETURN p.name AS name
-- UNION ALL (keeps duplicates)
MATCH (p:Person)-[:LIVES_IN]->(c:City)
RETURN c.name AS city
UNION ALL
MATCH (p:Person)-[:WORKS_IN]->(c:City)
RETURN c.name AS city
LOAD CSV¶
Import data from CSV files:
-- With headers (access fields by name)
LOAD CSV WITH HEADERS FROM 'data/people.csv' AS row
CREATE (:Person {name: row.name, age: toInteger(row.age)})
-- Without headers (access fields by index)
LOAD CSV FROM 'data/people.csv' AS row
CREATE (:Person {name: row[0], age: toInteger(row[1])})
-- Custom field terminator
LOAD CSV WITH HEADERS FROM 'data/people.tsv' AS row FIELDTERMINATOR '\t'
CREATE (:Person {name: row.name})
-- File URI
LOAD CSV WITH HEADERS FROM 'file:///data/people.csv' AS row
RETURN row.name
File path restrictions
LOAD CSV reads from the local filesystem only. You can specify a relative path (e.g., 'data/people.csv') or a file:// URI (e.g., 'file:///data/people.csv'). Remote URLs such as http:// or https:// are not supported. The file:// prefix is stripped before opening the file, so both 'data/file.csv' and 'file:///data/file.csv' resolve to the same local path.
Schema DDL¶
Create and manage indexes and constraints:
-- Create index
CREATE INDEX FOR (p:Person) ON (p.name)
-- Create vector index
CREATE INDEX FOR (n:Document) ON (n.embedding) USING VECTOR
-- Create text index
CREATE INDEX FOR (n:Article) ON (n.content) USING TEXT
-- Create constraint
CREATE CONSTRAINT FOR (p:Person) REQUIRE p.email IS UNIQUE
-- List indexes and constraints
SHOW INDEXES
SHOW CONSTRAINTS
-- Drop
DROP INDEX index_name
DROP CONSTRAINT constraint_name