Pulse - Value Added
FRACTIONAL CRO · MARYLAND-BASED, NATIONWIDE · $0→$200M

Kory White

RevOps & Revenue Leadership

Get a 30-minute revenue checkup — Kory reviews your pipeline and forecast, then names the 1–2 fixes that move revenue fastest. 25 yrs scaling teams $0→$200M.

30-minute revenue checkup →
Hire a Fractional CROHow We Help?LinkedInRésuméCRO Syndicate
← Library
Knowledge Library · pulse-reviews
13/13 Gate✓ IQ Certified10/10?

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake?

PULSEKNOWLEDGE LIBRARY
pulserevops.com
KnowledgeHow do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake?
📖 4,062 words🗓️ Published Aug 11, 2026
Direct Answer

Export both sides and join them. Pull Dynamics 365 contacts with emailaddress1, contactid, and merged, then left-join against Snowflake's contact dimension on normalized email or the acquisition source ID. Any email carrying two contactid values is a live duplicate corrupting usage rollups. Merge in Dynamics first, then reload the warehouse.

What it is and why it matters

A duplicate contact after an acquisition is not one problem. It is three problems wearing the same coat, and RevOps teams that treat it as a single defect spend months chasing symptoms.

The first problem is identity. Two companies had two customer lists. Some percentage of those lists overlap — the same human being who bought from you and also bought from the company you just acquired. Dynamics 365 has no idea these are the same person, because the record from the acquired system arrived with its own GUID, its own emailaddress1 casing, its own phone formatting, and often its own notion of what a "contact" even represents. One system may have modeled contacts as individuals; the other may have modeled them as roles on an account. When a bulk import lands, Dynamics creates new records rather than matching existing ones because the incoming payload has no shared match key with anything already present.

The second problem is propagation. Your Snowflake warehouse ingests Dynamics. It does not judge Dynamics. If Dynamics holds two rows for the same person, the contact dimension holds two surrogate keys, and every downstream fact table — usage events, billing lines, engagement scores, territory assignment — fans out against both. The duplicate does not stay in the CRM. It becomes structural in your reporting layer within one sync cycle.

The third problem, and the one that makes this urgent rather than annoying, is money. Under usage-based pricing, the contact is frequently the billing atom. Seats, API calls, active users, storage attribution, consumption tiers — these are counted per contact or per account-contact pair. When Snowflake aggregates consumption at the contact level and the contact is split across two rows, each row carries partial usage. Neither row crosses the tier threshold that the combined record would have crossed. Or worse: you count both as active users and charge for two seats where one human exists.

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake — figure 1

That asymmetry is what makes post-acquisition dedup different from routine CRM hygiene. In a seat-license world, a duplicate contact is a data-quality embarrassment. In a consumption world, it is a billing error, and billing errors have a legal surface. A customer who receives an invoice for two seats when they have one employee is not filing a data-quality ticket. They are filing a dispute, and their procurement team now reviews every line on every future invoice from you.

The adjacent version of this problem is worth naming because it usually arrives on the same timeline: duplicate accounts. Contact dedup that succeeds while account dedup fails produces a distinctive failure mode — clean people hanging off two parent organizations, so your revenue rollups double-count at the account tier even after the contact tier is spotless. If your acquisition involved overlapping customer bases at all, plan both passes, and sequence accounts first. Contacts inherit account structure; the reverse is not true.

There is also a timing dimension most teams underestimate. The window between "acquisition closes" and "first combined billing run" is when this is cheapest to fix. Before that run, duplicates are internal. After it, duplicates are on invoices, in revenue recognition, in the audit trail, and in customer inboxes. Every week of delay converts a data problem into an accounting problem.

The step-by-step process

Work this in a fixed order. The single most common way teams fail is running the automation before the audit, which mass-merges records against rules nobody validated.

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake — figure 2

