Skip to content
AI.info

ML data engineering

Joins, Cardinality, Deduplication, and Reconciliation

Control row multiplication, temporal relationships, ambiguous identity, and population changes during joins.

By the end you can

Key idea

A join should be auditable like an accounting reconciliation

SQL can multiply rows, drop unmatched entities, or pair records across the wrong effective interval and still return a syntactically valid result. At aggregate level the output looks plausible. A dependable join begins with an expected relationship: one-to-one, many-to-one, one-to-many, or many-to-many under explicit conditions. Any change in row count and any unmatched population then has to be explained, not merely observed.

A decennial census is record linkage at national scale, and the 2010 one shows how much a balanced total can hide. The Census Bureau's Coverage Measurement programme reported a net overcount of 0.01% — about 36,000 people on a base above 300 million, not statistically different from zero. Underneath that number, about 3.3% of the people counted in housing units were counted erroneously. That is some 10 million erroneous enumerations, of which “about 84.9 percent were duplicates”, set against 16.0 million omissions and roughly 6 million whole-person imputations. The Bureau put the omission side plainly in May 2012: “The Census Bureau estimated 16.0 million omissions in the census.” Ten million duplicated people and sixteen million missing ones cancelled down to one hundredth of one percent. A net figure that small is not evidence that the linkage worked. It is the arithmetic of two large errors pointing in opposite directions.

Reconciliation turns a join from hidden plumbing into a testable claim about identity and time.

Every unexplained row gained or lost is a modeling question.

Visual

Cardinality predicts the ways a join can change the example set

The expected relationship should be declared before code is written.

One-to-one means each left record matches at most one right record, so a duplicate match is an identity or version error rather than extra data. Many-to-one lets many events enrich from one entity snapshot, chosen under a valid effective time. One-to-many expands one example into detail rows and usually needs aggregation before modeling. Many-to-many holds multiple candidates on both sides, where naive joining creates a combinatorial product.

Writing the expected relationship down converts each of these into a test. If the join returns a shape the declaration did not predict, the defect sits in the identity model, not in the query. And the query will not report it. Multiplying rows is exactly what a join is for.

FigureHierarchy · 4 levels
  • One-to-one

    Each left record matches at most one right record, and duplicate matches signal identity or version errors.

    • Many-to-one

      Many events may enrich from one entity snapshot chosen under a valid effective time.

      • One-to-many

        One example expands into detail rows and usually needs aggregation before modeling.

        • Many-to-many

          Both sides contain multiple candidates, so naive joining can create a combinatorial product.

Cardinality is part of the feature definition, not merely a database optimization detail.

Example

One £8,000 transfer that Horizon recorded four times

A subpostmistress scanned a single £8,000 cash remittance from her core branch into her outreach branch. She scanned it once. In Horizon “the transfer replicated four times”, the system recorded receipts of £32,000, and the branch carried a phantom discrepancy of £24,000 — money the accounts said was missing and that had never existed.

The fault has a judgment behind it. Mr Justice Fraser handed down the sixth Bates v Post Office judgment, on the “Horizon Issues”, in the High Court on 16 December 2019. Among the defects the court examined was the “Dalmellington Bug”, which made Horizon replay the tail of a pouch-delivery script and issue duplicate pouch IDs. Fujitsu's own internal email trail on the error, disclosed to the Post Office Horizon IT Inquiry, records that the system “repeated the inward remittance transaction”.

Nothing about the duplicated rows was malformed. Each one was a valid transaction record, internally consistent, arithmetically well behaved. The total simply counted a real event four times.

  • The Post Log On script was left on the stack, so the Pouch Delivery script “thought it had not finished and attempted to repeat the last part of the script”, producing duplicate pouch IDs.
  • One £8,000 remittance became four rows: receipts of £32,000 against a real £8,000, and a £24,000 discrepancy carried by the branch.
  • The defect was not rare and not new. The judgment records: “When investigating this Fujitsu found 112 occurrences affecting 88 different branches in the previous 5 years.”
  • The impacts spanned four orders of magnitude. Mr Coyne, the claimants' IT expert, told the court in cross-examination that among them “there were some which were only pounds, just a few pounds, and I think there was at least one that was £25,000”. That is evidence recorded in the judgment, not a finding of the court.
  • A duplicate-per-key count on the pouch ID, reconciled against the £8,000 the branch actually moved, is the whole detection mechanism the system lacked for five years.

