Title: Batch merging via Apex?
Tags: apex;contact;merge
Question: Hello and thank you in advance. I'm looking to merge about 2000 records (about 1000 merges in total). My question is whether there is a best practices for this type of task. I'm able to merge 1 set of contacts via Execute Anonymous in the Developer Console:
Contact con = [select id,fax,phone from contact where id='xxxxx'];
Contact con2 = [select id,fax,phone from contact where id='xxxxx'];
database.merge(con, con2);
I would imagine I will need to employ a collection of tuples and do a foreach on them to handle the merging. Where do I start here, as far as storing a collection of tuples (e.g. {keeper1, mergee1},{keeper 2, mergee2})? Would I create a custom object that would house the tuples I intend to use for merging and then loop through them? When I have the Apex script written should I execute via the Execute Anonymous, or should I create a class to handle the work and invoke this a different way? Any help is greatly appreciated and sorry if my explanation is lacking.

Here is the accepted answer: Use a custom object so you can load them quickly. Use a class so you can process those all in one batch go. Because of DML limits, you won't be able to just execute anonymous, unless you feel like running your script hundreds of times.
For the custom object, use two lookup fields, the account to merge to, and the account to merge from. You could include a "status" field to indicate success/failure.
For your class, a simple batch class works:
public class BatchMerge implements Database.Batchable<Merge_Record__c&gt; {
public Database.QueryLocator start(Database.BatchableContext context) {
return Database.getQueryLocator([SELECT Merge_To_Account__c, Merge_From_Account__c FROM Merge_Record__c]);
public void execute(Database.BatchableContext context, Merge_Record__c[] records) {
for(Merge_Record__c record: records) {
try {
Database.merge(new Account(Id=record.Merge_To_Account__c), record.Merge_From_Account__c);
record.Status__c = 'Success';
} catch(exception e) {
record.Status__c = 'Failed';
}
}
update records;
public void finish(Database.BatchableContext context) {
Make sure when you call your script, it uses a small enough scope size (not 200) to accommodate the massive number of DML statements you'll wrack up using this code:
Database.executeBatch(new BatchMerge(), 10);
Adjust this size to suit your needs.
Comment for this answer: What exactly does the custom object accomplish? Does it have just two lookups to contacts? Several pairs? Kind of hard for me to suss out what you are actually recommending.
Comment for this answer: @AdrianLarson We just implemented this not too long ago (last week or so); here's the basic skeleton of what we did. It works like a champ, and is easy to implement (I typed up the code in the answer in less than 7 minutes).
Comment for this answer: @sfdcfox Much appreciated, that info is very helpful. Dumb question, but once I have the class created to do the work, how/where do I invoke the class?
Comment for this answer: @parrotsnest No, that's a legit question. For a simple one-time-use scenario, just use the Developer Console and run it from there (use the last script line in the answer). For times when you might want to run this, say, every day, you can write a quick scheduled class and run it from there.
Comment for this answer: @sfdcfox So run the last line (to invoke) via Execute Anonymous?
Comment for this answer: Yeah, just run that by itself, it'll do the heavy lifting for you.
Comment for this answer: @sfdcfox I know this is old, hoping you're still active here... any better methods to accomplish the same thing 4 years later? Is this still viable in mid 2019? If I were to use this, what objects would the custom fields Merge_To_Account__c, Merge_From_Account__c be created on? What is/how would I create Merge_Record__c(looks like a custom db table?)? I have experience in software development, but mostly web dev, relatively new to SF. Any help is appreciated!
Comment for this answer: @TCooper I'm still around. Let's see... (a) A better solution might use Duplicate Rules to automatically identify duplicates. (b) We're still using the framework here, it works. (c,d) In the Setup menu, you can create a custom object (which is just like a custom table in other RDBMS) with the custom fields outlined here. If you got stuck somewhere, I suggest you ask a new question, as comments are limited in length and ephemeral by nature. See also [this question](https://salesforce.stackexchange.com/q/250545).
Comment for this answer: Thank you! I'll be sure to post a new question if I run into anything, but I'm thinking I'll be good with what you laid out here. Definitely going to google duplicate rules real quick.

Here is another answer: Below simple, not (code) efficient but effective script have saved me tons of hours. Run in DevCons or Workbench ....
//** Tweak your account selection ... aka where clause ..
List<AggregateResult&gt; MyMergeList = [select count(id), name from account
where Origin__c = 'Lead' group by name  having count(id) &gt;1 limit 25];
For (AggregateResult Agm:MyMergeList){
//** Tweak the string parameters to match your preference
(confidence)....
// String accName = '%'+ string.valueOf(agm.get('Name')) + '%';
String accName = string.valueOf(agm.get('Name'));
Account ToAccount = [Select id, name from account where name like :accName limit 1];
Account FromAccount = [Select id, name from account where Id != :ToAccount.Id and name like :accName Limit 1];
system.debug(LoggingLevel.Info,'*** FromAccount: ' + FromAccount.Name+' ('+FromAccount.Id+')');
system.debug(LoggingLevel.Info,'*** ToAccount  : ' + ToAccount.Name+' ('+ToAccount.Id+')');
database.merge(ToAccount , FromAccount );

{'source': 'StackExchangeDataset', 'subset': 'salesforce.stackexchange.com', 'metadata': {'AnswerCount': 2, 'CommentCount': 0, 'ContentLicense': 'CC BY-SA 3.0', 'CreationDate': '2015:00:11/12/15', 'FavoriteCount': 1.0, 'PostTypeId': 1, 'Tags': '<apex><contact><merge>', 'Title': 'Batch merging via Apex?', 'ViewCount': 4364, 'id': 99250, 'user_AboutMe': None, 'user_AccountId': 5197365.0, 'user_DisplayName': 'tchock', 'user_Reputation': 71.0, 'user_Views': 8.0, 'user_id': 26307.0}}