Playbook

Entity resolution for data analysts

For the team that owns a company master and has to defend it.

Your company rows have no shared key: the vendor master holds five rows for a company the CRM holds two rows for, nothing in either table says so, and every aggregate over them is wrong by an amount nobody can quantify. The work is seven steps and the matching is the third: profile the columns, normalise without manufacturing merges, resolve the rows against a register, work the exceptions by cause, pick a surviving value per field, ship a crosswalk and an exceptions table rather than an edit to the source system, then report the four numbers that describe what happened. Most of the damage is done at step two, where a normalised name becomes a join key and two different companies quietly become one row in a report. Here is the sequence, the check at each step, and a worked example in which the true duplicate pair scores half what the false one does.

A stewardship queue, a CRM after a migration or a merger, a supplier file before a payment run, counterparty onboarding that has to survive an audit. Read the argument here, take the takeaways at the foot into your next vendor conversation, and send us a file when you want the same measurement on your own rows.

Playbooks · 5,220 words · every figure carries the population it was measured over

Reading time
25 min read
Sections
13
Questions answered
9
Key takeaways
9
the same measurement, on your rows
A first file is a few hundred of your own rows with the matches you already have beside them. The numbers in this article come back measured on your data, and we write nothing to your systems.
Start free →
Self-serviceCreate your workspace online and verify your email to start matching company records. Free starts with 300 credits and no card. Paid monthly plans are available in workspace Billing; VAT is added where applicable. Usage is tracked without automatic overage charges. Trust and security
On this page

What does a data analyst do with company records

A data analyst turns rows into a number somebody will act on: pull the data, work out what each row refers to, choose the grain, aggregate, then explain the result when it is questioned. On a company table the second of those decides whether the rest was worth doing. Spend by supplier, customers by industry, exposure by counterparty: each one is a group-by over a company, and the rows do not carry a company. They carry a name somebody typed, sometimes an identifier column, sometimes a postcode. Working out which rows are one company is entity resolution, and it stands between the extract and every number you publish off it.

Settle it once and the rest of the week is ordinary work. One canonical company record per company, your own keys mapped to it, and the aggregates come out in a grain you can name: four companies rather than eight spellings, one supplier line rather than five, a parent held apart from its subsidiary because a register says they are two companies. The same table answers the question you were asked and the one that arrives a week later, which is why these eight rows became four.

Five jobs land on an analyst who owns company rows, and the second one decides what the other four are worth:

  • Pull the extract. A CRM export, a vendor master, a billing table, a signup table. Each system spells the company differently, and none of them share a key with the others.
  • Resolve the rows. Which rows are one company is a fact about the world, settled against a register and citable, rather than a score two strings got from each other.
  • Choose the grain. One row per legal entity, per corporate group, per site and per account are four different numbers, and only one of them answers what was asked.
  • Aggregate. The arithmetic is trivial once the grouping is right and worthless when it is not, and a wrong grouping does not look broken on the way past.
  • Explain it later. The question that arrives months afterwards is which rows became one line and why. A decision id reopens the evidence at a URL; a similarity score cannot.

What a canonical company record has to carry

All five jobs want the same thing underneath them: one row per company, carrying the two identifiers that decide a match on their own (an agreeing LEI, and a registration number qualified by the register that issued it), one surviving value per field with the source and date behind it, and a link back to every source row that landed on it. No table in a warehouse holds that, and no amount of cleaning turns one into it. It is assembled by resolving the rows against a register, keeping the refusals as rows of their own, and publishing a crosswalk your marts join to.

Two parts of the work are not identity work, and no resolver supplies them. Whether a parent and its subsidiary are one line in your report is your organisation's policy, written down by whoever asked for the number. And the identity-shaped part is narrower than the project around it: what else stays your job, including the scope limit on people, is set out at the foot of this article.

What you are shipping, and what you are not

The request arrives as a number (spend by supplier, customers by industry, accounts per region) and the rows that refer to one company do not share a key, so a group-by over the name column counts spellings rather than companies. The report will not look broken. It will be wrong by an amount nothing inside the data can quantify, which is worse than a join that visibly fails.

