[SalesForce] Bulk merge of Contacts across different Accounts

I've created a class to manually do a mass merge of Contacts from different Accounts, creating AccountContactRelation records between the new master Contact and Accounts from the duplicates. Due to the limit of merging only 3 records per merge command, I'm hitting the Too many SOQL queries: 101 limit when I run a group of Contacts through.

This is not going to be an ongoing job, just a once-off to clean our database. How do I bulkify the code to run through all my contacts?

global class MergeRecords {

public boolean MergeContacts(){
    Contact master;
    list<Contact> masters = new list<Contact>([select Id, AccredoId__c
                                               from Contact where Master__c = true order by AccredoId__c]);
    set<string> acrIds = new set<string>();
    list<Contact> duplicates = new list<Contact>();
    map<string, list<Contact>> mapDups = new map<string, list<Contact>>();
    list<AccountContactRelation> acrs = new list<AccountContactRelation>();

    try{
        //Find all the Contacts that were duplicates, that have been marked as being the master Contact for the direct relationship
        for(Contact c : masters){
            if(c.AccredoID__c != null){
                acrIds.add(c.AccredoId__c);
            }
        }
        System.debug('#####MergeContacts acrIds: ' + acrIds);
        //Now get all those marked as being duplicates of the masters identified above. These will be merged into the master with
        //indirect relationships created to the original Accounts. The fields specific to those Accounts need to be populated after the ACR
        //has been created.
        duplicates = [select Id, AccountId, Department, Email, Phone, Title, Inactive__c, AccredoID__c,
                      CallCycleFrequency__c, AccredoCRMGroup__c, LastCallCycleVisitOn__c, Preferred_Day__c,
                      MasterAccredoId__c
                      from Contact where masterAccredoId__c in: acrIds order by MasterAccredoId__c, AccredoID__c];

        if(!duplicates.isEmpty()){
            Contact dup = duplicates[0];
            list<Contact> dups = new list<Contact>();
            for(Contact d : duplicates){
                if(d.MasterAccredoId__c != dup.MasterAccredoId__c){
                    mapDups.put(dup.MasterAccredoId__c, dups);
                    dup = d;
                    dups = new list<Contact>();
                }
                dups.add(d);                
            }
            mapDups.put(dup.MasterAccredoId__c, dups);

            for(Contact c : masters){
                list<Contact> cdups = mapDups.get(c.AccredoId__c);
                if(cdups != null){
                    list<Contact> merges = new list<Contact>();
                    database.MergeResult[] results;
                    integer i = 0;
                    for(Contact cd : cdups){
                        i += 1;
                        merges.add(cd);
                        if(i < 3){
                            results = database.merge(c, merges, false);
                            merges = new list<Contact>();
                            i = 0;
                        }
                        for(database.MergeResult res : results){
                            System.debug('#####MergeContacts Master record ID: ' + res.getId());
                            List<Id> mergedIds = res.getMergedRecordIds();

                        }
                        //Create the ACR since the merge doesn't do so
                        AccountContactRelation acr = new AccountContactRelation();
                        acr.AccountId = cd.AccountId;
                        acr.ContactId = c.Id;
                        acr.Acc_Email__c = cd.Email;
                        acr.Acc_Phone__c = cd.Phone;
                        acr.Acc_Title__c = cd.Title;
                        acr.Acc_Department__c = cd.Department;
                        acrs.add(acr);
                        System.debug('#####MergeContacts acr: ' + acr);
                    }
                }
            }
        }
        if(!acrs.isEmpty()){
            insert acrs;
        }

        return true;
    }
    catch(Exception e){
        System.debug('#####MergeContacts ERROR: ' + e);
        return false;
    }
}
}

Best Answer

While there is no way to bulky the code (as described in this answer), it is possible to create a process that will do the same as schedulable batchable job, instead of an insert/update trigger.

Steps:

  1. Implement a system field that will carry ID of a master record.
  2. Create an APEX class or a flow that will update duplicate record with master record id.
  3. Create schedulable batchable APEX class that will address marked records in small batches.

Pros of this method: free

Cons: you or your developer must maintain merge rules in APEX. The merge is not instant.

Alternatives: A. Use an application from AppExchange. There are quite a few available for any budget. B. Use a custom object to store pairs of ids (master, dupe). This will allow you to reduce amount of code that run by onUpdate trigger. (Make sense only if you have a dozens of them).

In my Org I am currently using a combination both solutions. The simple scenarios are identified and managed by Triggers/scheduled batches and the complex scenarios (when lots of business logic should be applied before merge) are handled by de-duplication application.

Related Topic