Survivorship
After matching identifies which records belong to the same entity, survivorship determines the canonical value for each field. When three sources say different things about the same customer, survivorship picks the winner.
The Conflict Problem
You've matched these three records as the same person:
| Field | CRM (Salesforce) | Billing (Stripe) | Support (Zendesk) |
|---|---|---|---|
| name | John Smith | Jon Smith | J. Smith |
| [email protected] | [email protected] | [email protected] | |
| phone | 555-0100 | null | 555-0100 |
| company | Acme Inc | Acme Incorporated | Acme |
| title | VP Engineering | null | VP of Eng |
| revenue | null | $50,000 | null |
| plan | null | Enterprise | null |
| tags | [prospect] | [paying] | [active, vip] |
| city | San Francisco | San Francisco | SF |
| updated_at | 2026-01-15 | 2026-02-01 | 2026-01-20 |
Which "name" goes in the golden record? What about "tags"? Survivorship answers these questions field by field.
Source Priority
The most common strategy. You rank your sources by trustworthiness, and the highest-ranked source with a non-null value wins.
How It Works
- Sources are ranked in order of priority (first = highest)
- For each field, check sources in priority order
- The first non-null value becomes the golden record value
Example
With ranking [crm, billing, support]:
| Field | CRM | Billing | Support | Winner | Why |
|---|---|---|---|---|---|
| name | John Smith | Jon Smith | J. Smith | John Smith | CRM is #1 and non-null |
| phone | 555-0100 | null | 555-0100 | 555-0100 | CRM is #1 and non-null |
| revenue | null | $50,000 | null | $50,000 | CRM null → Billing is next |
| plan | null | Enterprise | null | Enterprise | CRM null → Billing is next |
YAML Config
survivorship:
default: source_priority
source_ranking: [crm, billing, support]When to Use
- You have a clear "system of record" for most fields
- Data quality varies significantly between sources
- You want predictable, deterministic results
Most Recent
The value with the most recent timestamp wins. Best for fields that change over time (title, address, phone).
How It Works
- Each source record has a timestamp field (e.g.,
updated_at) - For each field, the value from the record with the latest timestamp wins
- Null values are skipped — the most recent non-null value wins
Example
| Field | CRM (Jan 15) | Billing (Feb 1) | Support (Jan 20) | Winner | Why |
|---|---|---|---|---|---|
| name | John Smith | Jon Smith | J. Smith | Jon Smith | Billing is most recent |
| title | VP Engineering | null | VP of Eng | VP of Eng | Support is more recent than CRM (Billing null) |
| city | San Francisco | San Francisco | SF | San Francisco | Billing is most recent |
YAML Config
survivorship:
default: most_recentWhen to Use
- Fields that change frequently (job title, address, phone number)
- All sources have reliable timestamps
- You want the "latest known truth"
Most Complete
The longest or most complete value wins. Best for fields where more detail is better (company name, address).
How It Works
- For string fields: the longest non-null value wins
- For arrays: the value with the most elements wins
- Ties are broken by source priority (if configured) or alphabetical order
Example
| Field | CRM | Billing | Support | Winner | Why |
|---|---|---|---|---|---|
| company | Acme Inc | Acme Incorporated | Acme | Acme Incorporated | Longest (18 chars) |
| name | John Smith | Jon Smith | J. Smith | John Smith | Longest (10 chars) |
| title | VP Engineering | null | VP of Eng | VP Engineering | Longest (14 chars) |
YAML Config
survivorship:
default: most_completeWhen to Use
- Company names where abbreviation vs. full name varies
- Address fields where some sources truncate
- Any field where more detail = better quality
Aggregate
Instead of picking one value, combine values from all sources. Six aggregate functions are available:
| Function | Description | Input → Output |
|---|---|---|
union | Combine all unique values | [a, b] + [b, c] → [a, b, c] |
intersection | Keep only values present in all sources | [a, b] + [b, c] → [b] |
max | Highest numeric value | 100, 200, 150 → 200 |
min | Lowest numeric value | 100, 200, 150 → 100 |
sum | Total across sources | 100, 200, 150 → 450 |
avg | Average across sources | 100, 200, 150 → 150 |
Example
| Field | CRM | Billing | Support | Function | Result |
|---|---|---|---|---|---|
| tags | [prospect] | [paying] | [active, vip] | union | [prospect, paying, active, vip] |
| revenue | null | $50,000 | null | max | $50,000 |
YAML Config
survivorship:
default: source_priority
source_ranking: [crm, billing, support]
overrides:
- field: tags
strategy: aggregate
function: union
- field: revenue
strategy: aggregate
function: maxWhen to Use
- union: Tags, labels, categories — keep everything
- intersection: Shared attributes across sources — only keep agreements
- max/min: Revenue, scores — use the extreme value
- sum: Quantities, counts — total across sources
- avg: Ratings, scores — average across sources
Field-Level Overrides
In practice, you'll use a different strategy for different fields. Set a global default and override specific fields:
survivorship:
default: source_priority
source_ranking: [crm, billing, support]
overrides:
- field: email
strategy: most_recent
- field: title
strategy: most_recent
- field: company
strategy: most_complete
- field: tags
strategy: aggregate
function: union
- field: revenue
strategy: aggregate
function: maxThis says:
- Most fields: Use the CRM value (source priority)
- email, title: Use the most recent value (these change over time)
- company: Use the most complete value (longest name wins)
- tags: Combine all tags from all sources
- revenue: Use the highest revenue figure
Worked Example: Golden Record
Let's build a complete golden record from the three-source example at the top of this page.
Input Records
| Field | CRM (Salesforce) | Billing (Stripe) | Support (Zendesk) |
|---|---|---|---|
| name | John Smith | Jon Smith | J. Smith |
| [email protected] | [email protected] | [email protected] | |
| phone | 555-0100 | null | 555-0100 |
| company | Acme Inc | Acme Incorporated | Acme |
| title | VP Engineering | null | VP of Eng |
| revenue | null | $50,000 | null |
| plan | null | Enterprise | null |
| tags | [prospect] | [paying] | [active, vip] |
| city | San Francisco | San Francisco | SF |
| updated_at | 2026-01-15 | 2026-02-01 | 2026-01-20 |
Survivorship Config
survivorship:
default: source_priority
source_ranking: [crm, billing, support]
overrides:
- field: email
strategy: most_recent
- field: title
strategy: most_recent
- field: company
strategy: most_complete
- field: tags
strategy: aggregate
function: union
- field: revenue
strategy: aggregate
function: maxResult: Golden Record
| Field | Golden Value | Strategy | Source |
|---|---|---|---|
| name | John Smith | source_priority | CRM (#1, non-null) |
| [email protected] | most_recent | Billing (Feb 1 — all same anyway) | |
| phone | 555-0100 | source_priority | CRM (#1, non-null) |
| company | Acme Incorporated | most_complete | Billing (longest: 18 chars) |
| title | VP of Eng | most_recent | Support (Jan 20 — Billing null, Support > CRM) |
| revenue | $50,000 | aggregate(max) | Billing (only non-null) |
| plan | Enterprise | source_priority | Billing (CRM null → #2) |
| tags | [prospect, paying, active, vip] | aggregate(union) | All sources combined |
| city | San Francisco | source_priority | CRM (#1, non-null) |
Each field used the strategy best suited to its semantics:
- name, phone, city, plan → source priority (CRM is the system of record)
- email, title → most recent (these fields change over time)
- company → most complete ("Acme Incorporated" beats "Acme Inc" and "Acme")
- tags → aggregate union (combine all labels)
- revenue → aggregate max (use the highest known figure)
Next Steps
- Overview — See how survivorship fits in the full pipeline
- String Comparators — The comparison algorithms that identify matches
- Scoring Strategies — How match scores determine which records to merge