Three questions sit inside that and only two have answers outside your organisation. Which rows refer to one company is a fact about the world, settled against a register and citable; which registered company each group is, is the same kind of fact. Whether two genuinely separate legal entities (a parent and its subsidiary, two franchises of one brand) should be one line in your report is your organisation's policy, and no resolver supplies it. Get that rule written down in a sentence by whoever asked for the number, because it decides whether a result is right.

Then ship two tables rather than a change to the source system. A crosswalk: one row per source row, carrying the system, its own key, the entity it resolved to, the confidence and the decision id. An exceptions table: the rows nothing could decide, each with a typed cause and the field that would settle it. Marts join the crosswalk, nobody edits the system of record, and a grouping that turns out to be wrong is one mapping row to change rather than a customer to reconstruct.

The sequence, with what each step hands to the next:

  • Profile. Four numbers per candidate column: how many rows carry it, how many distinct values it holds, how many of those repeat, and how many fail the shape their scheme requires.
  • Normalise. Case, punctuation, legal suffixes and stray whitespace reduced to a comparable form, with the raw value kept in the column beside it.
  • Resolve. The rows go out carrying your own key and come back as a decision, an abstention with a ranked shortlist, or a new entity, each with a receipt that reopens at a URL.
  • Adjudicate the exceptions. Work them by typed cause, because the four causes send work to four different places and only one of them is worth retrying.
  • Survive. One value per field, computed in a fixed order, with the losing values kept beside the winners rather than discarded.
  • Ship. A crosswalk keyed on your source rows and an exceptions table, loaded by you, joined by your marts.
  • Report. The decided share by tier, the exceptions by cause, the collapse ratio per source system, and a reconciliation that balances.

Step one: profile the columns before you write any matching logic

The shape of your columns sets the ceiling on everything after it, so measure the shape first. Four numbers for every column that might identify a company, and all four are a morning's work in SQL: fill rate, distinct values, how many values appear on more than one row, and how many fail the shape their scheme requires.

The fourth is the one that gets skipped and the one that finds most. A column named company_id in a warehouse that has survived two migrations holds a mix: company numbers, VAT numbers, the internal key of a retired procurement system, and free text somebody typed into the wrong box. Each scheme has a shape: a Companies House number is eight characters behind an optional letter prefix, a UK VAT number is nine digits behind a country code, a legal entity identifier is twenty characters carrying an offline check digit. A case expression over those patterns sorts the column in an afternoon and tells you how much of it is usable.

Then value each column by what it can decide rather than by how full it is: a column populated on a tenth of the rows settles a tenth of the file at the cheapest stage there is. The rates below are per input shape, which is the unit that matters here: your file's rate is the weighted average of its own column mix, so the table says what to go and find.

Resolution rate by input shape, on the live-adjudicated 317-company fixture of 2026-08-29
The columns you haveRateFractionWhat it means for the plan
A company number or an LEI100%317/317Decided deterministically, one credit, no model call and no egress, whatever the name column says.
A company name and its domain100%197/197The strongest column most warehouse tables actually carry. Worth a join to a web or email table to populate it.
A company name and a registered postcode98.74%313/317The column to find for a file from a register where names collide. Billing and registered addresses both work.
A company name and nothing else but the country98.42%312/317Stronger than it looks on a GB file, and the first row where the register rather than the engine sets the rate.
A name whose ending has been cut off by a field-length limit58.10%61/105The weakest measured shape. If your extract truncates, fix the extract rather than the matcher.

Historical results on an internal UK fixture with a preloaded catalogue. The September 5 replay has no full-prompt-verified calls and cannot establish current calibration. No independent customer holdout or global coverage claim. The name-and-website row is a separate measurement: Measured separately, at zero model spend, on 197 individually verified company-to-domain pairs. Those companies are large and well known, so this is the tier's behaviour on famous names rather than a register-wide sample. 44 of 105 come back as an abstention with a shortlist, which is the correct answer for a name whose missing tokens were the discriminating ones.

