Chapter 02 · Property Graph Model: Nodes, Relationships, Labels, Properties, Types, and Schema Discipline

Model Identity, Natural Keys, Surrogate Identifiers, Relationship Properties, and Effective Dating

Design identity and time explicitly so graph references survive infrastructure change and historical relationships remain queryable without accidental overwrite.

Intermediate115–135 minutesIdentity and effective-dating labNeo4j 2026.07.1 Community · Cypher 25Last reviewed: September 2026

Learning outcomes

Identity is not a single Neo4j feature. AtlasMart has natural identifiers such as SKU or supplier registration number, application-generated surrogate IDs such as P-1001, Neo4j implementation element IDs, and relationships whose properties may change over time. Choosing the wrong identity surface makes restores, merges, CDC, imports, and temporal queries brittle.

01

Compare natural keys, application surrogate identifiers, composite business identity, and Neo4j element IDs.

02

Choose stable application-generated IDs backed by uniqueness constraints for long-lived graph references.

03

Place facts on relationships only when they describe the connection rather than one endpoint.

04

Model effective dating with explicit validity intervals and define overlap/current-state invariants.

05

Recognize when a relationship should be reified as a node because it has independent lifecycle or multi-party semantics.

Chapter 02 continuity contract

Continue Chapter 01's free/local baseline: 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 disposable password atlasmart-course-2026. Existing AtlasMart identifiers use Community-supported uniqueness constraints. This chapter adds :Category, :Supplier, :Store, and :Shipment identities and makes graph-model contracts observable.

Execution and edition note

The generation environment does not provide a running Docker daemon or Neo4j server, so commands are documentation-checked but not presented as captured output. Community supports the uniqueness constraints used in the mandatory lab. Current property-existence, property-type, and key constraints are Enterprise-only; when those stronger contracts are discussed, the Community path uses validation queries plus application/import checks rather than fabricating successful Community DDL.

Business identity must survive infrastructure change

A natural key comes from the business domain: an ISO code, immutable external registration number, or perhaps a SKU if the organization truly guarantees its stability. A surrogate ID is created by the application specifically to identify the entity independently of descriptive attributes. Neo4j's elementId() is neither: it is an implementation-facing identifier whose mapping is not guaranteed as a durable application identity across transactions/environments.

Identity choice Strength Risk
Natural key Human/domain meaningful and may already exist across systems Can change, be recycled, be composite, or have messy source-system semantics
Application surrogate ID Stable across copy/import/restore if preserved; simple relationship/reference key Needs generation/governance and source-system mapping
Composite business key Can accurately express tenant + external ID or other scoped identity More fields in every lookup and migration; changes are harder
elementId() Convenient for in-transaction implementation evidence Not a durable business key; mapping guarantees are intentionally limited

AtlasMart keeps customerId, productId, orderId, and similar application IDs as its graph identity surface. If a source ERP supplier has registrationNumber, AtlasMart can store it and possibly constrain it when the business truly guarantees uniqueness, but it should not silently replace the internal integration ID until lifecycle rules are known.

Relationship properties belong to the connection

STOCKED_AT.onHand is about a product at a specific store; it does not belong solely to the product or the store. CONTAINS.quantity is about one product's participation in one order. SUPPLIED_BY.preferred describes AtlasMart's product-supplier connection. Those are good relationship-property candidates because the value changes when either endpoint changes.

Cypher · relationship-local facts
CYPHER 25MATCH (p:Product {productId:'P-1001'}),(st:Store {storeId:'ST-TEH-01'})MERGE (p)-[stock:STOCKED_AT]->(st)SET stock.onHand=14, stock.reorderPoint=5, stock.observedAt=datetime('2026-09-09T10:00:00Z');CYPHER 25MATCH (p:Product {productId:'P-1001'})-[stock:STOCKED_AT]->(st:Store)RETURN p.productId, st.storeId, stock.onHand, stock.reorderPoint, stock.observedAt;

A counterexample is Product.name: copying it onto every STOCKED_AT relationship creates write amplification and conflicting copies with no relationship-specific meaning. Denormalization can still be justified for measured queries, but it must be owned explicitly as a derived projection rather than smuggled into the model.

Effective dating makes “current” a query, not an overwrite

Some relationships are true only during an interval. Supplier contracts, store assignments, pricing agreements, or category classifications can change. Overwriting one edge property destroys history. Effective dating keeps separate facts with validFrom and optional validTo, but then AtlasMart must define whether intervals may overlap and how open-ended current state is represented.

Cypher · versioned supply relationship
CYPHER 25MATCH (p:Product {productId:'P-1001'}),(s:Supplier {supplierId:'SUP-1001'})CREATE (p)-[:SUPPLIED_BY {  agreementId:'AGR-2026-001',  validFrom:date('2026-01-01'),  validTo:date('2026-06-30'),  unitCost:91.00}]->(s)CREATE (p)-[:SUPPLIED_BY {  agreementId:'AGR-2026-002',  validFrom:date('2026-07-01'),  unitCost:94.50}]->(s);

