Rocon·Software Review

customer · 2026-11-03 · 8 min

Choosing Which Customer Record Survives a Platform Migration

Duplicate customer records after a migration, and which one to keep

By the Rocon editors · last checked 2026-11-03

You are staring at two records for someone named Sarah Miller. The first record was created four years ago; it holds nineteen completed orders, an expired credit card profile, and a physical address in Denver. The second record was created six weeks ago during guest checkout; it holds one order, an active delivery address in Seattle, and the password hash she actually remembers.

You have to delete or archive one of them before your fulfillment software and marketing sync tools start throwing foreign-key exceptions.

If you keep the older record, your warehouse might send her next order to Colorado, or her login might fail because the legacy platform’s hashing salt did not survive the import. If you keep the newer record, her order history disappears from her customer portal, and your support staff will spend twenty minutes reattaching invoices when she calls to ask about a warranty claim.

This is the survivorship problem. When you move an online store with a small staff, deduplication is rarely handled cleanly by automated platform importers. We have watched automated scripts make arbitrary decisions—like blindly keeping whichever row had the highest integer ID—and spend the next three months untangling the side effects.

The Survivor Rule: Old Identity vs. Active State

Every deduplication script requires a survivorship rule. You either nominate the legacy record as the “canonical” master and patch newer data into it, or you treat the newest record as canonical and backfill the legacy transaction history.

In our experience, teams get into trouble when they try to blend both strategies dynamically across different customer segments. We strongly suggest picking one default direction across your entire database and handling exceptions manually.

The Case for Preserving the Legacy ID

The primary argument for keeping the older profile is transactional continuity. Accounting engines, sales tax remittance logs, and legacy inventory databases typically reference customer IDs generated when the business opened.

When you retain the oldest record:

The failure mode here is stale customer-controlled data. When we keep the legacy record as canonical, we almost always find that the customer’s phone number, billing address, and marketing opt-in preferences are several years out of date.

The Case for Promoting the Most Recent Record

The newer record represents who the customer is right now. It reflects their current browser, their active payment method, and the address where they actually receive packages.

If a customer bought something last Thursday as a guest, that row reflects their current shipping reality. Promoting this newer record means:

The compromise is technical debt. If you promote the new record, you must run write-heavy queries to update the customer_id foreign keys on dozens or hundreds of historical order tables, return manifests, and support tickets. On a live database, running unindexed updates across two hundred thousand historical order rows can lock tables and stall checkout operations.

Resolving Attribute Conflicts Field by Field

You cannot simply copy the newest row over the oldest row. Customer records are trees of attributes, and those attributes drift at different rates.

When we write migration transformations, we isolate each field and assign it a resolution behavior based on operational risk rather than convenience.

Customer FieldDefault WinnerResolution RuleOperational Risk if Wrong
Email AddressMatch KeyStrict lowercase normalizationSplit profiles; unlinked checkouts
Password HashNewestKeep hash from platform customer used lastCustomer locked out of storefront
Shipping AddressNewestAppend new address; do not overwritePackages routed to old residences
Marketing Opt-InConservativeExplicit opt-in only if latest record opted inRegulatory compliance violations
Tax Exemption StatusOldestRetain verified documentation unless revokedIllegal zero-tax sales transactions
Order HistoryUnionRe-link all historical IDs to single survivorInaccurate lifetime revenue reporting

Address handling requires particular care. We never overwrite a legacy address book entry with a new address line. Instead, we append the newer address as the “default shipping” entry while leaving legacy addresses in the customer’s secondary address pool. If your target software limits customers to a single address record, choose the address tied to the most recent fulfilled order, not the address tied to the account registration form.

Email addresses require strict normalization before you even compare records. Customers routinely introduce whitespace or mixed casing during mobile checkout. According to the email address specifications in RFC 5322, the local part of an address (before the @ symbol) can theoretically be case-sensitive, but in commercial practice, treating addresses as case-insensitive strings prevents massive duplication. We convert all emails to lowercase and strip leading and trailing whitespace before running any deduplication checks.

What Breaks in Month Two

Week one of a migration feels successful if the front end takes money, orders print in the warehouse, and the daily sales balance matches your merchant account. Month two is when the latent migration debt surfaces.

Stale Webhooks and Third-Party Connectors

Most small stores rely on half a dozen cloud tools: a helpdesk, an email marketing engine, an inventory management tool, a reviews aggregator, and a fulfillment portal.

When you merge customer records, you usually perform the merge directly inside the primary store database. You delete record B and associate its historical orders with record A.

However, your downstream integrations often do not listen for customer merge events. Many basic store platforms do not even emit a webhook when two customer accounts merge.

By week six, we routinely see helpdesk tools operating on severed links. A customer opens a ticket using the email from record B. The helpdesk tries to look up their order history using the cached customer ID it stored during migration week. The store’s API returns a 404. Your customer support agent assumes the customer has never bought anything and asks for proof of purchase, frustrating a five-year repeat buyer.

Similarly, review apps often send review request emails thirty days after an order. If those apps hold the discarded customer ID, the review submission will either drop silently or fail to display as a “Verified Buyer” badge.

The Guest Checkout Trap

Between days thirty and sixty, customers who were merged suddenly return to place another order.

If they check out as a guest using an alternative email (perhaps an iCloud private relay or an old work address that was originally merged via physical address matching), the platform will often regenerate the exact duplicate you spent hours removing.

Worse, if your store allows customer account creation without email verification, someone may register an account using an email address that you merged into an administrative “master” record. When the platform encounters this, some commerce software will simply split the records again, isolating the historical data in a profile that the customer can no longer access.

Deduplication Mechanics We Trust

We do not trust automated merge apps found on platform app marketplaces. They are usually built for small catalogs and simple schemas; they make destructive assumptions about which records to drop, and they rarely log their transformation steps.

When we handle this, we stage the merge outside the production environment. We export the raw customer table, the address table, and the order table into a local relational database (usually PostgreSQL).

From there, we write explicit scripts to generate a translation map. The translation map is a simple two-column table: deprecated_id and surviving_id.

deprecated_id | surviving_id
--------------+-------------
cus_10928     | cus_88201
cus_10929     | cus_88201
cus_14502     | cus_12004

Before touching the production data, we validate the translation map against business logic:

Once the translation map passes review, we run the updates against historical order records first. We re-point the order foreign keys to the surviving_id. Only after all child records have been safely moved do we archive or delete the deprecated_id rows.

If your software platform does not let you update historical order records via API—which is true for several closed-ecosystem platforms—you should not merge the accounts at the platform level at all. Instead, leave both customer records in place, append an internal tag or note (such as duplicate_of:cus_88201) to the older record, and disable marketing outreach on the duplicate. Breaking your database constraints by force is always worse than living with redundant rows.

If we were forced to make this call on a standard store migration tomorrow, we would keep the newest record as the canonical profile, update historical order pointers to that new ID, append the older address data to the new record’s address book, and permanently preserve the translation map in cold storage.