Merging records¶
lht merge merges duplicate Salesforce records inside Salesforce. Find the duplicates with SQL in Snowflake, where fuzzy matching and window functions are easy, then let lht perform the merges through the SOAP API merge() call. The Bulk API can't merge records.
lht merge --sobject Account --sql-file account_pairs.sql --dry-run
lht merge --sobject Account --sql-file account_pairs.sql
Query shape¶
The query returns one row per record to merge away:
| Column | Meaning |
|---|---|
MasterId |
The record that survives |
LoserId |
The record merged into the master |
-- account_pairs.sql: keep the oldest account per normalized website
SELECT FIRST_VALUE(ID) OVER (PARTITION BY LOWER(WEBSITE) ORDER BY CREATEDDATE) AS MasterId,
ID AS LoserId
FROM RAW.ACCOUNT
WHERE WEBSITE IS NOT NULL AND NOT ISDELETED
QUALIFY MasterId <> LoserId;
Supported objects are Account, Contact and Lead.
Validation before anything is sent¶
lht rejects the whole run before calling Salesforce if any pair is:
- a malformed or missing Id
- a self-merge (the master and loser are the same record, compared by 15-character Id)
- a loser listed more than once
- a record that appears as both a master and a loser
--dry-run stops after validation and prints the requests that would be sent.
What Salesforce does¶
- The loser goes to the Recycle Bin with
MasterRecordIdset. - Its related records (contacts, opportunities, activities) move to the master.
- The master keeps only its own field values. None of the loser's fields are copied, even where the master's are blank. If you need field values carried over, update the master first with
lht retl update. - Salesforce refuses to merge two accounts that are both related to the same contact (
MERGE_FAILED). Remove the loser's redundantAccountContactRelationfirst.
lht groups up to two losers per master in each merge request and sends up to 200 requests per API call. The command exits non-zero if any merge fails.