Cypher Query Language Mastery
Module Duration : 6-8 hours
Learning Style : Hands-On + Pattern Recognition + Real-World Queries
Outcome : Write efficient, expressive Cypher queries for any graph problem
Introduction: ASCII Art for Graphs
Cypher uses ASCII art to represent graph patterns:
-- Nodes : ()
( alice ) -- node variable
(: Person ) -- node with label
( alice : Person ) -- node variable with label
( alice : Person { name : "Alice" } ) -- node with properties
-- Relationships : - [] ->
--> -- directed relationship
- [: KNOWS ] -> -- relationship with type
- [ k : KNOWS ] -> -- relationship variable
- [ k : KNOWS { since : 2020 } ] -> -- relationship with properties
- [: KNOWS | : FOLLOWS ] -> -- multiple types ( OR )
-- Patterns : Combine nodes and relationships
( alice : Person ) - [: KNOWS ] -> ( bob : Person )
( a ) - [: FRIEND *1..3 ] -> ( b ) -- variable length path ( 1 - 3 hops )
This visual syntax makes queries intuitive and readable .
Part 1: Basic Pattern Matching
MATCH: Find Patterns
Simple node match :
MATCH ( p : Person )
RETURN p . name , p . age
Relationship match :
MATCH ( alice : Person { name : "Alice" } ) - [: KNOWS ] -> ( friend )
RETURN friend . name
Multi-hop :
-- Friends of friends
MATCH ( alice : Person { name : "Alice" } ) - [: KNOWS ] -> ( friend ) - [: KNOWS ] -> ( fof )
RETURN fof . name
Undirected relationship (match either direction):
MATCH ( alice : Person { name : "Alice" } ) - [: KNOWS ] - ( friend )
RETURN friend . name
WHERE: Filter Results
MATCH ( p : Person ) - [: KNOWS ] -> ( friend )
WHERE p . name = "Alice"
AND friend . age > 25
RETURN friend . name , friend . age
Pattern predicates :
-- Find people who DON 'T know Bob
MATCH (p:Person)
WHERE NOT (p)-[:KNOWS]->(:Person {name: "Bob"})
RETURN p.name
String operations :
MATCH ( p : Person )
WHERE p . name STARTS WITH "A" -- or ENDS WITH , CONTAINS
RETURN p . name
Regular expressions :
MATCH ( p : Person )
WHERE p . email =~ ".*@example \\ .com"
RETURN p . name , p . email
List membership :
MATCH ( p : Person )
WHERE p . name IN [ "Alice" , "Bob" , "Charlie" ]
RETURN p . name
CREATE: Add Data
Create node :
CREATE ( p : Person { name : "Alice" , age : 30 } )
RETURN p
Create relationship :
MATCH ( alice : Person { name : "Alice" } )
MATCH ( bob : Person { name : "Bob" } )
CREATE ( alice ) - [: KNOWS { since : 2020 } ] -> ( bob )
Create pattern :
CREATE ( alice : Person { name : "Alice" } ) - [: KNOWS ] -> ( bob : Person { name : "Bob" } )
MERGE: Create if not exists
Idempotent create (prevents duplicates):
MERGE ( p : Person { name : "Alice" } )
ON CREATE SET p . created = timestamp ()
ON MATCH SET p . accessed = timestamp ()
RETURN p
Merge relationship :
MATCH ( alice : Person { name : "Alice" } )
MATCH ( bob : Person { name : "Bob" } )
MERGE ( alice ) - [ k : KNOWS ] -> ( bob )
ON CREATE SET k . since = 2020
RETURN k
SET: Update Properties
MATCH ( p : Person { name : "Alice" } )
SET p . age = 31 ,
p . city = "San Francisco"
RETURN p
Add label :
MATCH ( p : Person { name : "Alice" } )
SET p : Engineer
RETURN labels ( p ) -- [ "Person" , "Engineer" ]
Copy properties :
MATCH ( alice : Person { name : "Alice" } )
MATCH ( bob : Person { name : "Bob" } )
SET bob = alice -- Copy all properties
DELETE: Remove Data
Delete node (must delete relationships first!):
MATCH ( p : Person { name : "Alice" } )
DETACH DELETE p -- Delete node and all relationships
Delete relationship :
MATCH ( alice ) - [ k : KNOWS ] -> ( bob )
DELETE k
Conditional delete :
MATCH ( p : Person )
WHERE p . age > 100
DETACH DELETE p
Part 2: Variable-Length Paths
Syntax: *min..max
Friends within 1-3 hops :
MATCH ( alice : Person { name : "Alice" } ) - [: KNOWS *1..3 ] -> ( friend )
RETURN DISTINCT friend . name
Any depth (use carefully!):
MATCH ( alice : Person { name : "Alice" } ) - [: KNOWS * ] -> ( friend )
RETURN friend . name
LIMIT 100 -- Always limit unbounded paths !
Shortest Path
Find shortest path between two nodes :
MATCH path = shortestPath (
( alice : Person { name : "Alice" } ) - [: KNOWS * ] - ( bob : Person { name : "Bob" } )
)
RETURN length ( path ) AS hops , nodes ( path ) AS people
All shortest paths :
MATCH path = allShortestPaths (
( alice : Person { name : "Alice" } ) - [: KNOWS * ] - ( bob : Person { name : "Bob" } )
)
RETURN path
Path Functions
MATCH path = ( alice : Person { name : "Alice" } ) - [: KNOWS *1..3 ] -> ( friend )
RETURN
length ( path ) AS hops , -- Number of relationships
nodes ( path ) AS all_nodes , -- All nodes in path
relationships ( path ) AS all_rels -- All relationships in path
LIMIT 10
Part 3: Aggregations
Basic Aggregations
MATCH ( p : Person )
RETURN
count ( p ) AS total ,
avg ( p . age ) AS avg_age ,
min ( p . age ) AS min_age ,
max ( p . age ) AS max_age ,
sum ( p . salary ) AS total_salary
GROUP BY (implicit)
Cypher groups by non-aggregated columns:
-- Count friends per person ( grouped by p . name )
MATCH ( p : Person ) - [: KNOWS ] -> ( friend )
RETURN p . name , count ( friend ) AS friend_count
ORDER BY friend_count DESC
COLLECT: Aggregate to List
-- Collect all friends into a list
MATCH ( p : Person { name : "Alice" } ) - [: KNOWS ] -> ( friend )
RETURN p . name , collect ( friend . name ) AS friends
Collect distinct :
MATCH ( p : Person ) - [: KNOWS ] -> ( friend )
RETURN p . name , collect ( DISTINCT friend . name ) AS unique_friends
UNWIND: List to Rows
-- Expand list into rows
UNWIND [ "Alice" , "Bob" , "Charlie" ] AS name
CREATE ( p : Person { name : name } )
Flatten nested data :
WITH [[ 1 , 2 ], [ 3 , 4 ], [ 5 ]] AS nested
UNWIND nested AS list
UNWIND list AS value
RETURN value -- Returns : 1 , 2 , 3 , 4 , 5
Part 4: Advanced Patterns
OPTIONAL MATCH: Left Outer Join
-- Find all people , with their friends ( if any )
MATCH ( p : Person )
OPTIONAL MATCH ( p ) - [: KNOWS ] -> ( friend )
RETURN p . name , collect ( friend . name ) AS friends
-- People with no friends will have empty list []
Multiple Patterns
Comma separates independent patterns :
MATCH ( alice : Person { name : "Alice" } ),
( bob : Person { name : "Bob" } )
CREATE ( alice ) - [: KNOWS ] -> ( bob )
Chained patterns :
MATCH ( alice : Person { name : "Alice" } ) - [: KNOWS ] -> ( friend ) - [: LIKES ] -> ( movie : Movie )
RETURN movie . title , count ( friend ) AS popularity
ORDER BY popularity DESC
NOT Pattern
-- Find people who don 't know Bob
MATCH (p:Person)
WHERE NOT (p)-[:KNOWS]->(:Person {name: "Bob"})
RETURN p.name
EXISTS Subquery (Neo4j 4.0+)
-- Find people who know someone in San Francisco
MATCH ( p : Person )
WHERE EXISTS {
MATCH ( p ) - [ : KNOWS ] -> ( friend : Person )
WHERE friend . city = "San Francisco"
}
RETURN p . name
CASE Expressions
MATCH ( p : Person )
RETURN p . name ,
CASE
WHEN p . age < 18 THEN "Minor"
WHEN p . age < 65 THEN "Adult"
ELSE "Senior"
END AS age_group
Part 5: Functions
String Functions
RETURN
toUpper ( "hello" ) AS upper , -- "HELLO"
toLower ( "WORLD" ) AS lower , -- "world"
substring ( "Hello" , 1 , 3 ) AS sub , -- "ell"
replace ( "Hello" , "l" , "L" ) AS r , -- "HeLLo"
split ( "a,b,c" , "," ) AS parts , -- [ "a" , "b" , "c" ]
trim ( " hello " ) AS trimmed -- "hello"
Math Functions
RETURN
abs ( - 5 ) AS absolute , -- 5
ceil ( 4.3 ) AS ceiling , -- 5
floor ( 4.7 ) AS floor , -- 4
round ( 4.5 ) AS rounded , -- 5
sqrt ( 16 ) AS square_root , -- 4
rand () AS random -- 0.0 to 1.0
List Functions
WITH [ 1 , 2 , 3 , 4 , 5 ] AS numbers
RETURN
size ( numbers ) AS length , -- 5
head ( numbers ) AS first , -- 1
tail ( numbers ) AS rest , -- [ 2 , 3 , 4 , 5 ]
last ( numbers ) AS last_elem , -- 5
range ( 1 , 10 ) AS one_to_ten , -- [ 1 , 2 ,..., 10 ]
[ x IN numbers WHERE x > 3 ] AS filtered -- [ 4 , 5 ]
Date/Time Functions
RETURN
date () AS today , -- 2024 - 01 - 15
datetime () AS now , -- 2024 - 01 - 15 T10 : 30 :00 Z
timestamp () AS unix_timestamp , -- 1705317000000
duration . between ( date ( "2020-01-01" ), date ()) AS days_since
Graph Functions
MATCH ( p : Person ) - [ r : KNOWS ] -> ( friend )
RETURN
id ( p ) AS node_id , -- Internal node ID
id ( r ) AS rel_id , -- Internal relationship ID
labels ( p ) AS node_labels , -- [ "Person" ]
type ( r ) AS rel_type , -- "KNOWS"
keys ( p ) AS property_keys , -- [ "name" , "age" ]
properties ( p ) AS all_props -- { name : "Alice" , age : 30 }
1. Use Indexes
Create index :
CREATE INDEX person_name FOR ( p : Person ) ON ( p . name )
Check query plan :
EXPLAIN
MATCH ( p : Person { name : "Alice" } )
RETURN p
-- Look for "NodeIndexSeek" ( good ) vs "NodeByLabelScan" ( bad )
2. Filter Early
Bad (filter after expand):
MATCH ( p : Person ) - [: KNOWS ] -> ( friend )
WHERE p . name = "Alice" AND friend . age > 25
RETURN friend . name
Good (filter before expand):
MATCH ( p : Person { name : "Alice" } ) - [: KNOWS ] -> ( friend : Person )
WHERE friend . age > 25
RETURN friend . name
3. Limit Results
MATCH ( p : Person ) - [: KNOWS ] -> ( friend )
RETURN friend . name
LIMIT 100 -- Stop after 100 results
4. Use PROFILE to Find Bottlenecks
PROFILE
MATCH ( p : Person { name : "Alice" } ) - [: KNOWS *2 ] -> ( fof )
RETURN fof . name
Look for:
High DB Hits → Optimize
NodeByLabelScan → Add index
Cartesian Products → Fix query logic
5. Avoid Cartesian Products
Bad (creates all combinations):
MATCH ( p : Person ), ( m : Movie ) -- Cartesian product !
WHERE p . name = "Alice" AND m . title = "The Matrix"
RETURN p , m
Good (connected pattern):
MATCH ( p : Person { name : "Alice" } ) - [: WATCHED ] -> ( m : Movie { title : "The Matrix" } )
RETURN p , m
Part 7: Common Patterns
Pattern 1: Recommendations
“People like you who bought X also bought Y” :
MATCH ( user : User { id : $ userId } ) - [: PURCHASED ] -> ( product : Product )
MATCH ( product ) <- [: PURCHASED ] - ( other : User ) - [: PURCHASED ] -> ( recommendation : Product )
WHERE NOT ( user ) - [: PURCHASED ] -> ( recommendation )
RETURN recommendation . name , count ( * ) AS score
ORDER BY score DESC
LIMIT 10
Pattern 2: Shortest Path
“How are Alice and Bob connected?” :
MATCH path = shortestPath (
( alice : Person { name : "Alice" } ) - [: KNOWS * ] - ( bob : Person { name : "Bob" } )
)
RETURN [ node IN nodes ( path ) | node . name ] AS connection_path
Pattern 3: Influence/Centrality
“Who has the most friends?” (degree centrality):
MATCH ( p : Person ) - [: KNOWS ] -> ( friend )
RETURN p . name , count ( friend ) AS friend_count
ORDER BY friend_count DESC
LIMIT 10
“Find groups of mutual friends” (triangles):
MATCH ( a : Person ) - [: KNOWS ] -> ( b : Person ) - [: KNOWS ] -> ( c : Person ) - [: KNOWS ] -> ( a )
RETURN a . name , b . name , c . name
Pattern 5: Hierarchical Data
“Find all employees under a manager” (recursive):
MATCH ( manager : Employee { name : "Alice" } ) - [: MANAGES * ] -> ( employee )
RETURN employee . name , length ( path ) AS levels_down
Pattern 6: Time-Series Analysis
“What did users buy in the last 30 days?” :
MATCH ( user : User ) - [ p : PURCHASED ] -> ( product : Product )
WHERE p . timestamp > datetime () - duration ( { days : 30 } )
RETURN user . name , collect ( product . name ) AS recent_purchases
Part 8: Hands-On Exercises
Exercise 1: Social Network Queries
Setup :
CREATE ( alice : Person { name : "Alice" , age : 30 , city : "NYC" } )
CREATE ( bob : Person { name : "Bob" , age : 28 , city : "SF" } )
CREATE ( charlie : Person { name : "Charlie" , age : 35 , city : "NYC" } )
CREATE ( alice ) - [: KNOWS { since : 2015 } ] -> ( bob )
CREATE ( bob ) - [: KNOWS { since : 2018 } ] -> ( charlie )
CREATE ( charlie ) - [: KNOWS { since : 2020 } ] -> ( alice )
Queries :
Find Alice’s direct friends:
MATCH ( alice : Person { name : "Alice" } ) - [: KNOWS ] -> ( friend )
RETURN friend . name
Friends of friends (excluding Alice):
MATCH ( alice : Person { name : "Alice" } ) - [: KNOWS *2 ] -> ( fof )
WHERE fof <> alice
RETURN DISTINCT fof . name
Mutual friends of Alice and Bob:
MATCH ( alice : Person { name : "Alice" } ) - [: KNOWS ] -> ( mutual ) <- [: KNOWS ] - ( bob : Person { name : "Bob" } )
RETURN mutual . name
Average age of Alice’s friends:
MATCH ( alice : Person { name : "Alice" } ) - [: KNOWS ] -> ( friend )
RETURN avg ( friend . age ) AS avg_age
Exercise 2: Movie Recommendations
Setup :
CREATE ( alice : User { name : "Alice" } )
CREATE ( bob : User { name : "Bob" } )
CREATE ( matrix : Movie { title : "The Matrix" , year : 1999 } )
CREATE ( inception : Movie { title : "Inception" , year : 2010 } )
CREATE ( alice ) - [: WATCHED ] -> ( matrix )
CREATE ( alice ) - [: WATCHED ] -> ( inception )
CREATE ( bob ) - [: WATCHED ] -> ( matrix )
Query : Recommend movies Bob hasn’t seen (based on Alice):
MATCH ( bob : User { name : "Bob" } ) - [: WATCHED ] -> ( watched : Movie )
MATCH ( watched ) <- [: WATCHED ] - ( other : User ) - [: WATCHED ] -> ( rec : Movie )
WHERE NOT ( bob ) - [: WATCHED ] -> ( rec )
RETURN rec . title , count ( * ) AS score
ORDER BY score DESC
Original query (slow):
MATCH ( p : Person ) - [: KNOWS ] -> ( friend )
WHERE p . name = "Alice"
RETURN friend . name
Optimized :
CREATE INDEX person_name FOR ( p : Person ) ON ( p . name );
MATCH ( p : Person { name : "Alice" } ) - [: KNOWS ] -> ( friend )
RETURN friend . name
Verify improvement :
PROFILE [ query ]
-- Compare DB Hits before and after
Summary
Pattern Matching : Use ASCII art for intuitive graph patterns
Filtering : WHERE clause for predicates, pattern-based filtering
Aggregations : count, avg, sum, collect for grouped data
Variable Paths : *min..max for flexible traversals
Functions : Rich library for strings, math, lists, dates
Optimization : Indexes, early filtering, PROFILE for analysis
Next Steps : Apply Cypher to real-world data modeling scenarios!
What’s Next?
Module 5: Graph Data Modeling Design efficient graph schemas, handle many-to-many relationships, and model complex domains