Entity ResolutionData QualityPOV

Entity resolution: why your CRM thinks you have three customers when you have one

Every business above a certain age has the same hidden bug: the same customer appears two or three times across systems, just different enough to look like separate records. Here’s what that costs you, and the disciplined way to fix it in the warehouse layer.

TJ

Thomas Jones

Managing Director, RevenuePoint · Jan 13, 2026 · 8 min read

Three slightly different records — Acme, Inc., ACME Inc, acme inc. — converging into one resolved entity
Fig. 01 · Three slightly different records — Acme, Inc., ACME Inc, acme inc. — converging into one resolved entity

Finance pulls up ARR and sees “Acme, Inc.” at $180,000. Marketing opens the pipeline report and sees “ACME Inc” with $60,000 in open opps. Support looks at the ticket board and sees “acme inc.” with eight open cases, two of them angry. It's the same company. Nobody has any way to know that.

Every decision made from any one of those three views is wrong. Finance underestimates the account's importance. Marketing sends a cold outbound sequence to a customer who's mid-crisis in support. Renewal forecasting double-counts. None of this is a CRM hygiene problem. It's an entity resolution problem, and every business with more than one system of record has some version of it.

What entity resolution actually is

Entity resolution is the work of deciding, across records from many sources, which ones refer to the same real-world thing — a customer, an account, a person, a product — and collapsing them into one golden record that every downstream system can trust.

It's not the same as deduplication. Deduplication looks for exact matches. Entity resolution handles the messy, contradictory, partial data that real businesses actually produce: abbreviations, typos, legal-entity suffixes that come and go, addresses that formatted differently, a phone number in one system and an email in another. The whole problem exists because the records are almost the same but not quite.

Why it's not a CRM hygiene problem

It's tempting to file this under “we should clean up the CRM.” That's not where the cost lives. The cost lives in every downstream number and every outbound action.

  • Revenue reports that double-count or undercount an account depending on which spelling the query happened to catch.
  • Credit-risk views that miss an account already 90 days past due under a slightly different name.
  • Outbound sequences that email a customer in the middle of an escalated support case — because the outbound tool and the support tool are looking at different rows.
  • Renewal forecasts built on a customer count that doesn't match reality in either direction.

Cleaning up the CRM fixes one of those four places. Fixing the underlying entity-resolution problem fixes all of them at once.

Two methods, used together

There are really only two ways to match records, and every serious entity-resolution system uses both.

Deterministic matching

Hard rules on strong identifiers: same email domain, same tax ID, same customer number, same Dun & Bradstreet ID. If two records share one of those, they're the same entity with near-zero ambiguity. Deterministic matching is fast, cheap, and near-zero false positive. Its weakness is that it has nothing to say when the strong identifier is missing — which, in the real world, is most of the time.

Probabilistic matching

Statistical scoring across multiple attributes. Name similarity (using something like Jaro-Winkler or Levenshtein distance, which measure how many edits separate two strings). Address similarity. Phone number. Domain. Each attribute contributes to a combined match score, and records above a threshold get merged. Probabilistic matching handles typos, abbreviations, and word-order swaps. Its weakness is the cost of being wrong: merging two accounts that shouldn't have been merged is a much harder problem to undo than failing to merge two that should have been.

The right answer is both — in tiers

Run deterministic first. Collapse the easy matches with strong identifiers in one pass: same email domain, same tax ID, same internal customer number. That eliminates most of the volume for almost no computational cost and no real risk of false positive.

Then run probabilistic on what's left, and split the results into three tiers by confidence:

  • High confidence (auto-merge) — above, say, 0.90. Merge without review.
  • Medium confidence (human review) — between 0.70 and 0.90. Send to a review queue where a person confirms or rejects. These are cheap to review and expensive to get wrong.
  • Low confidence (no action) — below 0.70. Leave as separate entities. The signal isn't strong enough to act on.

The thresholds are not magic numbers. They're tuned over time, from the outcomes of the review queue and from how costly the false positives turn out to be for the specific business.

The same customer appearing three times isn't a CRM problem. It's a reporting problem, a renewal problem, and an outbound problem wearing a CRM disguise.
RevenuePoint design principle

