Cypher Guide
MATCH & RETURN
MATCH finds patterns in the graph. Nodes use () and relationships use [].
// Find all nodes with label Person
MATCH (p:Person)
RETURN p.name, p.age
ORDER BY p.age DESC
LIMIT 10;
// Find node by property
MATCH (p:Person {name: "Alice"})
RETURN p;
// Traverse a relationship
MATCH (a:Person)-[:KNOWS]->(b:Person)
RETURN a.name, b.name;
// Directed vs undirected
MATCH (a)-[:KNOWS]->(b) // directed: a knows b
MATCH (a)-[:KNOWS]-(b) // undirected: either direction
// Variable-length path (1 to 3 hops)
MATCH (a:Person {name:"Alice"})-[:KNOWS*1..3]->(b:Person)
RETURN DISTINCT b.name;
// WHERE clause
MATCH (p:Person)
WHERE p.age >= 18 AND p.country = "US"
RETURN p.name;
// Optional match (like LEFT JOIN)
MATCH (p:Person {name:"Alice"})
OPTIONAL MATCH (p)-[:HAS_PET]->(pet)
RETURN p.name, pet.name;
CREATE & MERGE
// CREATE node
CREATE (p:Person {name: "Bob", age: 30, email: "[email protected]"});
// CREATE relationship
MATCH (a:Person {name:"Alice"}), (b:Person {name:"Bob"})
CREATE (a)-[:KNOWS {since: 2020}]->(b);
// CREATE with RETURN
CREATE (p:Product {id: 1, name: "Laptop", price: 999})
RETURN p;
// MERGE: create only if not exists (get or create)
MERGE (p:Person {email: "[email protected]"})
ON CREATE SET p.name = "Carol", p.createdAt = datetime()
ON MATCH SET p.lastSeen = datetime()
RETURN p;
// MERGE relationship
MATCH (a:Person {name:"Alice"}), (b:Person {name:"Bob"})
MERGE (a)-[:FOLLOWS]->(b);
// SET: update properties
MATCH (p:Person {name:"Alice"})
SET p.age = 31, p.active = true;
// REMOVE: delete property or label
MATCH (p:Person {name:"Alice"})
REMOVE p.active;
// DELETE node (must have no relationships first)
MATCH (p:Person {name:"Temp"})
DETACH DELETE p; -- DETACH removes relationships too
Aggregation & WITH
// count, collect, sum, avg
MATCH (p:Person)-[:KNOWS]->(friend)
RETURN p.name, count(friend) AS friend_count
ORDER BY friend_count DESC;
// collect: aggregate into list
MATCH (p:Person)-[:LIKES]->(m:Movie)
RETURN p.name, collect(m.title) AS liked_movies;
// WITH: intermediate results (like a subquery)
MATCH (p:Person)-[:KNOWS]->(friend)
WITH p, count(friend) AS cnt
WHERE cnt > 3
RETURN p.name, cnt;
// UNWIND: expand a list into rows
UNWIND [1, 2, 3] AS x
RETURN x * 2;
// UNWIND with MERGE (bulk insert from list)
UNWIND [{name:"A",age:25},{name:"B",age:30}] AS row
MERGE (p:Person {name: row.name})
SET p.age = row.age;
Indexes & Constraints
-- Create index (Neo4j 4.x+)
CREATE INDEX idx_person_name FOR (p:Person) ON (p.name);
CREATE INDEX idx_person_email FOR (p:Person) ON (p.email);
-- Composite index
CREATE INDEX idx_product_cat_price FOR (p:Product) ON (p.category, p.price);
-- Full-text index
CREATE FULLTEXT INDEX idx_article_body FOR (a:Article) ON EACH [a.title, a.body];
CALL db.index.fulltext.queryNodes("idx_article_body", "neo4j performance") YIELD node, score;
-- Unique constraint (implies index)
CREATE CONSTRAINT uq_person_email FOR (p:Person) REQUIRE p.email IS UNIQUE;
-- Existence constraint (Enterprise)
CREATE CONSTRAINT nn_person_name FOR (p:Person) REQUIRE p.name IS NOT NULL;
-- List indexes
SHOW INDEXES;
SHOW CONSTRAINTS;
APOC Procedures
-- APOC (Awesome Procedures on Cypher) must be installed
-- Load JSON from URL
CALL apoc.load.json("https://api.example.com/data") YIELD value
UNWIND value.items AS item
MERGE (p:Product {id: item.id}) SET p.name = item.name;
-- Batch execution (avoid heap overflow on large imports)
CALL apoc.periodic.iterate(
"MATCH (p:Person) RETURN p",
"SET p.processed = true",
{batchSize: 1000, parallel: false}
);
-- Date formatting
RETURN apoc.date.format(timestamp(), 'ms', 'yyyy-MM-dd');
-- String utilities
RETURN apoc.text.camelCase("hello world");
RETURN apoc.text.slugify("Hello World!");
-- Graph algorithms (via GDS or APOC)
CALL apoc.algo.dijkstra(startNode, endNode, 'ROAD', 'distance')
YIELD path, weight;