Step one: freeze the ingestion path. Before you touch a record, stop the acquired system's connector from writing new contacts into Dynamics. If the pipe is still open, you are bailing a boat with a hole in it. If business continuity forbids a hard stop, route incoming records to a staging entity or a holding queue rather than directly to the contact table. Two days of manual triage beats two weeks of chasing a moving target.

Step two: build the reconciliation extract. From Dynamics, export contacts with at minimum contactid, emailaddress1, telephone1, parentcustomerid, createdon, modifiedon, merged, and whatever custom field carries the acquisition source identifier. Include the statecode so you can distinguish active from deactivated records — merged records in Dynamics are deactivated, not deleted, and if your export ignores state you will "discover" thousands of duplicates that were already resolved.

Step three: normalize before you compare. Lowercase every email. Strip whitespace. Strip the plus-addressing suffix if your customers use it (name+tag@domain.comname@domain.com). Normalize phone numbers to digits only. Do this in the warehouse, in SQL, not by eyeballing a spreadsheet. Roughly half of what looks like a matching problem is actually a formatting problem, and normalization resolves it before any fuzzy logic is needed.

Step four: join and flag. Left-join the Dynamics extract against the Snowflake contact dimension on normalized email. Then self-join the Dynamics extract to find any normalized email carrying more than one active contactid. Those are your exact-match duplicates — the high-confidence tier, safe to resolve in bulk.

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake — figure 3

Step five: build the fuzzy tier separately. Same last name plus same account plus similar first name. Same phone, different email. Same email local-part across the two corporate domains — j.doe@acquiredco.com and j.doe@yourco.com frequently belong to the same person post-integration when the acquired staff get new addresses. This tier requires human review, always. Never bulk-merge fuzzy matches.

Step six: rank and choose a survivor. For each duplicate cluster, decide which record wins before you merge anything. Dynamics' native merge favors the most recently modified record, which is often the wrong survivor — the recently touched record may be the empty one created by the import last Tuesday, while the record with eight years of activity history sits untouched. Choose survivors explicitly using activity count, opportunity linkage, and consumption history, not recency.

Step seven: merge, then reload. Merge in Dynamics. Do not attempt to fix this by deduplicating in Snowflake and leaving Dynamics dirty — you will have a clean warehouse feeding correct invoices while every rep sees two records and the next sync reintroduces the split. Fix the source, then refresh the dimension and re-run usage aggregation for the affected contacts.

Step eight: verify against billing. Take the merged clusters, query the usage fact table for the surviving contact, and confirm the aggregated consumption equals the sum of the pre-merge parts. If it does not, your fact table was keyed to the deprecated surrogate key and those events are now orphaned.

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake — figure 4

Costs, timelines, and typical ranges

Nobody can tell you your duplicate rate without seeing your data, and any number quoted at you in a vendor deck should be treated as marketing. What can be described honestly is the shape of the work and what drives it up or down.

Timeline drivers. Three variables dominate. First, whether the two companies shared a market — overlapping customer bases produce genuine duplicate humans, which is the expensive kind. Non-overlapping bases produce mostly false positives, which are cheap. Second, whether the acquired system carried a stable external identifier. If it did, your join is deterministic and most of the work is mechanical. If it did not, you are doing probabilistic matching and every ambiguous cluster costs human minutes. Third, whether billing already ran on the combined data. Pre-billing cleanup is a data project. Post-billing cleanup is a data project plus a credit-memo project plus a customer-communication project.

Where the hours actually go. In practice the extract-and-join work is the small part — a competent analyst who knows both systems builds the reconciliation query in a day or two. The fuzzy tier is where time disappears. Manual review of ambiguous clusters runs at a rate you should measure in your first hour and then extrapolate: pull fifty clusters, time yourself, multiply. That measured rate is the only credible input to a project estimate. Assume the rate degrades as you go, because you review the obvious ones first.

