Skip to content

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

EXPLAIN and PROFILE

-- Show query plan without executing
EXPLAIN MATCH (p:Person)-[:KNOWS]->(f:Person)
RETURN f.name

-- Execute and return per-operator metrics
PROFILE MATCH (p:Person)-[:KNOWS]->(f:Person)
RETURN f.name