Step two: normalise for comparison, never for identity

Normalising is cleaning text so that two strings meaning the same thing can be compared: case folded, punctuation dropped, legal suffixes reduced, accents transliterated, whitespace collapsed. Do it, keep the raw value in the column beside it so a result can be explained later, and do not promote the output to a key.

The anti-pattern is specific and it is in most warehouses: lower(regexp_replace(name, '[^a-z0-9]', '')), grouped, and treated as a company id. It manufactures merges. A Limited and an LLP sharing a trading name collapse into one group, and so do an operating company and the holding company above it, because the distinguishing word was a legal form and the legal form is what was stripped. The key looks deterministic, so nobody audits it. A normalised string is grounds for comparing two rows; it is not a statement that they are one company.

Three normalisations have to know which scheme they are cleaning. Stripping punctuation from an identifier column turns a VAT number into a digit string that can collide with a company number. Loading that column as an integer drops a leading zero and breaks the register's own shape rule, which is the rule that would have caught the collision. Truncating a name at a field limit removes the tokens that discriminate between group subsidiaries, because the discriminating token is usually at the end.

What the register supplies and no cleaning can is a former name. Companies House and several other registers publish previous names with validity dates, held as facts with their own provenance, so a row raised under the old name and one raised under the new resolve to one company. No normalisation finds that pair, because the two strings genuinely are not similar.

A worked example: eight rows, four companies, one refusal

The rows below are invented and the codes in them are not issued to anybody; the shapes are real and so is the arithmetic. Two systems contribute eight rows, the request is spend by company for the quarter, and the single identifier column holds three different schemes.

What comes back for each row, and what decided it:

  • ap_0117 is decided by its company number. That is the identifier tier, 100% (317/317) on the fixture above, and it costs one credit with no model call and no egress, whatever the name column says.
  • ap_0349 resolves to the same company on name and website. ap_1188 resolves on name and postcode, after its identifier is rejected as a company number: it is a VAT number, and the shape rule catches that before it can be matched as one.
  • crm_204 carries a company number that disagrees with ap_0117's, so the merge is blocked outright. One disagreeing strong identifier is enough, and no amount of name similarity overrides it. It is the parent, and whether your report rolls it up is the policy sentence from step zero rather than a matching question.
  • ap_2006 resolves to a third company on name and postcode. This is the row that needed a register: nothing in the eight rows says the Northern operation is a separate legal person, and the register says it is, with its own company number.
  • ap_2451 is decided by its legal entity identifier. crm_559 joins it on name and website, so the missing letter never has to be adjudicated at all: the domain agrees and the cheapest stage that can decide does.
  • ap_3302 abstains. A name cut off at a field limit, no domain, no postcode and no identifier leaves a partial string as the strongest signal, so what comes back is a ranked shortlist and the field that would settle it. Truncated names are the weakest measured shape at 58.10% (61/105), and the fix is the extract.
Eight invented source rows as they arrive: two systems, one identifier column, three schemes in it
Source rownamecompany_iddomainpostcode
ap_0117Halverstone Logistics Ltd04812993halverstone.co.ukM1 4WB
ap_0349Halverstone Logisticsn/ahalverstone.co.ukn/a
ap_1188HALVERSTONE LOGISTICS LIMITEDGB418299377n/aM1 4WB
crm_204Halverstone Group09344120halverstone.co.ukEC2A 4NE
ap_2006Halverstone Logistics (North)n/an/aNE1 7RU
ap_2451Brightmoor Analytics Ltda 20-character LEIbrightmoor.ion/a
crm_559Brightmoor Analyticn/abrightmoor.ion/a
ap_3302Brightmoor Analytics Limn/an/an/a

Invented rows: the two company numbers and the VAT number above are shaped like the real schemes and are not issued codes. ap_1188 holds a VAT number in the identifier column, ap_2451 holds a legal entity identifier in the same column, and ap_3302's name has been truncated at 24 characters by a middleware field limit.

Why no single similarity threshold gets this file right

