Advanced Cypher -2

 Pattern Comprehensions in Neo4j are a powerful way to “inline” queries inside a single Cypher statement.

They let you collect lists of related nodes or values while applying filters, without needing a separate MATCH.


🔹 Syntax

[ pattern IN expression WHERE filter | projection ]
  • pattern → a traversal pattern like (u)-[:WORKS_ON]->(p)

  • expression → usually the starting node

  • WHERE filter → optional condition

  • projection → what to return (node, property, or expression)


1️⃣ Example Dataset

(Alice:User {name: "Alice"}) (Bob:User {name: "Bob"}) (Charlie:User {name: "Charlie"}) (ProjectX:Project {name: "ProjectX"}) (ProjectY:Project {name: "ProjectY"}) (Alice)-[:WORKS_ON {since:2023}]->(ProjectX) (Alice)-[:WORKS_ON {since:2022}]->(ProjectY) (Bob)-[:WORKS_ON {since:2021}]->(ProjectX) (Charlie)-[:WORKS_ON {since:2024}]->(ProjectY)

2️⃣ Collect Alice’s projects with filters

MATCH (u:User {name: "Alice"}) RETURN u.name AS user, [ (u)-[:WORKS_ON]->(p:Project) WHERE p.name STARTS WITH "Project" | p.name ] AS projects

✅ Output:

userprojects
Alice["ProjectX", "ProjectY"]

3️⃣ Filter by relationship property

MATCH (u:User {name: "Alice"}) RETURN [ (u)-[r:WORKS_ON]->(p:Project) WHERE r.since >= 2023 | p.name ] AS recent_projects

✅ Output:

recent_projects
["ProjectX"]

4️⃣ Nested properties from relationships

MATCH (u:User {name: "Alice"}) RETURN [ (u)-[r:WORKS_ON]->(p:Project) WHERE r.since >= 2022 | {project: p.name, year: r.since} ] AS details

✅ Output:

details
[{project:"ProjectX", year:2023}, {project:"ProjectY", year:2022}]

5️⃣ Aggregation inside comprehension

MATCH (p:Project) RETURN p.name, size([ (u:User)-[:WORKS_ON]->(p) | u ]) AS team_size

✅ Output:

p.nameteam_size
ProjectX2
ProjectY2

👉 When to use Pattern Comprehensions?

  • To inline subqueries without extra MATCH/OPTIONAL MATCH.

  • To filter and project lists of related nodes/relationships.

  • To keep queries compact & expressive.

 Let’s now look at Subqueries in Neo4j — introduced in Neo4j 4.x — they’re very useful when you need multi-step logic or want to scope variables.


🔹 Subqueries with CALL { ... }

A subquery is like a "query inside a query".
It’s wrapped inside CALL { ... } and returns a result that the outer query can continue using.


1️⃣ Syntax

CALL { MATCH (u:User)-[:WORKS_ON]->(p:Project) RETURN u, p } RETURN u.name, p.name

2️⃣ Example Dataset (same as before)

(Alice:User {name: "Alice"}) (Bob:User {name: "Bob"}) (ProjectX:Project {name: "ProjectX"}) (ProjectY:Project {name: "ProjectY"}) (Alice)-[:WORKS_ON {since:2023}]->(ProjectX) (Alice)-[:WORKS_ON {since:2022}]->(ProjectY) (Bob)-[:WORKS_ON {since:2021}]->(ProjectX)

3️⃣ Example – Get Alice’s recent projects (subquery version)

MATCH (u:User {name:"Alice"}) CALL { WITH u MATCH (u)-[r:WORKS_ON]->(p:Project) WHERE r.since >= 2023 RETURN collect(p.name) AS recent_projects } RETURN u.name, recent_projects

✅ Output:

u.namerecent_projects
Alice["ProjectX"]

4️⃣ Example – Aggregations per group

MATCH (p:Project) CALL { WITH p MATCH (u:User)-[:WORKS_ON]->(p) RETURN count(u) AS team_size } RETURN p.name, team_size

✅ Output:

p.nameteam_size
ProjectX2
ProjectY1

5️⃣ Example – Optional subquery

You can also make the whole subquery optional:

MATCH (u:User {name:"Alice"}) CALL { WITH u OPTIONAL MATCH (u)-[:WORKS_ON]->(p:Project) RETURN collect(p.name) AS all_projects } RETURN u.name, all_projects

✅ Output:

u.nameall_projects
Alice["ProjectX", "ProjectY"]

🔹 When to use Subqueries vs Pattern Comprehensions?

Pattern ComprehensionSubquery (CALL {})
Inline filtering & projectionMulti-step logic
Works inside RETURN onlyCan be used anywhere in query
Good for small filters/listsGood for aggregations, multiple matches
Lightweight, compactMore verbose but powerful

👉 So, if you just need a list with filters → use pattern comprehension.
👉 If you need aggregation, optional logic, or multiple steps → use subqueries.

Let’s dig into List Operations in Cypher — especially two powerful clauses: UNWIND and WITH.


🔹 1. UNWIND – Turn a list into rows

UNWIND takes a list and expands it into individual rows.
Think of it as the opposite of collect().


Example 1: Simple UNWIND

UNWIND [1, 2, 3] AS num RETURN num

✅ Output:

num
1
2
3

Example 2: Create multiple nodes from a list

UNWIND ["Alice", "Bob", "Charlie"] AS name CREATE (:User {name: name})

👉 Creates 3 users: Alice, Bob, Charlie.


Example 3: UNWIND with properties

UNWIND [ {name:"Alice", role:"Dev"}, {name:"Bob", role:"Tester"} ] AS data CREATE (:User {name: data.name, role: data.role})

👉 Creates 2 users with properties.



🔹 2. WITH – Pipeline / pass variables

WITH is like a pipe: it passes variables to the next part of the query.
Also used for:

  • Aggregations

  • Aliases

  • Ordering, limiting

  • Scoping variables


Example 1: Simple WITH

MATCH (u:User) WITH u.name AS username RETURN username

✅ Renames u.nameusername.


Example 2: Filtering with WITH

MATCH (u:User)-[:WORKS_ON]->(p:Project) WITH u, count(p) AS project_count WHERE project_count > 1 RETURN u.name, project_count

✅ Finds users who work on more than 1 project.


Example 3: Chaining WITH

MATCH (u:User)-[:WORKS_ON]->(p:Project) WITH p, collect(u.name) AS team RETURN p.name, team

✅ Collects all users into a list per project.

Output:

p.nameteam
ProjectX["Alice", "Bob"]
ProjectY["Alice"]


🔹 3. Combining UNWIND + WITH

Example – Split list per user

MATCH (u:User) WITH u, ["Python", "Neo4j"] AS skills UNWIND skills AS skill CREATE (u)-[:HAS_SKILL]->(:Skill {name: skill})

👉 For each user, creates 2 skill relationships: Python, Neo4j.


✅ Key Takeaways

  • UNWIND → Break a list into rows (for iteration / creation).

  • WITH → Pipe, filter, rename, aggregate, and control query flow.

  • Together → They let you transform data flexibly.

Comments