Comprehensive guide for writing modern Neo4j Cypher read queries. Essential for text2cypher MCP tools and LLMs generating Cypher queries...
This skill helps generate Neo4j Cypher read queries using modern syntax patterns and avoiding deprecated features. It focuses on efficient query patterns for graph traversal and data retrieval.
When generating Cypher queries, immediately avoid these REMOVED features:
id() function → Use elementId()ALWAYS filter NULL values when sorting:
// WRONG - May include null values
MATCH (n:Node)
RETURN n.name, n.value
ORDER BY n.value
// CORRECT - Filter nulls before sorting
MATCH (n:Node)
WHERE n.value IS NOT NULL
RETURN n.name, n.value
ORDER BY n.value
Use standard Cypher patterns with modern syntax:
MATCH (n:Label {property: value})
WHERE n.otherProperty IS :: STRING
RETURN n
Consider Quantified Path Patterns (QPP) for better performance:
// Instead of: MATCH (a)-[*1..5]->(b)
// Use: MATCH (a)-[]-{1,5}(b)
// With filtering:
MATCH (a)((n WHERE n.active)-[]->(m)){1,5}(b)
Use COUNT{}, EXISTS{}, and COLLECT{} subqueries:
MATCH (p:Person)
WHERE count{(p)-[:KNOWS]->()} > 5
RETURN p.name,
exists{(p)-[:MANAGES]->()} AS isManager
Use CALL subqueries for sophisticated data retrieval:
MATCH (d:Department)
CALL (d) {
MATCH (d)<-[:WORKS_IN]-(p:Person)
WHERE p.salary IS NOT NULL // Filter nulls
WITH p ORDER BY p.salary DESC
LIMIT 3
RETURN collect(p.name) AS topEarners
}
RETURN d.name, topEarners
// Old: RETURN size((n)-[]->())
// Modern: RETURN count{(n)-[]->()}
// Old: WHERE exists((n)-[:REL]->())
// Modern: WHERE EXISTS {MATCH (n)-[:REL]->()}
// Also valid: WHERE exists{(n)-[:REL]->()}
// Old: WHERE id(n) = 123
// Modern: WHERE elementId(n) = "4:abc123:456"
// Note: elementId returns a string, not integer
// Always add null check
MATCH (n:Node)
WHERE n.sortProperty IS NOT NULL
RETURN n
ORDER BY n.sortProperty
// Or use NULLS LAST
MATCH (n:Node)
RETURN n
ORDER BY n.sortProperty NULLS LAST
Load the appropriate reference file when:
Before finalizing any generated query:
// Problem: RETURN n.prop, count(*) + n.other
// Solution: WITH n.prop AS prop, n.other AS other, count(*) AS cnt
// RETURN prop, cnt + other
// Use elementId() but note it returns a string, not integer
// Problem: MATCH (a)-[r*]->(), (b)-[r*]->()
// Solution: MATCH (a)-[r1*]->(), (b)-[r2*]->()
WHERE n:Label1|Label2 // OR
WHERE n:Label1&Label2 // AND
WHERE n:!Archived // NOT
WHERE n.prop IS :: STRING
WHERE n.value IS :: INTEGER NOT NULL
WHERE n.data IS :: LIST<STRING>
Always prefer modern syntax patterns for better performance and maintainability.