Normalisation does the easy part of the example and then hands you the hard part with nothing to separate it. Case folding and suffix reduction take ap_0117, ap_0349 and ap_1188 to the same string, halverstone logistics, and those three are one company. The two rows that matter, halverstone group and halverstone logistics north, survive the same cleaning, share the distinctive token, and are two other companies.

Now rank the pairs by similarity and the ordering inverts. On token-set overlap, the share of distinct words two names have in common, halverstone logistics against halverstone logistics north scores two of three, about 0.67, because the second name contains every word of the first. brightmoor analytics against brightmoor analytic scores one of three, about 0.33, because a singular and a plural are two different tokens. Those two figures are arithmetic on the rows above and are illustrative rather than measured: what they show is that the true duplicate pair here scores half what the false one does.

A threshold at 0.5 therefore makes both errors at once: it welds two different companies into one line of the report and leaves a genuine duplicate pair split across two. Moving the threshold changes which error you get rather than removing either, and swapping the measure moves the problem too: character trigrams rate brightmoor analytic and brightmoor analytics very close, which fixes that pair, and still rate halverstone logistics against halverstone logistics north above most of the true pairs in a real file, because one string contains the other.

What separates them is not in the file: the postcode on ap_2006 sits in a different city from the one on ap_0117, the company number on crm_204 disagrees with ap_0117's, and a register holds three live companies under names no string comparison can order. So the eight rows are four companies and one row nobody can count yet. A dashboard reporting eight suppliers was wrong; one reporting four and naming the eighth as pending is right; one reporting three because a threshold welded two companies together is wrong in the way nobody notices.

Step three: run it, and read the refusals before the matches

From a warehouse this is a batch over the HTTP API rather than a connector, and three properties of it belong in the job design. A request carries up to 500 records. Your own key rides along as a client reference and comes back verbatim on every row, so the result set joins to your table on your identifier rather than on ours. And batches run one at a time rather than being fanned out through a queue, so a three-hundred-thousand-row table is an overnight window to schedule rather than a throughput figure to assume.

Three result sets come back and the second two are the ones to model properly. Resolved rows carry an entity id, a confidence and a decision id. Abstentions carry a typed cause, a ranked shortlist and the column that would have decided. A new entity minted because nothing matched is a third thing again: it is not a match, the number on it is not a calibrated match confidence, and it must not be folded into an accepted-match count or counted as coverage.

Work the abstentions before you open a single match, because they describe your data rather than the matcher's opinion of it. A match below 0.90 confidence is unrepresentable. The record comes back as an abstention with a ranked shortlist instead. It is an acceptance contract, not calibration evidence: it says what we refuse to return, not how often an accepted match is right. Read it with the coverage it costs.

There is one temptation specific to this job. An aggregate has to sum, every row has to land somewhere, and the top row of each shortlist starts to look like a reasonable default. It is not a match and cannot be used as one. The correct company ranked first 74% (151/205) of the time and appeared in the top three 100% (205/205) of the time, on 205 name-only records on the gated acquisition fixture. Those figures come from the gated acquisition fixture. The stub-adjudicated run behind the pairwise gate, not the 317-company live recording behind the tier table. Recall is a property of the list, not of the winner. 74% is far below the 0.90 acceptance floor, so no row of that shortlist may be offered as a match, quoted as an accuracy number, or folded into an accepted-match count.

The other filter to resist is a confidence cut applied to the mart because it reads as prudence. Confidence bounds depend on the recorded evidence population; there is no universal score for every match. Read match_confidence_basis and the evidence. A 0.99 filter can return some v8 adjudicated matches while excluding valid identifier matches, so validate its coverage cost on your own inputs.

The four typed causes, and where each one sends the work. Only the last is worth a retry:

  • more_input_needed: the paid stages could not separate the shortlist. Running it again at greater depth will not change the answer, so this is a request to whoever owns the source system for a column, not a rerun.
  • no_identifying_signal: the fields normalised to nothing identifying. No entity was minted, nothing was queued and nothing was billed. This one is usually your own extract: a name column holding a punctuation-only string, or the wrong column mapped to the name field.
  • depth_capped: the job pinned a depth and the cascade stopped before it finished. A deeper run may decide it, which makes this a budget decision rather than a data one.
  • escalation_unavailable: a dependency failed. Retrying is the right response here, and it is the only one of the four where it is.

