Skip to content

MERGE on identity alone, then SET the rest

Problem

An upsert into a labelled graph is written the obvious way:

MERGE (d:Document:Team {path: $path})
SET d.title = $title

It fails with a uniqueness-constraint violation once the same node arrives under a different label set. MERGE matches on labels and properties together, so (:Document:Team {path: "x"}) and (:Document {path: "x"}) are different patterns — but a uniqueness constraint on path says they are the same node.

The result: the MERGE finds nothing, tries to create, and the constraint rejects it. The error names the constraint, not the labels, so it reads as a data problem rather than a query-shape problem.

Technique

Match on the identity property alone. Apply labels and metadata afterwards.

MERGE (d {path: $path})
SET d:Document, d:Team,
    d.title = $title

MERGE now matches exactly what the constraint governs, and labels become an assertion about a node you have already resolved rather than part of resolving it.

The general rule: the MERGE pattern should contain exactly the properties the uniqueness constraint covers, and nothing else. Anything extra — a label, a second property — silently narrows the match and reintroduces the bug.

When it applies

Any graph where nodes carry multiple or evolving label sets and identity is a property. Common where labels encode domain or ownership and a node can belong to more than one, or be reclassified later.

When it does NOT apply

Where the label genuinely is part of identity — where (:Draft {id: 1}) and (:Published {id: 1}) are meant to be two nodes. Then the constraint is wrong, not the query.

It also costs something: MERGE (d {path: $path}) without a label cannot use a label-scoped index, so on a large graph it may scan. Make sure the constraint's index covers the bare property.

Evidence

A documentation graph took nodes from several repositories, labelled by domain. Writes began failing on a uniqueness constraint over the identity property once the same path appeared under two domain labels.

The label-then-property form had worked for months, because until then every node had exactly one label. The bug was latent from the first write and became visible only when the data shape changed — the query was never right, it was just never asked the question that exposed it.

See also