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.
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.
Explain why simple DELETE cannot remove a node
that still has relationships.
Distinguish deleting relationships explicitly from
DETACH DELETE of a node.
Use preflight counts and stable IDs to bound destructive matches before executing them.
Treat cascade behavior as an application/domain policy rather than an automatic relational foreign-key assumption.
Design reversible test procedures and production large-delete strategies with transaction/memory limits in mind.
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.
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 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 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 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 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
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 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 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.
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 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.
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
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 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
- Why does plain DELETE reject a connected node?
- What does DETACH DELETE add?
- Why run a preflight MATCH/RETURN before a destructive query?
- Why is cascade behavior an application/domain policy?
- 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
- Current Neo4j versions — Release/LTS snapshot used to pin the course baseline.
- Cypher Manual — CREATE — CREATE semantics for nodes, relationships, parameters, and dynamic labels/types.
- Cypher Manual — MERGE — Match-or-create semantics, ON CREATE/ON MATCH, constraints, and concurrent relationship merges.
- Cypher Manual — SET — Property/label updates and map replacement versus mutation.
- Cypher Manual — REMOVE — Property and label removal semantics.
- Cypher Manual — DELETE — DELETE, NODETACH DELETE, DETACH DELETE, and large-delete guidance.
- Operations Manual — transactional behavior — ACID, read-committed isolation, locking, transaction logs, and deadlock behavior.
- CALL subqueries in transactions — Batching large transactional work into bounded subtransactions.
- Transaction management — Large-update memory considerations and transaction lifecycle.