Step four: survivorship is one value per field, computed rather than stored

Three rows resolved to one company still disagree about that company's name, address and website, and something has to decide what the report prints. That is survivorship: a separate step from matching, with its own failure modes, and in a warehouse it belongs in a view over the facts rather than in a table somebody can edit. A golden record that can be edited directly stops being derivable from its inputs, and nobody can then answer why a field says what it says.

Run it per field, in a fixed order. Source precedence first, so a statutory register outranks a scrape on the facts it is the register for. Precedence is per field, because the register of incorporation is not authoritative about a company's website. Recency second within a precedence level, on when a source last confirmed the value rather than on when you first loaded it. Provenance confidence as the tie-break, and it has to be the confidence of the fact rather than of the match that brought the fact in.

The exception is the one that bites hardest in a warehouse, because your own loaded rows are most of what you hold. Their timestamps record when the file arrived rather than when anything was true: a table ingested this morning can describe a company as it stood in an export eighteen months old. Skip the recency step for caller-submitted facts, or every refresh promotes the oldest data in the pipeline to the freshest thing in it.

Keep the losing value with its source rather than deleting it. Two systems disagreeing about a registered address is frequently the most useful row in the output: it is a data quality finding with an owner attached, and a view that emits one flattened value per field has thrown the finding away to produce a tidier table.

Step five: ship a crosswalk that reproduces

Here your requirements diverge from a data steward's. A steward wants the best answer today. You want the same answer twice, because a figure that moves between two refreshes with no input change is a figure you cannot defend in the meeting where somebody noticed.

Batching is a decision that changes the result. Inside one batch the row order makes no difference: every record is proposed against the store as it stood when the batch began, and the links found within the batch are folded together afterwards, so anything connected to anything else lands in one group and four orderings of the same rows give one grouping. Across batches the chunking does change the grouping, because each batch is proposed against a store that already holds the previous one: smaller batches collapse more duplicates, one large batch buys order-independence. A nightly load and a one-off rebuild therefore want opposite choices, so the chunking belongs in the job definition rather than in a loader default.

A re-run of an unchanged row costs nothing, which is what makes a scheduled refresh affordable. The price rule is five lines and they travel together. 1 credit: a decision reached by the deterministic or probabilistic stage. CPU only, no egress. 5 credits: a decision that needed LLM adjudication or agentic research. 0: an abstention, at any stage, including the ones that spent the most getting there. 0: a byte-identical replay. Per tenant, per exact content, and with no expiry. 0: an input that normalised to no identifying signal. There was nothing to answer. So the cost of a refresh follows how many rows changed and which stage each new row stops at, not the size of the table.

Carry the decision id into the crosswalk as a column. Every decision, refusals included, persists its feature vector, the top weighted contributions, the thresholds it was judged under, the cited evidence with source URLs, the adjudicating model and prompt version where one ran, and a re-run of the score from the stored features, and it reopens at a URL. The question that arrives six months later is rarely whether you can reproduce the grouping; it is whether you can explain one row of it, and a similarity score cannot.

Nothing is written to your warehouse, your CRM or your source systems, and automatic writeback is off the roadmap rather than on its way. Loading the result sets is your job, which puts the irreversible step in the system that owns the record, under your own change control.

Step six: the four numbers to report, and the one not to quote

The metric everybody asks for is the number of duplicates found, and publishing it rewards whichever run merged hardest. A run that welded two large customers into one account scores beautifully on it.

