Cypher Quick Reference Guide
A comprehensive quick reference for Cypher, the query language for graph databases. This guide focuses on Cypher syntax and patterns that work across Neo4j, Kuzu, and other graph databases supporting Cypher.
For SQL Developers: How Cypher Relates to SQL
If you’re coming from a SQL background, you’ll find Cypher surprisingly familiar. Both are declarative query languages, but they’re optimized for different data models:
Key Similarities:
-
MATCH is like SELECT – it retrieves data
-
WHERE works exactly the same for filtering
-
RETURN is like SELECT in SQL – it projects what to return
-
CREATE is like INSERT – adds new data
-
DELETE is like DELETE – removes data
-
Aggregations (count, sum, avg) work similarly
-
WITH is like subqueries or CTEs (Common Table Expressions)
Key Differences:
-
Instead of tables and rows, you work with nodes and relationships
-
Pattern matching replaces JOINs: (a)-[:REL]->(b) instead of FROM table1 JOIN table2
-
Relationships are first-class citizens with types and directions
-
No need for foreign keys – relationships are explicit
Think of Cypher as SQL for graphs, where you describe the shape of the data you want rather than how to join tables.
Table of Contents
-
Basic Concepts
-
Node Patterns
-
Relationship Patterns
-
CREATE
-
MATCH
-
MERGE
-
WHERE
-
RETURN
-
WITH
-
PATHS
-
CALL and Procedures
-
DELETE and DETACH
-
Aggregations
-
Indexes and Constraints
-
Common Patterns
-
Kuzu vs Neo4j Notes
Basic Concepts
Graph Structure
-
Nodes: Entities (vertices) with labels and properties
-
Relationships: Connections between nodes with types and properties
-
Properties: Key-value pairs on nodes and relationships
Syntax Basics
-
MATCH, CREATE, MERGE, WHERE, RETURN, WITH are case-insensitive
-
Strings: ‘single quotes’ or “double quotes”
-
Comments: // single line or /* multi-line */
-
Variables: case-sensitive, start with letter or underscore
Node Patterns
// Basic node pattern(n) // Any node(n:Person) // Node with label Person(n:Person {name: 'Alice'}) // Node with label and properties(p:Person:Employee) // Node with multiple labelsRelationship Patterns
// Directed relationships()-[]->() // Any relationship, directed()-[:KNOWS]->() // Specific relationship type()-[:KNOWS|FRIENDS]->() // Multiple relationship types()-[:KNOWS*1..3]->() // Variable length (1 to 3 hops)
// Undirected relationships()-[]-() // Any relationship, undirected()-[:KNOWS]-() // Specific type, undirected
// Relationship with properties()-[:KNOWS {since: 2020}]->()()-[r:KNOWS]->() // Capture relationship as variableCREATE
Purpose: Creates new nodes and/or relationships in the graph. Unlike MERGE, CREATE always creates new elements, even if they already exist.
// Create a simple nodeCREATE (n:Person {name: 'Alice', age: 30})
// Create multiple nodesCREATE (p1:Person {name: 'Alice'}), (p2:Person {name: 'Bob'})
// Create nodes and relationshipCREATE (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}), (a)-[:KNOWS {since: 2020}]->(b)
// Create with pathCREATE p = (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})RETURN pMATCH
Purpose: Finds and matches patterns in the graph. It’s the most common clause for querying data.
// Match all nodesMATCH (n) RETURN n
// Match specific labelMATCH (p:Person) RETURN p.name, p.age
// Match with relationshipMATCH (a:Person)-[:KNOWS]->(b:Person)RETURN a.name, b.name
// Match with variable lengthMATCH (a:Person)-[:KNOWS*1..2]->(b:Person)RETURN a.name, b.name
// Match with relationship variableMATCH (a)-[r:KNOWS]->(b)RETURN type(r), properties(r)
// Optional matchOPTIONAL MATCH (p:Person)-[:HAS_PET]->(pet)RETURN p.name, pet.nameMERGE
Purpose: Ensures existence – creates if not found, matches if exists. Like CREATE + MATCH combined. Prevents duplicates.
// MERGE ensures existence (like CREATE or MATCH)MERGE (p:Person {name: 'Alice'})ON CREATE SET p.age = 30, p.created = timestamp()ON MATCH SET p.lastSeen = timestamp()
// MERGE with relationshipsMERGE (a:Person {name: 'Alice'})MERGE (b:Person {name: 'Bob'})MERGE (a)-[:KNOWS]->(b)
// MERGE with pathMERGE p = (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})ON CREATE SET p.created = timestamp()WHERE
Purpose: Filters results based on conditions. Works with MATCH, CALL, and other clauses.
// Basic filteringMATCH (p:Person)WHERE p.age > 25RETURN p.name
// Multiple conditionsMATCH (p:Person)WHERE p.age > 25 AND p.city = 'New York'RETURN p.name
// String operationsMATCH (p:Person)WHERE p.name STARTS WITH 'A'RETURN p.name
// Pattern in WHEREMATCH (p:Person)WHERE EXISTS { MATCH (p)-[:HAS_FRIEND]->(friend) WHERE friend.age > 30}RETURN p.name
// Comparison with listMATCH (p:Person)WHERE p.age IN [25, 30, 35]RETURN p.nameRETURN
Purpose: Specifies what data to return from the query. Projects and formats the final result.
// Basic returnMATCH (p:Person)RETURN p.name, p.age
// Return allMATCH (p:Person)RETURN p
// Return with aliasMATCH (p:Person)RETURN p.name AS fullName, p.age AS years
// Return with calculationsMATCH (p:Person)RETURN p.name, p.age + 5 AS futureAge
// Return distinctMATCH (p:Person)-[:KNOWS]->(friend)RETURN DISTINCT p.name, friend.name
// Limit resultsMATCH (p:Person)RETURN p.nameLIMIT 10
// Order resultsMATCH (p:Person)RETURN p.name, p.ageORDER BY p.age DESCWITH
Purpose: Passes results from one part of the query to the next. Enables aggregation and chaining of complex queries.
// Pass results to next stepMATCH (p:Person)-[:KNOWS]->(friend)WITH p, count(friend) AS friendCountWHERE friendCount > 5RETURN p.name, friendCount
// Aggregate and continueMATCH (p:Person)-[:HAS_ORDER]->(o:Order)WITH p, sum(o.amount) AS totalSpentWHERE totalSpent > 1000MATCH (p)-[:HAS_FRIEND]->(friend)RETURN p.name, totalSpent, count(friend) AS friends
// Multiple WITH clausesMATCH (p:Person)-[:HAS_POST]->(post:Post)WITH p, count(post) AS postCountWITH p, postCount, postCount * 2 AS doubledWHERE doubled > 10RETURN p.name, postCount, doubledPATHS
Purpose: Captures and manipulates entire paths (sequences of nodes and relationships) for complex traversals.
// Capture entire pathMATCH p = (a:Person)-[:KNOWS*1..3]->(b:Person)RETURN p
// Path functionsMATCH p = (a:Person)-[:KNOWS]->(b:Person)RETURN nodes(p), relationships(p)
// Shortest pathMATCH p = shortestPath((a:Person)-[:KNOWS*]-(b:Person))WHERE a.name = 'Alice' AND b.name = 'Bob'RETURN p
// All shortest pathsMATCH p = allShortestPaths((a:Person)-[:KNOWS*]-(b:Person))WHERE a.name = 'Alice' AND b.name = 'Bob'RETURN p
// Path lengthMATCH p = (a:Person)-[:KNOWS*]->(b:Person)WHERE length(p) > 2RETURN a.name, b.name, length(p) AS pathLength
// Extract from pathMATCH p = (a:Person)-[:KNOWS]->(b:Person)-[:WORKS_AT]->(c:Company)RETURN a.name, b.name, c.name, [node in nodes(p) | node.name] AS nodeNamesCALL and Procedures
Purpose: Executes procedures (predefined functions) to perform operations beyond standard Cypher. Used for system functions, data import, algorithms, etc.
// CALL with built-in proceduresCALL db.info() YIELD version, editionRETURN version, edition
// CALL with parametersCALL db.awaitIndex(':Person(name)', 30) YIELD valueRETURN value
// CALL with subquery (Neo4j 4.x+)CALL { MATCH (p:Person) WHERE p.age > 30 RETURN count(p) AS olderThan30}RETURN olderThan30
// CALL with apoc procedures (Neo4j only)CALL apoc.load.json('https://api.example.com/data')YIELD valueCREATE (n:Node {data: value})
// CALL with aggregationMATCH (p:Person)CALL { WITH p MATCH (p)-[:KNOWS]->(friend) RETURN count(friend) AS friendCount}RETURN p.name, friendCount
// CALL with YIELD and WHERECALL db.propertyKeys() YIELD propertyKeyWHERE propertyKey STARTS WITH 'name'RETURN propertyKey
// CALL with multiple YIELDCALL db.labels() YIELD labelWITH labelCALL db.indexes() YIELD indexName, propertiesWHERE label IN propertiesRETURN label, indexName, propertiesDELETE and DETACH
Purpose: Removes nodes and relationships from the graph. DELETE removes only if no relationships exist, DETACH removes relationships along with nodes.
// Delete nodes (only if no relationships exist)MATCH (p:Person {name: 'Alice'})DELETE p
// Detach delete nodes (removes relationships too)MATCH (p:Person {name: 'Alice'})DETACH DELETE p
// Delete specific relationshipsMATCH (a:Person)-[r:KNOWS]->(b:Person)WHERE a.name = 'Alice' AND b.name = 'Bob'DELETE r
// Detach delete all relationships of a nodeMATCH (p:Person {name: 'Alice'})-[r]-()DELETE r
// Delete with patternMATCH (p:Person {name: 'Alice'})-[r:KNOWS]-()DETACH DELETE p, r
// Remove properties (not delete)MATCH (p:Person {name: 'Alice'})REMOVE p.tempProperty
// Remove labelsMATCH (p:Person {name: 'Alice'})REMOVE p:PersonAggregations
// CountMATCH (p:Person)RETURN count(p) AS totalPeople
// Count distinctMATCH (p:Person)-[:KNOWS]->(friend)RETURN p.name, count(DISTINCT friend) AS uniqueFriends
// Sum, avg, min, maxMATCH (p:Person)RETURN sum(p.age) AS totalAge, avg(p.age) AS averageAge, min(p.age) AS youngest, max(p.age) AS oldest
// CollectMATCH (p:Person)-[:KNOWS]->(friend)RETURN p.name, collect(friend.name) AS friends
// Aggregation with conditionsMATCH (p:Person)RETURN count(CASE WHEN p.age = 30 THEN 1 END) AS matureIndexes and Constraints
// Create indexCREATE INDEX person_name_index FOR (p:Person) ON (p.name)
// Create unique constraintCREATE CONSTRAINT person_name_unique FOR (p:Person) REQUIRE p.name IS UNIQUE
// Create node key constraintCREATE CONSTRAINT person_node_key FOR (p:Person) REQUIRE (p.name, p.email) IS NODE KEY
// Show indexesSHOW INDEXES
// Show constraintsSHOW CONSTRAINTS
// Drop indexDROP INDEX person_name_index
// Drop constraintDROP CONSTRAINT person_name_uniqueCommon Patterns
// Find mutual friendsMATCH (a:Person)-[:KNOWS]->(mutual:Person)(f))
// Using CALL with aggregationMATCH (p:Person)CALL { WITH p MATCH (p)-[:KNOWS]->(friend) RETURN count(friend) AS friendCount}RETURN p.name, friendCount
// CALL with apoc procedures (Neo4j only)CALL apoc.load.json('https://api.example.com/data')YIELD valueCREATE (n:Node {data: value})
// CALL with db proceduresCALL db.propertyKeys() YIELD propertyKeyRETURN collect(propertyKey) AS allPropertiesKuzu vs Neo4j Notes
Kuzu Advantages
-
Embedded: No server required, runs in-process
-
Performance: Optimized for analytical queries
-
Simplicity: Easier setup and deployment
-
Memory efficient: Better for large graphs on limited resources
Neo4j Advantages
-
APOC Library: Extensive procedures for data import, graph algorithms, etc.
-
Bloom: Visual query builder
-
Enterprise features: Clustering, security, monitoring
-
Larger ecosystem: More tools and integrations
APOC Equivalent in Kuzu
Kuzu doesn’t have APOC, but provides:
-
Built-in functions for common operations
-
Python/C++ API for custom extensions
-
Regular updates with new features
Migration Considerations
-
Most basic Cypher works in both
-
APOC procedures need custom implementation in Kuzu
-
Kuzu focuses on analytical queries
-
Neo4j better for transactional workloads
Quick Tips
-
Always use labels for better performance
-
Use parameters in applications: CREATE (p:Person {name: $name})
-
Profile queries: PROFILE MATCH… to optimize
-
Use indexes for frequently queried properties
-
MERGE over CREATE when you want to avoid duplicates
-
WITH for chaining complex queries
-
Path variables for complex traversals
Resources
-
Cypher Reference Manual
-
Kuzu Documentation
-
Cypher Cheatsheet