Where this belongs — in the warehouse, not in every tool

You could try to solve this in the CRM. Every CRM has some form of merge-duplicates tool. If you do the work there, you've deduplicated the CRM — and exactly the CRM. The ERP still has three Acmes. So does support. So does the billing system.

The right place to do entity resolution is the warehouse. One resolution pass, run on the union of records from every source, produces a golden-record table. Every tool that reads from the warehouse — every dashboard, every agent, every report — joins to that golden record. Resolve once, benefit everywhere. And critically, every golden record keeps a lineage back to the original source rows, so you can always answer “which three records did this roll up from, and from which systems?”

One customer, three records

Here's what this looks like with a concrete example. Three source rows, from three systems, about what a human would immediately recognize as the same company:

json
// Source records

{ "system": "CRM",     "id": "0018X00002aKj9e", "name": "Acme, Inc.",   "domain": "acme.com",  "city": "Austin", "tax_id": null         }
{ "system": "ERP",     "id": "C-14821",         "name": "ACME Inc",     "domain": null,        "city": "Austin", "tax_id": "47-2081319" }
{ "system": "Support", "id": "cust_4f2a",       "name": "acme inc.",    "domain": "acme.com",  "city": "AUSTIN", "tax_id": null         }

// Match rules

rules:
  deterministic:
    - match if: same(tax_id)
    - match if: same(domain) AND domain is not null
  probabilistic:
    features:
      - name_similarity     (jaro_winkler)
      - city_match          (case_insensitive)
      - domain_match
    threshold:
      auto_merge: score >= 0.90
      review:     0.70 <= score < 0.90
      ignore:     score <  0.70

// Golden record

{
  "entity_id":       "ent_acme_austin_001",
  "display_name":    "Acme, Inc.",
  "canonical_domain":"acme.com",
  "city":            "Austin",
  "tax_id":          "47-2081319",
  "sources": [
    { "system": "CRM",     "id": "0018X00002aKj9e", "matched_via": "domain"       },
    { "system": "ERP",     "id": "C-14821",         "matched_via": "probabilistic (0.93)" },
    { "system": "Support", "id": "cust_4f2a",       "matched_via": "domain"       }
  ]
}

Deterministic rules matched the CRM and the support records via the shared domain. The ERP record had no domain, so it fell through to the probabilistic tier, scored 0.93 on name + city + indirect-domain signals, and auto-merged. The golden record inherits the tax ID from the ERP, the canonical display name from the CRM, and the lineage pointers back to all three. Every downstream join uses entity_id.

The hard cases that always show up

Any entity-resolution pipeline has to make deliberate choices about a handful of awkward situations that come up over and over:

  • Parents and subsidiaries. Is a parent company the same entity as its subsidiary? For reporting, often yes. For billing, usually no. The right answer is to model the relationship rather than resolve it away — keep them as separate entities with a parent_entity_idlink.
  • Slowly changing attributes. An address changes. The entity is still the same entity. The resolution logic has to not treat address-change as evidence against being the same entity, while still flagging enough change to catch a record that's been repurposed.
  • Same person, different companies. Contacts with the same email across two accounts aren't necessarily the same person at two jobs — they're sometimes a shared inbox. The resolution logic for contacts needs to be strictly less aggressive than for accounts.
  • Decay. Records go stale. A customer closes, a contact leaves, a domain gets repossessed. The pipeline needs a re-resolution schedule, because what was one entity last year might legitimately be two this year.
Resolution pipeline: three source records on the left flow through deterministic, probabilistic, and review tiers, collapsing into a single golden record on the right
Three source records, three matching tiers, one golden record with the lineage preserved.

How we think about it at RevenuePoint

Entity resolution isn't the flashiest part of a data stack. It's rarely what anyone wants to talk about. But it's the thing that makes every downstream number honest — the revenue report, the renewal forecast, the outbound queue, the support load by account. Do it once, in the warehouse, with deterministic rules for the easy cases, probabilistic scoring for the hard ones, a review queue for the ambiguous middle, and the lineage preserved throughout. The rest of the stack spends the rest of its life benefiting from that one boring pass.

Ready to see Foundry in your stack?

A 30-minute walkthrough, scoped to the systems you already run.