Comparison

Deduplication rules encode different beliefs about sameness

Choosing one strategy before the domain says what counts as the same event can erase valid episodes or preserve duplicates.

Some duplicates are prevented upstream rather than removed later. Kafka has offered an idempotent producer since release 0.11.0.0, and its documentation says resending “will not result in duplicate entries in the log”. The broker assigns each producer an ID and deduplicates on a sequence number sent with every message. Confluent's documentation describes the same mechanism in the same terms. Note what the guarantee covers: retries of one write, not two genuine events that look alike.

Entity reconciliation is not the informal option of last resort. It has a formal decision rule older than most of the systems that run it. Fellegi and Sunter published A Theory for Record Linkage in 1969. Each candidate pair is scored by the likelihood ratio R = P(γ|M)/P(γ|U). Two cutoffs then split every pair into three disjoint regions: a match above Tμ, a non-match below Tλ, and between them a region that decides nothing. Winkler states the middle rule as: “If Tλ ≤ R ≤ Tμ, then designate pair as a possible match and hold for clerical review.” The cutoffs are not tuned by feel. They are set from a priori error bounds on false matches and false non-matches. The size of the ambiguous class is a stated consequence of the error rates the linkage is willing to accept.

The rule is in production. The UK Ministry of Justice's open-source Splink implements the model with m and u probabilities estimated by EM, built on “FastLink's implementation in R of an Expectation-Maximisation algorithm to estimate a Fellegi-Sunter linkage model”. It has been used to produce deduplicated and linked data for the entire criminal justice system. A pipeline that returns only matched and unmatched has not avoided the third region. It has silently assigned every pair in it to one of the other two.

FigureComparison · 3 columns

Exact-byte deduplication

Removes identical records with the same serialized values.

  • Useful for replayed payloads
  • Misses semantically equal records
  • Can remove legitimate repeated events
  • Requires stable serialization

Key-based winner

Keeps one record per declared key according to a precedence rule.

  • Simple to operationalize
  • Winner rule must be temporal
  • Can hide conflicting values
  • Best when key semantics are strong

Entity reconciliation

Groups candidate records and preserves evidence about uncertainty and conflicts.

  • Supports probabilistic matching
  • Retains source provenance
  • More expensive to govern
  • Best for imperfect real-world identity

Steps

Build a join reconciliation report

Run the report for every dataset release. Compare it with the previous edition.

Step 1 and step 4 are the cheapest lines in the procedure and the most expensive to skip. On 4 October 2020 Public Health England reported that “15,841 cases between 25 September and 2 October were not included in the reported daily COVID-19 cases”. Some files containing positive test results had exceeded the maximum file size. Those rows were never transferred into daily reporting — or into contact tracing. From inside the pipeline the failure is a row count on each input that does not reconcile with the row count of the output, checked once per release.

The cost of not running that check has been estimated. Fetzer and Graeber used the gap as a natural experiment and published the result in PNAS in 2021. Each case referred late was associated with about 18.6 additional infections and 0.24 additional deaths over the following six weeks: over 125,000 additional infections and over 1,500 additional deaths in total. Every one of those records was correct where it was written. It was the transfer that lost them, and nothing downstream announced the loss.

FigureProcess · 6 steps
  1. 1. Freeze each input

    Record version, row count, key uniqueness, and effective-time range.

  2. 2. Classify matches

    Count matched, left-only, right-only, duplicate, and ambiguous cases.

  3. 3. Measure multiplication

    Report the distribution of right-side matches per left example.

  4. 4. Reconcile quantities

    Check totals such as money, events, duration, or inventory before and after the join.

  5. 5. Inspect slices

    Review changes by source, tenant, region, device type, and time period.

  6. 6. Approve exceptions

    Document why each material mismatch is expected or block the release.