This intentionally creates separate historical relationships rather than MERGEing one generic product→supplier edge. The identity now includes agreement semantics. If agreements are referenced by invoices, approvals, documents, or more than two parties, model :SupplyAgreement {agreementId} as a node and connect participants to it. Reification is justified by lifecycle/query needs, not by a rule that every relationship with properties must become a node.

Cypher · current effective relationship as of a parameter date
CYPHER 25MATCH (p:Product {productId:$productId})-[r:SUPPLIED_BY]->(s:Supplier)WHERE r.validFrom <= $asOf AND (r.validTo IS NULL OR r.validTo > $asOf)RETURN s.supplierId, r.agreementId, r.unitCost, r.validFrom, r.validToORDER BY r.validFrom DESC;

Define interval boundaries precisely. The query above uses a half-open interpretation for validTo (validFrom <= asOf < validTo), while an open-ended missing validTo means still valid. Another system may use inclusive end dates. The important part is one documented convention, not the specific choice.

Deliberately break temporal identity with overlapping active contracts

Cypher · create a conflicting interval in the disposable fixture
CYPHER 25MATCH (p:Product {productId:'P-1001'}),(s:Supplier {supplierId:'SUP-1001'})CREATE (p)-[:SUPPLIED_BY {  agreementId:'AGR-BAD-OVERLAP',  validFrom:date('2026-08-01'),  unitCost:89.00}]->(s);

If AtlasMart's contract says only one active agreement may exist for a product-supplier pair, this write violates the model even though each relationship is structurally valid. Detect it by testing an as-of date and counting active relationships:

Cypher · detect multiple active agreements
CYPHER 25WITH date('2026-09-09') AS asOfMATCH (p:Product)-[r:SUPPLIED_BY]->(s:Supplier)WHERE r.validFrom <= asOf AND (r.validTo IS NULL OR r.validTo > asOf)WITH p, s, collect(r.agreementId) AS activeAgreementsWHERE size(activeAgreements) > 1RETURN p.productId, s.supplierId, activeAgreements;

The database cannot infer which contract is legitimate. The safe repair is business reconciliation, not deleting whichever edge is newest. In this synthetic lab, remove only AGR-BAD-OVERLAP after proving it is the injected record:

Cypher · isolated reset of the injected conflict
CYPHER 25MATCH ()-[r:SUPPLIED_BY {agreementId:'AGR-BAD-OVERLAP'}]->()DELETE r;

Natural-key change and alias strategy

Suppose supplier registration numbers can change after corporate restructuring. If registrationNumber is the only node identity, changing it can break external references and deduplication history. AtlasMart's stable supplierId remains the node key while registration numbers become attributes, perhaps with an :ExternalIdentifier node or history structure if aliases must be searchable over time.

The production decision surface includes collision handling, source precedence, mergers/splits, tenant scoping, GDPR/privacy constraints on identifiers, and whether an identifier can be regenerated after backup/restore. Identity policy belongs in architecture documentation and tests, not only in one CREATE CONSTRAINT statement.

Lab: defend each identity and temporal choice

Cypher · identity and relationship-property inventory
CYPHER 25MATCH (n)WHERE any(k IN keys(n) WHERE k ENDS WITH 'Id')RETURN labels(n) AS labels, [k IN keys(n) WHERE k ENDS WITH 'Id' | k] AS idKeys, count(*) AS nodesORDER BY labels;CYPHER 25MATCH ()-[r]->()WHERE size(keys(r)) > 0RETURN type(r) AS relationshipType, keys(r) AS propertyKeys, count(*) AS relationshipsORDER BY relationshipType, propertyKeys;

Verification checklist: every durable entity has an application/domain ID; no application contract stores elementId(); relationship properties describe the connection; effective-dated intervals use one boundary convention; overlap rules are testable; and reified relationship nodes are introduced only when independent identity/lifecycle/multi-party traversal justifies them.

Check your understanding

  1. Why is elementId() not a replacement for supplierId?
  2. Give one fact that naturally belongs on STOCKED_AT.
  3. When does a relationship with properties deserve reification as a node?
  4. Why must validTo boundary semantics be documented?
  5. Why is an overlapping agreement not automatically repairable by “keep newest”?
Review the answers

1. supplierId is an application-controlled identity intended to survive system movement; elementId mapping is not guaranteed as a durable cross-transaction/environment key.

2. onHand, reorderPoint, observedAt, or another fact specific to that product-store pair.

3. When the connection has independent durable identity/lifecycle, many participants, references from other facts, rich history, or query needs that make it an entity.

4. Inclusive/exclusive endpoints change which relationship is current at boundary instants/dates.

5. The database does not know business authority; timestamps/order alone do not prove which contract is valid. Reconciliation must use source/business evidence.

Summary and next step

AtlasMart now separates business identity from implementation IDs, locates connection-specific facts on relationships, and treats temporal validity as explicit data with testable interval semantics. The final lesson uses all of Chapter 02 to translate relational/document source shapes into a graph and defend each modeling decision against a concrete query.

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.