Chapter 03 · Cypher Fundamentals: MATCH, RETURN, WHERE, Parameters, Ordering, and Pagination

Build Parameterized Read Queries in Cypher Shell and Verify Results Against Known Graph Fixtures

Convert Chapter 03 semantics into regression evidence: known rows, explicit parameters, stable ordering, null edge cases, and safe scripted execution.

Beginner → Intermediate120–145 minutesParameterized read-suite labNeo4j 2026.07.1 Community · Cypher 25Last reviewed: September 2026

Learning outcomes

The chapter ends by turning concepts into a reproducible acceptance suite. AtlasMart will load the deterministic fixture, set parameters in cypher-shell, run five read contracts, compare actual rows to known expectations, then deliberately introduce one faulty pagination/predicate case and prove the correction.

01

Run parameterized read queries from cypher-shell without embedding user values in query text.

02

Define expected row fixtures for MATCH/WHERE/RETURN/DISTINCT/ORDER BY/LIMIT behavior.

03

Use plain/verbose shell output appropriately for human and script-oriented verification.

04

Diagnose one unstable pagination case and one null-predicate edge case from evidence.

05

Package read-query invariants as regression tests before advanced pattern matching begins.

Chapter 03 continuity contract

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.

Version and execution note

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. Reset only the Chapter 03 read fixture

The chapter is designed to coexist with Chapters 01–02. The fixture uses stable MERGE keys and named constraints, so rerunning it is safe for the course graph. Cleanup should never use MATCH (n) DETACH DELETE n against an unrelated database. If a pristine state is required, reset the disposable Chapter 01 container/volumes using that chapter’s documented reset instead.

Cypher · idempotent AtlasMart read fixture
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);

2. Set cypher-shell parameters and verify connection identity

Cypher Shell · connection and parameter evidence
CALL dbms.components() YIELD name, versions, edition RETURN name, versions, edition;:param {customerId:'C-1001', region:'eu', tier:'gold', pageSize:2};:param;

Cypher Shell communicates over Bolt. For automation, credentials should come from a protected environment/secret mechanism rather than being embedded in committed shell history. The local disposable password from Chapter 01 is acceptable only for the isolated course instance.

3. Execute the read-query acceptance suite

Cypher · Q1 one customer → two orders
CYPHER 25MATCH (c:Customer {customerId:$customerId})-[:PLACED]->(o:Order)RETURN o.orderIdORDER BY o.orderId;
Cypher · Q2 one customer → three order-item rows
CYPHER 25MATCH (:Customer {customerId:$customerId})-[:PLACED]->(o:Order)-[:CONTAINS]->(p:Product)RETURN o.orderId, p.productIdORDER BY o.orderId, p.productId;
Cypher · Q3 DISTINCT purchased products
CYPHER 25MATCH (:Customer {customerId:$customerId})-[:PLACED]->(:Order)-[:CONTAINS]->(p:Product)RETURN DISTINCT p.productIdORDER BY p.productId;
Cypher · Q4 region+tier predicate
CYPHER 25MATCH (c:Customer)WHERE c.region=$region AND c.tier=$tierRETURN c.customerIdORDER BY c.customerId;
Cypher · Q5 deterministic page 1
CYPHER 25MATCH (o:Order)RETURN o.orderId, o.placedAtORDER BY o.placedAt DESC, o.orderId DESCLIMIT $pageSize;

Expected invariants for the default parameters: Q1 returns O-2001/O-2003; Q2 returns three rows; Q3 returns P-1001/P-1002; Q4 returns C-1001; Q5 returns the two newest rows according to the explicit timestamp+orderId ordering. If your output differs, inspect graph state before “fixing” the query.

4. Deliberately faulty case: LIMIT without deterministic ordering

Cypher · faulty page contract
CYPHER 25MATCH (o:Order)RETURN o.orderId, o.placedAtLIMIT 2;

This query can return two rows, but it cannot promise which two. A test that happens to pass repeatedly on a tiny unchanged store is not proof of an ordering contract. Repair by adding a total ORDER BY and repeat the test. A second edge case is Leo’s missing tier: verify that WHERE c.tier <> 'gold' excludes him because the predicate is null, then encode the intended missing-value policy explicitly.

Cypher · repaired deterministic page and explicit null policy
CYPHER 25MATCH (o:Order)RETURN o.orderId, o.placedAtORDER BY o.placedAt DESC, o.orderId DESCLIMIT 2;CYPHER 25MATCH (c:Customer)WHERE c.tier IS NULL OR c.tier <> 'gold'RETURN c.customerId, c.tierORDER BY c.customerId;

5. Script the suite without hard-coding query data

bash · run a parameterized Cypher file
cypher-shell -a neo4j://127.0.0.1:7687 -u neo4j -p "$NEO4J_PASSWORD" -d neo4j \  --format plain -P '{customerId:"C-1001", region:"eu", tier:"gold", pageSize:2}' \  -f chapter03-read-suite.cypher

On Windows, the current cypher-shell documentation also supports file execution and piping with PowerShell. Keep the query file in version control, keep secrets out of it, and compare normalized expected rows rather than brittle timing/plan-operator text. For performance tests later, record graph size, indexes, page-cache state and runtime separately.

Contract Expected fixture evidence Failure means
Q1 customer orders 2 rows: O-2001, O-2003 Fixture or PLACED match changed.
Q2 customer order items 3 rows Traversal cardinality changed.
Q3 unique purchased products 2 rows: P-1001, P-1002 DISTINCT/projection or fixture changed.
Q4 EU gold customers 1 row: C-1001 Predicate/null/data contract changed.
Q5 stable page 2 rows in timestamp+ID order Ordering/page contract changed.

Production judgment

A query suite should assert semantics before it asserts speed. Parameters protect the query boundary; row-count fixtures expose accidental expansion; null cases prevent silent exclusion; and stable ordering prevents pagination drift. Plan text is not a stable golden file. With these fundamentals proven, Chapter 04 can safely introduce variable-length paths, OPTIONAL MATCH, quantified patterns and shortest paths—features where row/path explosion becomes even easier to misunderstand.

Check your understanding

  1. Why should the suite compare expected rows rather than exact plan operator names?
  2. What does the Q2 row count prove?
  3. Why is LIMIT 2 not a page contract by itself?
  4. How should secrets be supplied to a scripted cypher-shell run?
  5. What Chapter 04 risk does the row-pipeline model prepare you for?
Review the answers

1. Planner/operator details can change across releases/statistics while result semantics remain correct.

2. It proves the deterministic customer→order→product traversal currently yields three binding rows.

3. Without ORDER BY, Neo4j does not guarantee which rows are returned.

4. Through protected environment/secret handling, not committed query files or copied production credentials.

5. Variable/optional path expansions can multiply rows/paths dramatically; understanding bindings and predicates is prerequisite to controlling them.

Summary and next step

Chapter 03 turns MATCH, WHERE, parameters, RETURN, DISTINCT, ORDER BY, SKIP/OFFSET and LIMIT into observable row transformations. Preserve the read fixture and acceptance queries: they become regression evidence as later chapters add indexes, advanced matching and performance tuning.

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.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.