Analogy

Passport control between two record systems

One table is a passenger manifest, the other passport records. Matching requires more than similar names; identity, validity interval, issuer, and document status matter. Ambiguous cases should be routed for review rather than forced into one identity. A strong process preserves the evidence used for the match and the alternatives rejected. A join can also intentionally represent a one-to-many relationship. A model may need every shipment or device associated with an account, a case passport control never has to handle.

Health care has measured what the analogy describes, and the number moves sharply the moment the join crosses an organisational boundary. Kaiser Permanente runs 17 instances of Epic across its regions. The Patient Identification and Matching Final Report, commissioned by the Office of the National Coordinator for Health Information Technology and delivered in 2014, reports: “For example, Kaiser Permanente (which has 17 instances of Epic across its regions) reported a match rate of greater than 90 percent within each instance; that rate fell to around 50 percent to 60 percent when sharing between regions using a separate instance of Epic or with outside Epic partners.”

The finding held up under independent audit. The Government Accountability Office recorded in 2019 that “A 2014 study found that as few as 50 percent of records are accurately matched when organizations exchange information.”, and that 45% of large hospitals said difficulty with patient identification limited health information exchange. Same people, same names, same software vendor. One boundary, and roughly four in ten matches disappear.

A match is a defensible relationship, not simply an equal string.

Relationships also have effective time

An account can change household, a merchant can change category, and a sensor can move between assets. Joining every event to the latest relationship rewrites history.

Model the effective interval of the relationship. Then decide whether the decision should use the state at event time, scoring time, or another declared cutoff.

This is not a bespoke pattern each team has to invent. SQL:2011 added application-time period tables, in which the user sets the validity interval of each row, and system-versioned tables, in which the system maintains it. It also added period-qualified referential integrity. A child row can be forbidden unless its application-time period is contained in the period of a matching parent row, or in the union of contiguous matching parent rows. That constraint is the timeless foreign key rejected at the engine rather than caught in review. Kulkarni and Michels documented the features in 2012, and MariaDB, DB2 and SQL Server implement them. MariaDB's own documentation states the origin: “System-versioned tables were first introduced in the SQL:2011 standard.”

For slowly changing dimensions, test boundary conditions around start and end times. Overlapping intervals and gaps should be explicit errors or documented fallbacks.

A compacted log makes the same trade explicitly. Kafka's design documentation says compaction “ensures that Kafka will always retain at least the last known value for each message key within the log of data for a single topic partition”. HPE's Data Fabric documentation describes the behaviour the same way. That is a current-state store, useful for reloading caches after a restart. It is not a source for reconstructing what a key held last March.

A timeless foreign key can still produce temporal leakage.

Publish join evidence with the dataset

Reconciliation metrics, ambiguity counts, deduplication policy, and examples of unresolved cases should travel with a dataset release. These artifacts explain why the population changed between versions. When a model shift appears, the team can compare join diagnostics before retraining or tuning. That shortens root-cause analysis and prevents the learner from being blamed for an identity defect. The report also supports governance: it reveals whose records are disproportionately unmatched, multiplied, or discarded.

The 2020 Census shows what that disclosure is worth. The Census Bureau's Post-Enumeration Survey, released on 10 March 2022, found no statistically significant net coverage error for the total population. Underneath that total: “The Hispanic or Latino population had a statistically significant undercount rate of 4.99%.” The Black or African American alone-or-in-combination population was undercounted by 3.30%, and American Indian or Alaska Native people living on reservations by 5.64%. The non-Hispanic White alone population was overcounted by 1.64%, and the Asian alone-or-in-combination population by 2.62%. A Government Accountability Office audit in 2024 confirmed the pattern held across two censuses: “in 2020 and 2010, Black or African American and Hispanic persons, young children, and renters were undercounted, while non-Hispanic White persons, adults over 50, and homeowners were overcounted”.

A release that published only the national figure would have published a defensible number and concealed every one of those. Sliced evidence is what makes that difference visible to someone who did not run the join.

Join quality is observable when its uncertainty and population effects travel with the release.

Key takeaways