Report these four instead, each with its denominator attached:

  • The share of rows decided, split by the tier that decided them. An identifier-decided row and an adjudicated row are both decided, and they are not equally cheap or equally defensible. For a sense of the split on a labelled population, 63.10% of pairs were decided automatically on the continuous-integration gate of 2026-09-05. 742 of 1,176 pairs were decided automatically in the September 5 saved gate report. This measures automation, separately from accepted-merge precision.
  • The exceptions, grouped by typed cause. That is your data quality backlog with an owner per group, and it is the one figure in the pack that converts directly into work somebody else can do this quarter.
  • The collapse ratio: rows in, companies out, per source system. One system contributing four rows per company while another contributes one is a finding about the first system, and it is the finding that gets the extract fixed.
  • The reconciliation. Every input row should land in exactly one ledger and the distinct source references should equal the rows in. A run that does not reconcile is a run to repeat rather than a pack to publish.

The ceiling your SQL cannot move

Two properties of the registers your rows come from set a ceiling no tuning reaches, and knowing them in advance turns the exceptions table from a surprise into a forecast. The first is name uniqueness. Companies House holds 5,464,811 live companies, of which 99.53% carry a name no other live company shares; SIRENE holds 5,841,117 legal persons and the figure is 67.96%. A third of French companies share their exact name — 1,003 live companies sit on one name. A French file needs a town or postcode column, and with one it measured 2,998 of 3,000 on the postcode tier. So a French row carrying only a name returns a shortlist far more often than a British one, and the remedy is the column that note names.

The second is supply. The registers with fixtures behind them are the United Kingdom, the United States through Colorado and through SEC filers, France and Norway; adapters for Finland, Czechia and Poland are wired with no evaluation fixtures, so they carry no measured quality figure; and Germany is an absence rather than thin coverage, because there is no lawful bulk source. Combined parity against the UK baseline measured 0.999955 against a 0.9565 bar over United Kingdom, United States (Colorado) and EMEA (France and Norway). Measured with no model spend at all, on identifier and digital tiers plus retrieval and fragmentation, using the same seeded, sibling-complete builder in every register. It does not carry the live-adjudicated name tiers across, which rest on jurisdiction-free calibration keys and have been recorded in the UK only.

One rule follows for your own reporting. Quote the population with any resolution rate, including ours, because a blended rate is set by the mix of input shapes in the file it was measured on rather than by the engine. A historical 2026-08-29 blend across six input shapes in a preloaded UK catalogue. Its value depends on that mix: truncated names measured 58.10% while the blend measured 95.75%. Neither establishes the current minimum-input target.

What stays your job

The identity-shaped part of this work is the narrow part. The policy sentence about parents and subsidiaries, the grain of the report, the change control in the source systems, the loading, the scheduling, and the conversation in which somebody who knows the business agrees that the four companies in the crosswalk are the four the business recognises: all of that is the project, and none of it is a resolve call.

Scope is company and organisation identity. A contacts table, an employee table or a shared mailbox is outside it, people are never resolved, and no result may be used for an eligibility decision about an individual.

And the figures above come from internal fixtures. No independent external validation of the accuracy figures. Every fixture is one we built. No production customer holdout. The evidence is internal. The number that describes your file is the one measured on your own rows: a few hundred of them, with the matches you already have beside them.

Key takeaways

What to take away

  • A data analyst on company rows does five things, and the second is where the file runs out of information: pull the extract, decide which rows are one company, choose the grain, aggregate, then explain the grouping months later. One canonical company record per company is what unblocks the other four.
  • Ship a crosswalk and an exceptions table rather than an edit to the source system. The marts join to the crosswalk, so a wrong grouping is one row to change instead of a customer record to reconstruct.
  • Profile the identifier column by shape before anything else. A company_id column in a migrated warehouse holds company numbers, VAT numbers and retired internal keys, and a shape test sorts them in an afternoon.
  • Normalise for comparison and never promote the normalised name to a key. A Limited and an LLP sharing a trading name collapse into one group, and the key looks deterministic enough that nobody audits it.
  • No single similarity threshold gets a real file right: in the worked example the true duplicate pair scores about 0.33 on token overlap while two different companies score about 0.67. Moving the threshold changes which error you make.
  • Read the abstentions before the matches, and never promote the top row of a shortlist to a match: the correct company ranked first 74% (151/205) of the time, far below the 0.90 acceptance floor, on 205 name-only records on the gated acquisition fixture.
  • Survivorship is a view computed per field, in the order precedence, recency, confidence. Recency is skipped for your own loaded rows, whose timestamps record the load rather than the truth.
  • Batching changes the grouping across batches and not within one, so the chunking belongs in the job definition. An unchanged row replays for nothing, which is what makes a scheduled refresh affordable.
  • Report the decided share by tier, the exceptions by cause, the collapse ratio per system and the reconciliation. A count of duplicates found rewards whichever run merged hardest.

