382 lines
7.9 KiB
Markdown
382 lines
7.9 KiB
Markdown
# Common Cypher Mistakes and How to Avoid Them
|
|
|
|
## Clause Ordering Errors
|
|
|
|
### MATCH After CREATE Without WITH
|
|
|
|
**Error**: `Invalid input 'MATCH': expected ... WITH`
|
|
|
|
```cypher
|
|
// WRONG
|
|
CREATE (e:Event {id: $id})
|
|
MATCH (ref:Event {id: $refId}) // ERROR!
|
|
CREATE (e)-[:REFERENCES]->(ref)
|
|
|
|
// CORRECT - Use WITH to transition
|
|
CREATE (e:Event {id: $id})
|
|
WITH e
|
|
MATCH (ref:Event {id: $refId})
|
|
CREATE (e)-[:REFERENCES]->(ref)
|
|
```
|
|
|
|
**Rule**: After CREATE, you must use WITH before MATCH.
|
|
|
|
### WHERE After WITH Without Carrying Variables
|
|
|
|
**Error**: `Variable 'x' not defined`
|
|
|
|
```cypher
|
|
// WRONG - 'a' is lost
|
|
MATCH (a:Author), (e:Event)
|
|
WITH e
|
|
WHERE a.pubkey = $pubkey // ERROR: 'a' not in scope
|
|
|
|
// CORRECT - Include all needed variables
|
|
MATCH (a:Author), (e:Event)
|
|
WITH a, e
|
|
WHERE a.pubkey = $pubkey
|
|
```
|
|
|
|
**Rule**: WITH resets the scope. Include all variables you need.
|
|
|
|
### ORDER BY Without Aliased Return
|
|
|
|
**Error**: `Invalid input 'ORDER': expected ... AS`
|
|
|
|
```cypher
|
|
// WRONG in some contexts
|
|
RETURN n.name
|
|
ORDER BY n.name
|
|
|
|
// SAFER - Use alias
|
|
RETURN n.name AS name
|
|
ORDER BY name
|
|
```
|
|
|
|
## MERGE Mistakes
|
|
|
|
### MERGE on Complex Pattern Creates Duplicates
|
|
|
|
```cypher
|
|
// DANGEROUS - May create duplicate nodes
|
|
MERGE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})
|
|
|
|
// CORRECT - MERGE nodes separately first
|
|
MERGE (a:Person {name: 'Alice'})
|
|
MERGE (b:Person {name: 'Bob'})
|
|
MERGE (a)-[:KNOWS]->(b)
|
|
```
|
|
|
|
**Rule**: MERGE simple patterns, not complex ones.
|
|
|
|
### MERGE Without Unique Property
|
|
|
|
```cypher
|
|
// DANGEROUS - Will keep creating nodes
|
|
MERGE (p:Person) // No unique identifier!
|
|
SET p.name = 'Alice'
|
|
|
|
// CORRECT - Provide unique key
|
|
MERGE (p:Person {email: $email})
|
|
SET p.name = 'Alice'
|
|
```
|
|
|
|
**Rule**: MERGE must have properties that uniquely identify the node.
|
|
|
|
### Missing ON CREATE/ON MATCH
|
|
|
|
```cypher
|
|
// LOSES context of whether new or existing
|
|
MERGE (p:Person {id: $id})
|
|
SET p.updated_at = timestamp() // Always runs
|
|
|
|
// BETTER - Handle each case
|
|
MERGE (p:Person {id: $id})
|
|
ON CREATE SET p.created_at = timestamp()
|
|
ON MATCH SET p.updated_at = timestamp()
|
|
```
|
|
|
|
## NULL Handling Errors
|
|
|
|
### Comparing with NULL
|
|
|
|
```cypher
|
|
// WRONG - NULL = NULL is NULL, not true
|
|
WHERE n.email = null // Never matches!
|
|
|
|
// CORRECT
|
|
WHERE n.email IS NULL
|
|
WHERE n.email IS NOT NULL
|
|
```
|
|
|
|
### NULL in Aggregations
|
|
|
|
```cypher
|
|
// count(NULL) returns 0, collect(NULL) includes NULL
|
|
MATCH (n:Person)
|
|
OPTIONAL MATCH (n)-[:BOUGHT]->(p:Product)
|
|
RETURN n.name, count(p) // count ignores NULL
|
|
```
|
|
|
|
### NULL Propagation in Expressions
|
|
|
|
```cypher
|
|
// Any operation with NULL returns NULL
|
|
WHERE n.age + 1 > 21 // If n.age is NULL, whole expression is NULL (falsy)
|
|
|
|
// Handle with coalesce
|
|
WHERE coalesce(n.age, 0) + 1 > 21
|
|
```
|
|
|
|
## List and IN Clause Errors
|
|
|
|
### Empty List in IN
|
|
|
|
```cypher
|
|
// An empty list never matches
|
|
WHERE n.kind IN [] // Always false
|
|
|
|
// Check for empty list in application code before query
|
|
// Or use CASE:
|
|
WHERE CASE WHEN size($kinds) > 0 THEN n.kind IN $kinds ELSE true END
|
|
```
|
|
|
|
### IN with NULL Values
|
|
|
|
```cypher
|
|
// NULL in the list causes issues
|
|
WHERE n.id IN [1, NULL, 3] // NULL is never equal to anything
|
|
|
|
// Filter NULLs in application code
|
|
```
|
|
|
|
## Relationship Pattern Errors
|
|
|
|
### Forgetting Direction
|
|
|
|
```cypher
|
|
// WRONG - Creates both directions
|
|
MATCH (a)-[:FOLLOWS]-(b) // Undirected!
|
|
|
|
// CORRECT - Specify direction
|
|
MATCH (a)-[:FOLLOWS]->(b) // a follows b
|
|
MATCH (a)<-[:FOLLOWS]-(b) // b follows a
|
|
```
|
|
|
|
### Variable-Length Without Bounds
|
|
|
|
```cypher
|
|
// DANGEROUS - Potentially explosive
|
|
MATCH (a)-[*]->(b) // Any length path!
|
|
|
|
// SAFE - Set bounds
|
|
MATCH (a)-[*1..3]->(b) // 1 to 3 hops max
|
|
```
|
|
|
|
### Creating Duplicate Relationships
|
|
|
|
```cypher
|
|
// May create duplicates
|
|
CREATE (a)-[:KNOWS]->(b)
|
|
|
|
// Idempotent
|
|
MERGE (a)-[:KNOWS]->(b)
|
|
```
|
|
|
|
## Performance Mistakes
|
|
|
|
### Cartesian Products
|
|
|
|
```cypher
|
|
// WRONG - Cartesian product
|
|
MATCH (a:Person), (b:Product)
|
|
WHERE a.id = $personId AND b.id = $productId
|
|
CREATE (a)-[:BOUGHT]->(b)
|
|
|
|
// CORRECT - Single pattern or sequential
|
|
MATCH (a:Person {id: $personId})
|
|
MATCH (b:Product {id: $productId})
|
|
CREATE (a)-[:BOUGHT]->(b)
|
|
```
|
|
|
|
### Late Filtering
|
|
|
|
```cypher
|
|
// SLOW - Filters after collecting everything
|
|
MATCH (e:Event)
|
|
WITH e
|
|
WHERE e.kind = 1 // Should be in MATCH or right after
|
|
|
|
// FAST - Filter early
|
|
MATCH (e:Event)
|
|
WHERE e.kind = 1
|
|
```
|
|
|
|
### Missing LIMIT with ORDER BY
|
|
|
|
```cypher
|
|
// SLOW - Sorts all results
|
|
MATCH (e:Event)
|
|
RETURN e
|
|
ORDER BY e.created_at DESC
|
|
|
|
// FAST - Limits result set
|
|
MATCH (e:Event)
|
|
RETURN e
|
|
ORDER BY e.created_at DESC
|
|
LIMIT 100
|
|
```
|
|
|
|
### Unparameterized Queries
|
|
|
|
```cypher
|
|
// WRONG - No query plan caching, injection risk
|
|
MATCH (e:Event {id: '" + eventId + "'})
|
|
|
|
// CORRECT - Use parameters
|
|
MATCH (e:Event {id: $eventId})
|
|
```
|
|
|
|
## String Comparison Errors
|
|
|
|
### Case Sensitivity
|
|
|
|
```cypher
|
|
// Cypher strings are case-sensitive
|
|
WHERE n.name = 'alice' // Won't match 'Alice'
|
|
|
|
// Use toLower/toUpper for case-insensitive
|
|
WHERE toLower(n.name) = toLower($name)
|
|
|
|
// Or use regex with (?i)
|
|
WHERE n.name =~ '(?i)alice'
|
|
```
|
|
|
|
### LIKE vs CONTAINS
|
|
|
|
```cypher
|
|
// There's no LIKE in Cypher
|
|
WHERE n.name LIKE '%alice%' // ERROR!
|
|
|
|
// Use CONTAINS, STARTS WITH, ENDS WITH
|
|
WHERE n.name CONTAINS 'alice'
|
|
WHERE n.name STARTS WITH 'ali'
|
|
WHERE n.name ENDS WITH 'ice'
|
|
|
|
// Or regex for complex patterns
|
|
WHERE n.name =~ '.*ali.*ce.*'
|
|
```
|
|
|
|
## Index Mistakes
|
|
|
|
### Constraint vs Index
|
|
|
|
```cypher
|
|
// Constraint (also creates index, enforces uniqueness)
|
|
CREATE CONSTRAINT foo IF NOT EXISTS FOR (n:Node) REQUIRE n.id IS UNIQUE
|
|
|
|
// Index only (no uniqueness enforcement)
|
|
CREATE INDEX bar IF NOT EXISTS FOR (n:Node) ON (n.id)
|
|
```
|
|
|
|
### Index Not Used
|
|
|
|
```cypher
|
|
// Index on n.id won't help here
|
|
WHERE toLower(n.id) = $id // Function applied to indexed property!
|
|
|
|
// Store lowercase if needed, or create computed property
|
|
```
|
|
|
|
### Wrong Composite Index Order
|
|
|
|
```cypher
|
|
// Index on (kind, created_at) won't help query by created_at alone
|
|
MATCH (e:Event) WHERE e.created_at > $since // Index not used
|
|
|
|
// Either create single-property index or query by kind too
|
|
CREATE INDEX event_created_at FOR (e:Event) ON (e.created_at)
|
|
```
|
|
|
|
## Transaction Errors
|
|
|
|
### Read After Write in Same Transaction
|
|
|
|
```cypher
|
|
// In Neo4j, reads in a transaction see the writes
|
|
// But be careful with external processes
|
|
CREATE (n:Node {id: 'new'})
|
|
WITH n
|
|
MATCH (m:Node {id: 'new'}) // Will find 'n'
|
|
```
|
|
|
|
### Locks and Deadlocks
|
|
|
|
```cypher
|
|
// MERGE takes locks; avoid complex patterns that might deadlock
|
|
// Bad: two MERGEs on same labels in different order
|
|
Session 1: MERGE (a:Person {id: 1}) MERGE (b:Person {id: 2})
|
|
Session 2: MERGE (b:Person {id: 2}) MERGE (a:Person {id: 1}) // Potential deadlock
|
|
|
|
// Good: consistent ordering
|
|
Session 1: MERGE (a:Person {id: 1}) MERGE (b:Person {id: 2})
|
|
Session 2: MERGE (a:Person {id: 1}) MERGE (b:Person {id: 2})
|
|
```
|
|
|
|
## Type Coercion Issues
|
|
|
|
### Integer vs String
|
|
|
|
```cypher
|
|
// Types must match
|
|
WHERE n.id = 123 // Won't match if n.id is "123"
|
|
WHERE n.id = '123' // Won't match if n.id is 123
|
|
|
|
// Use appropriate parameter types from Go
|
|
params["id"] = int64(123) // For integer
|
|
params["id"] = "123" // For string
|
|
```
|
|
|
|
### Boolean Handling
|
|
|
|
```cypher
|
|
// Neo4j booleans vs strings
|
|
WHERE n.active = true // Boolean
|
|
WHERE n.active = 'true' // String - different!
|
|
```
|
|
|
|
## Delete Errors
|
|
|
|
### Delete Node With Relationships
|
|
|
|
```cypher
|
|
// ERROR - Node still has relationships
|
|
MATCH (n:Person {id: $id})
|
|
DELETE n
|
|
|
|
// CORRECT - Delete relationships first
|
|
MATCH (n:Person {id: $id})
|
|
DETACH DELETE n
|
|
```
|
|
|
|
### Optional Match and Delete
|
|
|
|
```cypher
|
|
// WRONG - DELETE NULL causes no error but also doesn't help
|
|
OPTIONAL MATCH (n:Node {id: $id})
|
|
DELETE n // If n is NULL, nothing happens silently
|
|
|
|
// Better - Check existence first or handle in application
|
|
MATCH (n:Node {id: $id})
|
|
DELETE n
|
|
```
|
|
|
|
## Debugging Tips
|
|
|
|
1. **Use EXPLAIN** to see query plan without executing
|
|
2. **Use PROFILE** to see actual execution metrics
|
|
3. **Break complex queries** into smaller parts to isolate issues
|
|
4. **Check parameter types** - mismatched types are a common issue
|
|
5. **Verify indexes exist** with `SHOW INDEXES`
|
|
6. **Check constraints** with `SHOW CONSTRAINTS`
|