Chapter 06 · Graph Writes: CREATE, MERGE, SET, REMOVE, DELETE, and Idempotent Mutation

DELETE vs DETACH DELETE, Relationship Cleanup, Cascades by Application Policy, and Safety Guardrails

Delete graph state with explicit blast radius: distinguish relationship cleanup, node deletion, DETACH semantics, application cascades, and bounded production deletion before executing destructive queries.

Intermediate → Advanced120–150 minutesSafe deletion labNeo4j 2026.07.1 Community · Cypher 25Last reviewed: September 2026

Learning outcomes

AtlasMart must purge a synthetic test order while preserving its customer and product. A broad DETACH DELETE is easy to type, but deletion is a graph operation: connected relationships, downstream audit requirements, retention rules, and the exact match predicate define the blast radius.

01

Explain why simple DELETE cannot remove a node that still has relationships.

02

Distinguish deleting relationships explicitly from DETACH DELETE of a node.

03

Use preflight counts and stable IDs to bound destructive matches before executing them.

04

Treat cascade behavior as an application/domain policy rather than an automatic relational foreign-key assumption.

05

Design reversible test procedures and production large-delete strategies with transaction/memory limits in mind.

Chapter 06 baseline · reviewed 9 September 2026

The mandatory lab continues the accepted course baseline: Neo4j Community 2026.07.1, database neo4j, explicit CYPHER 25 on version-sensitive examples, authentication enabled, local Bolt at bolt://localhost:7687, no mandatory APOC/GDS plugin, and the AtlasMart Customer/Product/Order model from Chapters 01–05. Neo4j 5.26.30 remains the current LTS comparison line. Optional concurrency examples use Neo4j Python Driver 6.3.x; the mandatory mutation logic itself is plain Community Cypher.

Safety and evidence rule

This chapter changes graph state. Every destructive example targets dedicated IDs prefixed P-WRITE-, O-WRITE-, or O-DELETE-, and every cleanup query repeats those predicates. Never broaden them to MATCH (n) DETACH DELETE n in a database containing unrelated work. This generation environment does not run Neo4j or Docker, so expected results are stated as deterministic invariants, not fabricated captured output.

Re-establish the isolated Chapter 06 write fixture

The fixture deliberately reuses AtlasMart's stable business identifiers and Community-supported uniqueness constraints. It creates or normalizes one customer, one dedicated product, one dedicated order, and their PLACED/CONTAINS relationships. Because every identity is deterministic and the write pattern uses MERGE, rerunning the fixture should leave the target counts unchanged.

Cypher · idempotent Chapter 06 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;MERGE (c:Customer {customerId:'C-1001'})  ON CREATE SET c.name='Ava Chen', c.region='eu', c.createdAt=datetime('2026-09-09T00:00:00Z')  ON MATCH  SET c.lastSeenAt=datetime('2026-09-09T00:00:00Z');MERGE (p:Product {productId:'P-WRITE-1001'})  ON CREATE SET p.name='Atlas Travel Camera', p.price=149.90, p.status='active', p.createdAt=datetime('2026-09-09T00:00:00Z')  ON MATCH  SET p.name='Atlas Travel Camera', p.price=149.90, p.status='active';MERGE (o:Order {orderId:'O-WRITE-2001'})  ON CREATE SET o.status='pending', o.source='chapter06', o.createdAt=datetime('2026-09-09T00:00:00Z')  ON MATCH  SET o.source='chapter06';MERGE (c)-[:PLACED]->(o)MERGE (o)-[line:CONTAINS]->(p)  ON CREATE SET line.quantity=1, line.unitPrice=149.90  ON MATCH  SET line.quantity=1, line.unitPrice=149.90;
Cypher · acceptance counts for only the dedicated slice
CYPHER 25MATCH (p:Product {productId:'P-WRITE-1001'})OPTIONAL MATCH (c:Customer {customerId:'C-1001'})-[placed:PLACED]->(o:Order {orderId:'O-WRITE-2001'})OPTIONAL MATCH (o)-[line:CONTAINS]->(p)RETURN count(DISTINCT p) AS products,       count(DISTINCT o) AS orders,       count(DISTINCT placed) AS placedRelationships,       count(DISTINCT line) AS containsRelationships;

The deterministic target for the dedicated slice is 1 / 1 / 1 / 1. Those counts do not imply the entire database contains only one Product or Order; previous chapters intentionally contain additional AtlasMart data.

1. DELETE removes only the entity you bound

DELETE can remove a relationship directly. A node with connected relationships cannot be deleted with plain DELETE until those relationships are removed. In Cypher 25, NODETACH DELETE is an explicit GQL-conformant spelling with the same functional behavior as ordinary node DELETE: it does not detach relationships.

Cypher · create isolated deletion target
CYPHER 25MERGE (c:Customer {customerId:'C-1001'})MERGE (p:Product {productId:'P-WRITE-1001'})MERGE (o:Order {orderId:'O-DELETE-9001'})SET o.status='test-only', o.source='chapter06-delete'MERGE (c)-[:PLACED]->(o)MERGE (o)-[:CONTAINS {quantity:1}]->(p);
Cypher · preflight the exact target and degree
CYPHER 25MATCH (o:Order {orderId:'O-DELETE-9001'})OPTIONAL MATCH (o)-[r]-()RETURN o.orderId AS orderId, count(r) AS connectedRelationships;

2. Plain DELETE protects you from silently orphaning relationships