Those takeaways hold for anybody's data. The number for yours comes out of your own rows — a few hundred of them, with the matches you already have beside them.

Start free
Still not answered?it will name the article that covers it, or say where the field has no settled answer
Ask whatever this article left open. If the answer is a number it comes with the population it was measured over, and if it belongs to another article or a glossary entry I will name that one.

Prefer to put it to a person? Talk to us instead →

Is this your file?

The article is for anyone. The measurement behind it is worth commissioning for one kind of team.

built for

Teams with a company master they have to defend — an MDM or stewardship queue, a CRM after a migration or a merger, a supplier file before a payment run, or counterparty onboarding that has to survive an audit.

wrong for

Buying breadth as the product: we hold a fraction of an incumbent’s records and are not building a global company graph. Entity Bind resolves companies and organisations only. It does not resolve people, vessels, bank-account ownership, sanctions exposure, emissions or geolocation, and its results may not be used for eligibility decisions about individuals, including employment, credit and tenancy.

Sources

The papers, regulators and registers this article quotes, each with the caveat that travels with it.

  • Companies House register search

    The register the United Kingdom figures were measured on. A name-uniqueness share is a property of the register, not of any resolver.

  • Annuaire des Entreprises

    The keyless government search route to the SIRENE universe. What it cannot fix is that a third of French companies share their exact name.

  • GLEIF, “The Legal Entity Identifier (LEI)”

    The operator of the system, describing the twenty-character code and the four ISO 17442 principles it adheres to. It is GLEIF's account of the standard rather than the standard's own text.

the basis for every figure above

Internal fixtures only. The historical 2026-08-29 per-tier recording covers 317 GB companies with identifier-rich arrivals seeding the catalogue first; the context tier includes country and registered-town prose. Its legacy replay has no full-prompt-verified calls and cannot certify current calibration. The September 5 CI reports cover 1,176 labelled pairs and 705 records over 250 companies using offline stubs. These are not independent customer holdouts or live production validation; the 99% minimum-input product target remains unmet. Scope is company and organisation identity only — not people, vessels, bank-account ownership, sanctions or emissions. Results may not be used for eligibility decisions about individuals, including employment, credit and tenancy.

Figures read off the product repository on 2026-09-05. The per-tier rates come from the live-adjudicated 317-company recording of 2026-08-29; the gate figures come from the stub-adjudicated continuous-integration run and are a floor rather than a ceiling. The two are never averaged. Entity Bind holds no SOC 2, no ISO 27001 and no other certification, and claims none. Every figure, with the population it was measured over and the method behind both recordings, is set out on the accuracy page. The retention, erasure, tenancy and sub-processor detail a security review asks for is under Trust and security, with where assurance stands: no external validation of the accuracy figures and no customer holdout.

the ask

The same measurement, on your own rows.

Create your workspace, verify your email and run your first company file. The Free plan needs no card. Create API keys in your workspace when you need an integration.

300 credits. No card.

For a guided review of your file, explore the Merge Safety Sprint →

The questions this article answers

What does a data analyst do with company data?

Five things, and the second is the one the warehouse cannot settle on its own: pull the extract, work out which rows are one company, choose the grain the report is published at, aggregate, and explain the grouping when somebody questions a line in it. Company rows arrive carrying a name somebody typed and, if you are lucky, an identifier column or a postcode, so which rows are one company has to be settled against a register rather than against a similarity score. The deliverable is a crosswalk and an exceptions table rather than an edit to the source system, and the four numbers worth reporting are the decided share by tier, the exceptions by typed cause, the collapse ratio of rows in to companies out per source system, and whether the run reconciled.

