Chapter 03 · Cypher Fundamentals: MATCH, RETURN, WHERE, Parameters, Ordering, and Pagination
Project Results with RETURN, Aliases, DISTINCT, Expressions, Maps, and Pattern Comprehensions
Turn binding rows into deliberate API shapes and use DISTINCT only when uniqueness is part of the result semantics.
Learning outcomes
A graph read is not finished when a pattern matches. AtlasMart
must turn binding rows into API values: scalar properties,
aliases, expressions, maps, unique rows, and compact
related-data lists. RETURN defines that projection
boundary.
Project nodes, relationships, scalar properties and expressions with stable aliases.
Explain DISTINCT as result-row deduplication over selected expressions.
Build map projections for API-shaped results without confusing query maps with stored properties.
Use pattern comprehensions to produce related-value lists while tracking cardinality and null behavior.
Recognize when a projection hides accidental expansion instead of fixing the query shape.
Continue Chapters 01–02 with Neo4j Community
2026.07.1, database neo4j, explicit
CYPHER 25 in version-sensitive examples, local
container atlasmart-neo4j, Bolt
127.0.0.1:7687, HTTP 127.0.0.1:7474,
and the existing constraint-backed AtlasMart domain IDs. This
chapter does not redesign the graph; it adds a deterministic
read fixture and treats Cypher as a pipeline of row bindings
produced and transformed by patterns, predicates and
projections.
The current Cypher Manual covers Cypher 25. Cypher 5 is frozen
while new language features since Neo4j 2025.06 are added to
Cypher 25; current 2026.02+ newly created databases explicitly
default to Cypher 25, while existing deployments may differ.
The mandatory examples therefore use
CYPHER 25 and avoid assuming a server-wide
default. This environment has no running Neo4j/Docker runtime,
so expected results are deterministic fixture invariants
rather than fabricated captured output.
1. RETURN defines the result contract
CYPHER 25CREATE CONSTRAINT customer_id IF NOT EXISTS FOR (c:Customer) REQUIRE c.customerId IS UNIQUE;CREATE CONSTRAINT product_id IF NOT EXISTS FOR (p:Product) REQUIRE p.productId IS UNIQUE;CREATE CONSTRAINT order_id IF NOT EXISTS FOR (o:Order) REQUIRE o.orderId IS UNIQUE;CREATE CONSTRAINT category_id IF NOT EXISTS FOR (c:Category) REQUIRE c.categoryId IS UNIQUE;MERGE (c1:Customer {customerId:'C-1001'}) SET c1.name='Ava Chen', c1.tier='gold', c1.region='eu'MERGE (c2:Customer {customerId:'C-1002'}) SET c2.name='Noah Smith', c2.tier='silver', c2.region='us'MERGE (c3:Customer {customerId:'C-1003'}) SET c3.name='Mina Rahimi', c3.tier='gold', c3.region='me'MERGE (c4:Customer {customerId:'C-1004'}) SET c4.name='Leo Martin', c4.region='eu'MERGE (cat1:Category {categoryId:'CAT-CAMERAS'}) SET cat1.name='Cameras'MERGE (cat2:Category {categoryId:'CAT-AUDIO'}) SET cat2.name='Audio'MERGE (p1:Product {productId:'P-1001'}) SET p1.name='Trail Camera', p1.price=129.90, p1.rating=4.7MERGE (p2:Product {productId:'P-1002'}) SET p2.name='Studio Headphones', p2.price=89.00, p2.rating=4.7MERGE (p3:Product {productId:'P-1003'}) SET p3.name='Action Camera', p3.price=219.00, p3.rating=4.5MERGE (p4:Product {productId:'P-1004'}) SET p4.name='USB Microphone', p4.price=75.00MERGE (p1)-[:IN_CATEGORY]->(cat1)MERGE (p3)-[:IN_CATEGORY]->(cat1)MERGE (p2)-[:IN_CATEGORY]->(cat2)MERGE (p4)-[:IN_CATEGORY]->(cat2)MERGE (o1:Order {orderId:'O-2001'}) SET o1.placedAt=datetime('2026-08-01T09:00:00Z'), o1.status='paid', o1.total=218.90MERGE (o2:Order {orderId:'O-2002'}) SET o2.placedAt=datetime('2026-08-02T10:30:00Z'), o2.status='paid', o2.total=219.00MERGE (o3:Order {orderId:'O-2003'}) SET o3.placedAt=datetime('2026-08-03T12:00:00Z'), o3.status='shipped', o3.total=129.90MERGE (o4:Order {orderId:'O-2004'}) SET o4.placedAt=datetime('2026-08-04T14:15:00Z'), o4.status='paid', o4.total=164.00MERGE (o5:Order {orderId:'O-2005'}) SET o5.placedAt=datetime('2026-08-04T14:15:00Z'), o5.status='paid', o5.total=75.00MERGE (c1)-[:PLACED]->(o1)MERGE (c2)-[:PLACED]->(o2)MERGE (c1)-[:PLACED]->(o3)MERGE (c3)-[:PLACED]->(o4)MERGE (c4)-[:PLACED]->(o5)MERGE (o1)-[:CONTAINS {quantity:1}]->(p1)MERGE (o1)-[:CONTAINS {quantity:1}]->(p2)MERGE (o2)-[:CONTAINS {quantity:1}]->(p3)MERGE (o3)-[:CONTAINS {quantity:1}]->(p1)MERGE (o4)-[:CONTAINS {quantity:1}]->(p2)MERGE (o4)-[:CONTAINS {quantity:1}]->(p4)MERGE (o5)-[:CONTAINS {quantity:1}]->(p4);
CYPHER 25MATCH (c:Customer)-[:PLACED]->(o:Order)WHERE o.orderId=$orderIdRETURN o.orderId AS orderId, c.customerId AS customerId, c.name AS customerName, o.status AS status, o.total AS total;
Returning an entire node is convenient during exploration, but production APIs usually benefit from explicit fields and aliases. Explicit projection prevents incidental property additions from silently changing an API response.
2. DISTINCT deduplicates selected result rows
CYPHER 25MATCH (:Customer {customerId:'C-1001'})-[:PLACED]->(:Order)-[:CONTAINS]->(p:Product)RETURN p.productId AS productIdORDER BY productId;CYPHER 25MATCH (:Customer {customerId:'C-1001'})-[:PLACED]->(:Order)-[:CONTAINS]->(p:Product)RETURN DISTINCT p.productId AS productIdORDER BY productId;
Trail Camera appears through two different orders. The first
query correctly has two Trail Camera rows because there are two
purchase paths. The second says the API wants the set of product
IDs. DISTINCT is therefore a semantic choice. It
can also require state/memory, so do not sprinkle it everywhere
to hide unexplained row multiplication.
Current Cypher also supports explicit
RETURN ALL as GQL-aligned syntax; simple
RETURN already keeps duplicates. For
compatibility-focused examples the course uses ordinary
RETURN and RETURN DISTINCT, which
keep the intent obvious across Cypher 5/25.
3. Expressions and map projections shape application data
CYPHER 25MATCH (c:Customer)-[:PLACED]->(o:Order)WHERE o.orderId=$orderIdRETURN o { .orderId, .status, .total, customer: c {.customerId, .name}, isLarge: o.total >= $largeOrderThreshold} AS order;
The returned map is a constructed query value. It is ideal for driver/API transport but is not a nested property stored on the Order node. Its keys are part of the response contract and should be tested like any serializer.
4. Pattern comprehension can keep one outer row while collecting related values
CYPHER 25MATCH (o:Order {orderId:$orderId})RETURN o.orderId AS orderId, [(o)-[line:CONTAINS]->(p:Product) | p { .productId, .name, quantity: line.quantity } ] AS items;
A pattern comprehension returns a list derived from matching a pattern. It can be more compact than returning one row per item, but it does not make fan-out free: Neo4j still has to match the relationships, and the resulting list occupies memory/driver payload. Bound the outer lookup and understand expected degree.
5. Wrong approach: DISTINCT as a universal repair tool
If an API unexpectedly emits many rows, adding
DISTINCT may make the final output look right while
masking a Cartesian product or incorrect expansion. Diagnose
cardinality first. Return enough variables to reveal the binding
rows, then decide whether duplicates are semantically identical
or represent different paths/events.
CYPHER 25MATCH (c:Customer {customerId:$customerId})-[:PLACED]->(o:Order)-[:CONTAINS]->(p:Product)RETURN c.customerId, o.orderId, p.productId, p.nameORDER BY o.orderId, p.productId;
Lab: build three stable response shapes
CYPHER 25MATCH (o:Order)RETURN o.orderId, o.status, o.totalORDER BY o.orderId;CYPHER 25MATCH (c:Customer)-[:PLACED]->(o:Order)WHERE c.customerId=$customerIdRETURN o {.orderId,.placedAt,.status,.total} AS orderORDER BY o.placedAt, o.orderId;CYPHER 25MATCH (:Customer {customerId:$customerId})-[:PLACED]->(:Order)-[:CONTAINS]->(p:Product)RETURN DISTINCT p.productId, p.nameORDER BY p.productId;
Verification: each result shape has documented fields; aliases do not depend on internal IDs; the map projection returns one row per matched Order; the distinct-set query intentionally removes repeated purchase paths; and all public ordering is explicit.
Check your understanding
- What does RETURN control?
- What exactly does DISTINCT deduplicate?
- Does a map projection persist a nested map property?
- Why can pattern comprehension still be expensive?
- What should you inspect before adding DISTINCT to an unexpectedly large result?
Review the answers
1. The expressions/variables included in the result set and their aliases.
2. Duplicate result rows over the selected expressions, not stored graph entities.
3. No. It constructs a result value.
4. Its inner pattern still traverses relationships and materializes list values.
5. The underlying binding rows/cardinality and query pattern to identify the source of multiplication.
Production judgment
Projection is part of schema governance for an API. Prefer explicit fields, stable aliases and bounded related lists. Treat DISTINCT as semantics, not cleanup. The next lesson adds ordering and pagination, where apparently harmless omissions can produce unstable page boundaries and expensive deep offsets.
Summary and next step
Project Results with RETURN, Aliases, DISTINCT, Expressions, Maps, and Pattern Comprehensions is useful only when its assumptions and observed evidence stay attached to the decision. The examples above establish a reproducible mechanism and boundary; they do not turn one lab result into a universal production rule.
Next, continue to ORDER BY, SKIP/OFFSET, LIMIT, Deterministic Pagination, and Why Deep Pagination Can Be Expensive. Carry forward the verified assumptions, fixture state, version/edition boundaries, and measurements from this lesson instead of treating the next topic as an isolated recipe.
Authoritative references
- Current Neo4j versions — Official current-release and 5.26 LTS patch snapshot.
- Cypher Manual introduction — Current Cypher 25 baseline and Cypher 5 compatibility framing.
- MATCH — Pattern matching and variable-binding semantics.
- Variables — Variable naming and scope across query parts.
- WHERE — Pattern constraints and post-WITH filtering semantics.
- Predicates — Boolean/comparison/string/list/type predicates and three-valued results.
- Working with null — Null propagation and missing-property semantics.
- RETURN — Projection, aliases, expressions and DISTINCT semantics.
- List expressions and pattern comprehension — Current fixed/variable pattern-comprehension syntax and behavior.
- ORDER BY — Only ORDER BY guarantees result ordering; sort semantics and index-backed ordering.
- SKIP / OFFSET — Offset semantics and OFFSET synonym.
- LIMIT — Row limiting semantics and the absence of ordering guarantees without ORDER BY.
- Cypher Shell — Bolt CLI, parameter support, script/file execution and output modes.
- Query tuning and plans — EXPLAIN/PROFILE and execution-plan evidence.