Sequencing against the billing calendar. Anchor the whole plan to your billing run date, working backward. You need the merge complete, the warehouse reloaded, and the usage reconciliation clean before the aggregation job fires. If that is not achievable, the better move is to pin the acquired cohort out of the combined billing run for one cycle and bill them on the legacy path — a deliberate, documented exception beats a wrong invoice.

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake — figure 5

The temporary view pattern. If you genuinely cannot finish the Dynamics merge before billing, a defensible interim is to point the billing aggregation at a deduplicating view rather than the raw dimension — a ROW_NUMBER() OVER (PARTITION BY normalized_email ORDER BY modifiedon DESC) filtered to rank one, with usage summed across the whole partition rather than taken from the surviving row alone. That last detail matters. A naive dedup view picks one row and silently drops the other row's consumption, which understates the bill instead of overstating it. You have traded one billing error for another.

Treat that view as scaffolding with a demolition date. Put the removal ticket in the backlog the same day you create the view. Deduplicating views have a strong tendency to become permanent load-bearing infrastructure that nobody remembers is there, and three quarters later someone builds a new report on top of it.

Cost of doing nothing. The compounding cost is not the duplicates you have. It is every downstream artifact built on top of them — dashboards, cohort analyses, churn models, territory assignments, and the compensation calculations that flow from territory assignments. A duplicate contact that survives a quarter has been baked into a comp plan, and unwinding a comp plan is an entirely different kind of conversation than unwinding a database row.

Where teams get it wrong

Deduplicating in the warehouse and calling it fixed. This is the most seductive mistake because it works — briefly, and in exactly one place. The report is clean, the invoice is right, everyone declares victory. Meanwhile every rep still sees two contacts, logs activity against whichever one appears first in search, and the next sync reintroduces the split into the dimension. Snowflake is downstream. Dedup belongs at the source.

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake — figure 6

Trusting the native merge to pick the survivor. Dynamics 365 duplicate detection is genuinely useful for catching new duplicates at creation time. It is not a bulk remediation tool, and the default survivor logic — most recently modified — actively works against you in an acquisition scenario, where the freshly imported empty record always looks newer than the rich one.

Matching on email alone at the fuzzy tier. Shared mailboxes will destroy you. info@, billing@, ap@, support@, orders@ — these belong to organizations, not people, and matching on them merges unrelated humans into a single record. That is a worse outcome than the duplicate you started with, because it is genuinely hard to unwind. Maintain an explicit exclusion list of generic local-parts and skip them entirely.

Bulk-merging the fuzzy tier under deadline pressure. It happens near the end of every project like this. The exact-match tier goes smoothly, confidence rises, the billing date approaches, and someone proposes running the same automation across the probable matches. Merges are not cleanly reversible. The activity history, the opportunity links, the notes — those get consolidated, and separating them afterward means restoring from backup or reconstructing by hand.

Ignoring the fact table. Merging contacts in Dynamics resolves the dimension. It does not automatically remap historical usage events keyed to the deprecated contactid. Those events either follow the merge or become orphans, and orphaned usage is invisible on the invoice until a customer asks why their consumption dropped.

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake — figure 7

Forgetting deactivated records. Dynamics deactivates the loser in a merge rather than deleting it. If your Snowflake ingestion does not filter on statecode, deactivated losers keep landing in the dimension and your duplicate count refuses to fall no matter how many merges you run.

Running dedup and system consolidation simultaneously. Post-acquisition, there is enormous pressure to migrate the acquired company off its CRM at the same time as cleaning the data. Resist it. Do the dedup against a stable target, then migrate. Debugging a match failure while the schema underneath you is being rewritten is a genuinely miserable way to spend a quarter.

No exclusion path for legitimate near-duplicates. Some contacts genuinely should exist twice — a person who is a contact at two different customer organizations, or a consultant working across several accounts. Without an explicit "reviewed, keep separate" flag, every dedup pass rediscovers them, re-queues them for review, and slowly erodes the reviewers' trust in the whole exercise.

Decision framework: when to choose what