Can I do this with fuzzy matching in SQL?

For the easy rows, yes, and it is the right first step: a normalised name takes you to the groups where the strings really are the same. The rows that decide whether the report is right are the ones where two different companies have nearly identical names (a parent and its operating subsidiary, a regional entity with a bracketed suffix), and no threshold separates those from a typo pair, because the typo pair frequently scores lower. In the worked example above a true duplicate scores about 0.33 on token overlap while two different companies score about 0.67. What separates them is evidence from outside the file: a disagreeing company number, a postcode in another city, a register's record of a former name.

What should the mart join on?

Your own source key, through a crosswalk table: one row per source row, carrying the system, the source key, the resolved entity id, the confidence and the decision id. Never join a mart on a normalised name, and never let the resolved entity id into a fact table without the crosswalk in between: the indirection is what lets you correct a grouping by updating a mapping row rather than rewriting history. Your key travels through the pipeline as a client reference and comes back verbatim on every row, so the result set joins to your table on your identifier rather than on ours.

Will the same rows group the same way on the next run?

Within one batch, yes: every record is proposed against the store as it stood when the batch began and the links are folded together afterwards, so the row order cannot change the grouping. Across batches the chunking can change it, because each batch is proposed against a store that already holds the previous one: smaller batches collapse more duplicates, one large batch buys order-independence. Pin the chunking in the job definition and keep it stable between refreshes, and carry the decision id so that a grouping which does change can be explained rather than argued about.

Can I use the top row of a shortlist so that every row is counted?

No, and this is the one place where an analyst's need for a complete aggregate pushes hardest against what the evidence supports. A shortlist is a ranked list of candidates, and recall is a property of the list rather than of its first row: the correct company ranked first 74% (151/205) of the time and appeared in the top three 100% (205/205) of the time, on 205 name-only records on the gated acquisition fixture. Recall is a property of the list, not of the winner. 74% is far below the 0.90 acceptance floor, so no row of that shortlist may be offered as a match, quoted as an accuracy number, or folded into an accepted-match count. Report those rows as a named pending bucket with their row count instead; an aggregate with a stated hole in it is usable, and one silently filled with top guesses is not.

Should I filter the mart on a high confidence score?

Be stricter than the floor if you have measured what it costs you, and know that you cannot be looser: 0.90 is built into the resolution outcome as a domain invariant and checked again on the online, batch and replay paths, so a sub-floor match cannot be constructed anywhere. Confidence bounds depend on the recorded evidence population; there is no universal score for every match. Read match_confidence_basis and the evidence. A 0.99 filter can return some v8 adjudicated matches while excluding valid identifier matches, so validate its coverage cost on your own inputs.

Does anything get written back to the warehouse?

No. There is no writeback connector and none is planned: what comes back is three result sets (resolved rows, abstentions with their typed cause and shortlist, and newly minted entities), and loading them is your job. For a warehouse that is usually the right division of labour anyway, since the load is where your tests, your change control and your lineage already live. It also means the irreversible step happens in the system that owns the record, under your own process.

What is the smallest useful first run?

A few thousand rows of the real master, not a curated sample. A curated sample proves the easy rows, and the interesting rows are the ugly ones. Have us run the free preview over the whole file first — it calls no model and no registry, spends nothing, and tells you which of your populated columns are unusable and why — then run a slice through the cascade and check the results by hand against the register pages cited on each decision.

How do I report accuracy for this pipeline to whoever asked?

Four numbers, each with its denominator: the share of rows decided and by which tier, the exceptions grouped by typed cause, the collapse ratio of rows in to companies out per source system, and whether the run reconciled (every input row in exactly one ledger, distinct source references equal to rows in). Do not report a count of duplicates found, which rewards aggressive merging, and do not carry a vendor's blended resolution rate into your own pack. A historical 2026-08-29 blend across six input shapes in a preloaded UK catalogue. Its value depends on that mix: truncated names measured 58.10% while the blend measured 95.75%. Neither establishes the current minimum-input target.