Expected failure · node still has relationships
CYPHER 25MATCH (o:Order {orderId:'O-DELETE-9001'})DELETE o;

The failure is useful evidence: the node still participates in graph facts. Decide whether those relationships should be deleted, reattached, archived elsewhere, or should block deletion altogether.

3. Explicit relationship cleanup gives the cascade a name

Cypher · application-defined cascade in one transaction
CYPHER 25MATCH (o:Order {orderId:'O-DELETE-9001'})OPTIONAL MATCH (o)-[r]-()DELETE rDELETE o;

This transaction states the cascade directly: delete all relationships touching only the specifically identified test order, then delete that order. It preserves Customer/Product nodes. For real orders, such a cascade might violate audit, tax, payment, or retention rules; the correct policy may be soft deletion, status transition, redaction, or archival instead.

4. DETACH DELETE is concise but increases review burden

Cypher · targeted DETACH DELETE
CYPHER 25MATCH (o:Order {orderId:'O-DELETE-9001'})DETACH DELETE o;

DETACH DELETE removes the bound node and all relationships connected to it. The dangerous part is not the clause itself; it is a broad or wrong MATCH. Current documentation explicitly warns that MATCH (n) DETACH DELETE n is appropriate only for small example datasets, not large production deletion, and that it does not remove indexes or schema.

Two-step destructive review

Before executing a production-like delete, first run the exact MATCH with a RETURN count(...), stable identifiers, and sample rows. Only after the candidate set is understood should the same predicate be paired with DELETE. Never change the predicate between preflight and execution without reviewing again.

5. Relationship deletion can be the entire business operation

Sometimes the entity remains valid and only a connection has expired. For example, AtlasMart may remove a temporary RELATED_TO recommendation edge without deleting either product.

Cypher · delete only one relationship fact
CYPHER 25MATCH (:Product {productId:'P-WRITE-1001'})-[r:RELATED_TO {reason:'temporary'}]->(other:Product)DELETE rRETURN other.productId AS preservedProduct;

Graph deletion should start from the business fact being retired. “Delete the node because its relationship is stale” is often the wrong abstraction.

6. Large deletes need bounded transactions

Very large updates consume transaction memory and hold locks/resources until completion. Current Neo4j guidance recommends transactional batching (for example, CALL { ... } IN TRANSACTIONS) rather than one giant DETACH DELETE. The exact batch size must be measured on the target hardware, workload, graph degree, and recovery expectations; there is no universal safe number.

Pattern · bounded large-delete workflow, adapt only after dry-run review
CYPHER 25MATCH (o:Order)WHERE o.source='expired-test-import'CALL (o) {  DETACH DELETE o} IN TRANSACTIONS OF 1000 ROWS;

This pattern is intentionally not part of the mandatory tiny lab. It illustrates transaction partitioning. In production, add audit/backup/recovery controls, dry-run counts, rate/resource monitoring, and stop conditions appropriate to the dataset.

7. Deliberately wrong: broad DETACH DELETE as cleanup

Never use this on the shared academy graph
MATCH (n)DETACH DELETE n;

This destroys all matched graph data and can erase unrelated chapters' fixtures. The safe course cleanup targets only known Chapter 06 IDs. If the intent is to reset an entire disposable database, recreate/reset that dedicated database/container explicitly rather than normalizing a graph-wide destructive query into everyday workflow.

Lab: preflight, fail safely, cascade deliberately, verify survivors

Create O-DELETE-9001, verify two relationships, attempt plain DELETE and record the expected failure, then choose either explicit relationship deletion or targeted DETACH DELETE. Afterward prove: order count is zero; Customer C-1001 still exists; Product P-WRITE-1001 still exists.

Cypher · post-delete invariant
CYPHER 25OPTIONAL MATCH (o:Order {orderId:'O-DELETE-9001'})WITH count(o) AS ordersRemainingMATCH (c:Customer {customerId:'C-1001'})MATCH (p:Product {productId:'P-WRITE-1001'})RETURN ordersRemaining,       count(DISTINCT c) AS customersPreserved,       count(DISTINCT p) AS productsPreserved;

The target invariant is 0 / 1 / 1.

Production judgment

Deletion policy belongs with retention, audit, privacy, authorization, backup/recovery, and downstream integration design. In Aura or Enterprise environments, privileges can also restrict DETACH DELETE. For tenant-aware data, prove the tenant predicate is part of every destructive match. Prefer archival/redaction when the legal/business requirement is “forget a field” rather than “erase the entire connected entity.”

Check your understanding

  1. Why does plain DELETE reject a connected node?
  2. What does DETACH DELETE add?
  3. Why run a preflight MATCH/RETURN before a destructive query?
  4. Why is cascade behavior an application/domain policy?
  5. Why is one huge production DELETE risky even with the correct predicate?
Review the answers

1. Because Neo4j cannot leave relationships pointing to a deleted node; connected relationships must be removed explicitly or via DETACH DELETE.

2. It deletes the selected node and all relationships connected to it.

3. It makes the candidate set and relationship degree observable before irreversible state change.

4. The database cannot infer retention, audit, ownership, or whether neighboring entities should survive.

5. Large transactions consume memory, hold locks/resources longer, complicate rollback/recovery, and can create bursty operational impact.

Summary and next step

Safe deletion is predicate discipline plus domain policy plus verification. The final lesson combines the chapter's ideas into a replay-safe import/upsert workflow whose committed graph remains correct under repeated and concurrent execution.

Authoritative references

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.