The question that actually matters is not *how* to dedup. It is *what to dedup, in what order, and how much automation to grant each tier*. Three inputs drive the answer.

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake — figure 8

Input one: does a deterministic key exist? If the acquired system's records carry a stable external identifier that also exists on your side — a shared billing system ID, a common customer number, a portal login — the join is deterministic and you can automate aggressively. If no such key exists, everything is probabilistic and human review is not optional at the ambiguous tier.

Input two: is the contact the billing atom? Under usage-based pricing, often yes — and that raises the stakes enormously. If the contact is purely a relationship record and billing keys off the account, the urgency drops and you can sequence dedup behind other integration work. Answer this before you scope anything.

Input three: has combined billing already run? Pre-billing, this is an internal data project on your own schedule. Post-billing, you have a remediation project with an external-facing component, and the sequencing inverts — you fix the invoices that went out before you fix the records that produced them, because customers are waiting.

From those three inputs, the tiering falls out. Exact matches on normalized email, excluding generic mailboxes, with a survivor chosen by activity and usage weight rather than recency: automate. Domain-crossover matches where the local-part is identical across the acquired and acquiring corporate domains: batch for review, but review in bulk, since these are usually the same person and go fast. Name-and-account similarity without email or phone corroboration: individual review, no exceptions. Anything touching a shared mailbox or a high-value account: individual review with a second pair of eyes.

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake — figure 9

For guardrails afterward, the layered approach holds up. Turn on Dynamics duplicate detection at create time — set to warn rather than block during the integration window, because a hard block will strand records that reps need today and generate a shadow spreadsheet you will never find. Log every warning to a staging table so you can measure the false-positive rate before tightening. On the warehouse side, add a scheduled check that flags any normalized email carrying multiple active contact IDs and alerts rather than auto-merging. Detection automates safely. Resolution does not.

Broader integration patterns this touches

Contact dedup rarely arrives alone, and the teams that handle it well treat it as one lane in a wider integration program rather than a standalone ticket.

Account hierarchy first. As noted earlier, resolve parent-child account structure before contacts. If the acquired company's "Acme Corp" and your "Acme Corporation" remain separate accounts, deduplicating their contacts produces clean people scattered across two org trees — which fixes nothing at the reporting layer where executives actually look.

Product and entitlement mapping. Under consumption pricing, the contact record is only half the billing key. The other half is the entitlement — which meter, which tier, which contract. Two companies almost never define a "unit" the same way. Contact dedup that completes while meter definitions stay unreconciled produces correct customer counts against incomparable consumption numbers.

How do you debug duplicate contacts after acquisition for usage-based pricing RevOps teams on Dynamics 365 when data warehouse in Snowflake — figure 10

Territory and comp. The moment contacts merge, ownership changes, and ownership changes ripple into quota attainment. Coordinate the merge window with sales ops so the recalculation is expected rather than discovered. A rep whose attainment drops overnight because their duplicate accounts collapsed will escalate, and they will be right to.

Consent and communication state. This one gets missed constantly. If one record opted out of marketing email and its duplicate did not, the merge must preserve the more restrictive state. Survivor logic based purely on recency or completeness will happily promote the opted-in record and resume emailing someone who unsubscribed. Encode consent as an override on survivor selection, not as a field that follows whichever row wins.

Support and product telemetry. Contacts frequently exist in the support system and the product's own user table as well as the CRM. A dedup that covers CRM and warehouse but leaves the support tool split means agents still see fragmented history. Inventory every system that holds a contact identity before you start, and decide explicitly which ones are in scope for this pass and which are deferred.

The general principle: identity resolution is an ongoing capability, not a one-time cleanup. Acquisitions are simply the event that makes the absence of that capability impossible to ignore.

Related questions

Should we deduplicate in Dynamics 365 or in Snowflake?

Dynamics, always, as the system of record. Warehouse-side dedup is acceptable only as temporary scaffolding while the source merge is in flight, and it should carry an explicit removal date. Otherwise reps see split records and the next sync undoes your work.

What happens to usage history when two contacts merge?

It depends on whether your fact tables key to the contact GUID or to a stable business key. GUID-keyed events referencing the deactivated record become orphans unless explicitly remapped. Always reconcile post-merge usage totals against the sum of the pre-merge parts.

Can Dynamics 365 duplicate detection rules handle this alone?

They are built to catch new duplicates at creation time, not to remediate a bulk import backlog. Use them as the forward-looking guardrail after cleanup, set to warn during the integration window so reps are not blocked mid-deal.

How do we prevent this in the next acquisition?

Require a stable external identifier in the data-transfer agreement during diligence, normalize email at ingestion rather than after, and route acquired records through a staging entity with a match step instead of writing straight to the contact table.

Do duplicate accounts matter more than duplicate contacts?

Usually yes, and they should be sequenced first. Contacts inherit account structure, so account-level duplicates keep revenue rollups double-counting even after every contact is perfectly clean.

FAQ

Why do acquisitions produce duplicate contacts even when both companies had clean data?

Because cleanliness is defined within a system, not across systems. Each company's records were internally consistent and deduplicated against their own population. Neither had any way to know about the other's records. When the two populations merge, the overlap — customers, partners, and prospects who dealt with both companies — surfaces as duplicates for the first time. Clean inputs, duplicate output. It is a structural consequence of combination, not a sign that anyone was sloppy.

How do I know whether the contact or the account is my real billing atom?

Read the aggregation logic in your usage pipeline. Find the query that produces the numbers on the invoice and look at its GROUP BY. If it groups by a contact key, the contact is the atom and duplicates directly distort billing. If it groups by account and contacts are only used for attribution or notification, duplicates hurt reporting and rep experience but not the invoice. This distinction sets your entire urgency level, so establish it before scoping anything else.

Is it ever safe to bulk-merge fuzzy matches?

Only if you can cleanly reverse the operation, which in practice means only if you have a tested restore path for the affected records. Merges consolidate activity history, notes, and relationship links; unwinding them by hand is slow and lossy. The pressure to bulk-merge always arrives late in a project when the exact-match tier went smoothly and the deadline is close. That confidence is not transferable to the ambiguous tier.

What is the single most reliable early signal that duplicates are hurting the business?

Compare distinct active contacts in the warehouse against distinct billable users in your product's own telemetry, per account. A persistent gap where the CRM shows more people than the product does is the clearest evidence that identity is fragmented, and it is measurable without any dedup tooling at all.

How should we handle contacts who legitimately appear on multiple accounts?

Give them a permanent, explicit exclusion flag with a reason and a reviewer name. Without it, every subsequent dedup pass rediscovers them, re-queues them for review, and slowly teaches your reviewers that the queue is full of noise. That erosion of trust is what eventually kills an otherwise well-run dedup program.

Should the ingestion connector stay off for the whole cleanup?

Not necessarily, and often it cannot. The workable compromise is routing incoming records to a staging entity or holding queue with a match step, rather than writing directly to the contact table. That keeps the target stable while you work without blocking business continuity for the acquired team.

Sources

flowchart TD S["How do you debug duplicate contacts af"] S --> N0["What it is and why it matters"] N0 --> N1["The step-by-step process"] N1 --> N2["Costs, timelines, and typical ranges"] N2 --> N3["Where teams get it wrong"]
flowchart LR C["How do you debug duplicate contacts af"] C --> H0["Costs, timelines, and typical ranges"] C --> H1["Where teams get it wrong"] C --> H2["Decision framework: when to choose wha"] C --> H3["Broader integration patterns this touc"]

Related on PULSE

Download:
Was this helpful?  
Sources cited
Pulse RevOps operational practicePulse RevOps operational practice
⌬ Apply this in PULSE
How-To · SaaS ChurnSilent revenue killer playbook