+ Start a Discussion
Mee SharmaMee Sharma 

Merge accounts using Apex with duplicate value in a particular field

I have a requirement where i need to merge all accounts if a particular field vendor_code__c value is duplicate.There is also a condition that the account of record type 'A' needs to be the master and record type 'B' needs to be the dupliacte. I have written  a batch code for this fucntionality.I am not getting any errors but the accounts are not getting merged either. Kindly help!

global class BatchVendorAccountMerge implements database.Batchable<sobject>   {

    global  database.QueryLocator start(Database.BatchableContext ctx){
        string query;
        query = 'SELECT Id, Type, RecordTypeId,Record_Type__c, Name, MasterRecordId, Vendor_Code__c FROM Account';
                   
        return database.getQuerylocator(query);
        
    }
    
    global void execute(Database.BatchableContext BC, list<account> scope ){
        
     //create a map with vendor code and its account        
            
        //to store all unique vendor codes
        Set<string> strVC = new Set<string>();
        
         //create a map with vendor code and its account        
        map<string,list<account>> vendoraccmap = new map<string,list<account>>();
        
        
        for(account a:scope){
            
            strVC.add(a.Vendor_Code__c);
                        
            if(vendoraccmap.containskey(a.Vendor_Code__c)) {
              vendoraccmap.get(a.Vendor_Code__c).add(a);
            } else {
                vendoraccmap.put(a.Vendor_Code__c, new List<account> {a});
           
        } 
        }
       system.debug('****Unique vendor codes***'+strVC);
        system.debug('****vendor and acc map***'+vendoraccmap);
        
       Account masteracc = new account();
        list<account> dupacc = new list<account>();
            
        for(string v:vendoraccmap.keySet()){
            if(vendoraccmap.get(v).size() > 1)               
                
            
                system.debug('**vendoraccmapsize**'+vendoraccmap.get(v).size());
            {  
          
                for(Account a:vendoraccmap.get(v)) 
                {
                    if(a.Record_Type__c == 'A'){
                        masteracc.id=a.id;
                    }
                     else  if (a.Record_Type__c == 'B') {
                        dupacc.add(a);
                    }
                    
                     
                }
                
                system.debug('***Master account***'+masteracc);
                system.debug('***Duplicate accounts***'+dupacc);
            }
        }
        
        
        Database.MergeResult[] results = Database.merge(masteracc, dupacc, false);
      
        
        
     system.debug('***results merged**'+results);
        
       for(Database.MergeResult res : results) {
          if (res.isSuccess()) {
              System.debug('Master record ID: ' + res.getId());
        System.assertEquals(masteracc.Id, res.getId());               
        
      
        List<Id> mergedIds = res.getMergedRecordIds();
        System.debug('IDs of merged records: ' + mergedIds);                
        
                   
    }
    else {
        for(Database.Error err : res.getErrors()) {
            
            System.debug(err.getMessage());
        }
       }
    
     }
    }
    
    
     global void finish(Database.BatchableContext BC){
         
    }

    
    
}
Best Answer chosen by Mee Sharma
Zuinglio Lopes Ribeiro JúniorZuinglio Lopes Ribeiro Júnior
Hello Meenakshi Rajasekaran, 


I've changed your code a bit but couldn't test it. Here are some considerations about the changes:

1 - Your batch was not completely bulkified as the merge operation was not isolated by master Account.
2  - Keep in mind that the merge takes up two records to merge with a master record. In the code below I check whether this limit was reached to prevent any errors. In the next execution of the Batch the merge should continue without problems.
3 - If there are more than two record types in the Account you should consider adding a where clause to your query filtering by the ones you need. Preferably using the record type id.
4 - If you use the field Record_Type__c only on this occasion, you should consider removing it and access the Name or DeveloperName from the query. SELECT Id, Type, RecordTypeId, Record.DeveloperName or RecordType.Name, Name, MasterRecordId, Vendor_Code__c FROM Account.
5 - As the merge operation counts against the DML limit, I'm also checking it every time a merge is finished. If the limit is reached the execution is stopped and should continue without problems in the next execution of the Batch.
6 - Consider following the Java naming convention as per the recommendation of Salesforce.
7- Consider creating an Event Log for the asynchronous processes to make easier to track issues.
 
global class BatchVendorAccountMerge implements Database.Batchable<sobject> {

    global  Database.QueryLocator start(Database.BatchableContext ctx) {                  
        return Database.getQuerylocator([SELECT Id, Type, RecordTypeId, Record_Type__c, Name, MasterRecordId, Vendor_Code__c FROM Account]); 
    }
    
    global void execute(Database.BatchableContext BC, List<Account> accounts ) {
        
        
        // Create a map with vendor code and its account
        Map<String, List<Account>> vendorAccountsToMerge = new Map<String, List<Account>>();
        
        
        for (Account account : accounts) {
				
			List<Account> accountsToMerge = vendorAccountsToMerge.get(account.Vendor_Code__c);
			
			if (accountsToMerge == null) {
				accountsToMerge = new List<Account>();
				vendorAccountsToMerge.put(account.Vendor_Code__c, accountsToMerge);
			}
			
			if (accountsToMerge.size() < 2) {
				accountsToMerge.add(account);
			} else {
				
				// Merges takes up to two records to merge with the master
				// https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_database.htm
				System.debug('Maximum of accounts to merge reached.');
			}
            
        }

        System.debug('****vendor and acc map*** ');
		System.debug(vendorAccountsToMerge);

        List<account> dupacc = new list<account>();
            
        for (String vendorCode : vendorAccountsToMerge.keySet()) {
			
			List<Account> accountsToMerge = vendorAccountsToMerge.get(vendorCode);
			
            if (accountsToMerge.size() > 1) {  
			
				Account masterAccount;
				List<Id> duplicatedAccounts = new List<Id>();			
          
                for (Account account : accountsToMerge) {
					
                    if (account.Record_Type__c == 'A') {
                        
						if (masterAccount == null) {
							masterAccount = account;
						} else {
							System.debug('More than one account with the record type A found. Details:');
							System.debug('Account Ids: ' + masterAccount + ' ' + account.Id);
						}
						
                    } else if (account.Record_Type__c == 'B') {
                        duplicatedAccounts.add(account.Id);
                    }

                }

                System.debug('***Master account*** ' + masterAccount);
                System.debug('***Duplicated accounts*** ' + duplicatedAccounts);
                
                Database.MergeResult[] results = Database.merge(masterAccount, duplicatedAccounts, false);
                
                System.debug('***results merged** ' + results);
                
                for (Database.MergeResult res : results) {
                    if (res.isSuccess()) {
                        System.debug('Master record ID: ' + res.getId());
                        System.assertEquals(masterAccount.Id, res.getId());               
                        List<Id> mergedIds = res.getMergedRecordIds();
                        System.debug('IDs of merged records: ' + mergedIds);                	   
                    } else {
                        for (Database.Error err : res.getErrors()) {
                            System.debug(err.getMessage());
                        }
                    }						
                }                
			}
			
			// If the DML limit is reached, breaks the execution and continue on the next Batch execution
			if (Limits.getDMLRows() == Limits.getLimitDMLRows()) {
				System.debug('DML limit reached. Shall continue on the next execution');
				break;
			}
							
        }
    
    }
 
    global void finish(Database.BatchableContext BC) {
         
    }

}

Hope to have helped!

Regards.

Don't forget to mark your thread as 'SOLVED' with the answer that best helps you.

All Answers

Zuinglio Lopes Ribeiro JúniorZuinglio Lopes Ribeiro Júnior
Hello Meenakshi Rajasekaran, 


I've changed your code a bit but couldn't test it. Here are some considerations about the changes:

1 - Your batch was not completely bulkified as the merge operation was not isolated by master Account.
2  - Keep in mind that the merge takes up two records to merge with a master record. In the code below I check whether this limit was reached to prevent any errors. In the next execution of the Batch the merge should continue without problems.
3 - If there are more than two record types in the Account you should consider adding a where clause to your query filtering by the ones you need. Preferably using the record type id.
4 - If you use the field Record_Type__c only on this occasion, you should consider removing it and access the Name or DeveloperName from the query. SELECT Id, Type, RecordTypeId, Record.DeveloperName or RecordType.Name, Name, MasterRecordId, Vendor_Code__c FROM Account.
5 - As the merge operation counts against the DML limit, I'm also checking it every time a merge is finished. If the limit is reached the execution is stopped and should continue without problems in the next execution of the Batch.
6 - Consider following the Java naming convention as per the recommendation of Salesforce.
7- Consider creating an Event Log for the asynchronous processes to make easier to track issues.
 
global class BatchVendorAccountMerge implements Database.Batchable<sobject> {

    global  Database.QueryLocator start(Database.BatchableContext ctx) {                  
        return Database.getQuerylocator([SELECT Id, Type, RecordTypeId, Record_Type__c, Name, MasterRecordId, Vendor_Code__c FROM Account]); 
    }
    
    global void execute(Database.BatchableContext BC, List<Account> accounts ) {
        
        
        // Create a map with vendor code and its account
        Map<String, List<Account>> vendorAccountsToMerge = new Map<String, List<Account>>();
        
        
        for (Account account : accounts) {
				
			List<Account> accountsToMerge = vendorAccountsToMerge.get(account.Vendor_Code__c);
			
			if (accountsToMerge == null) {
				accountsToMerge = new List<Account>();
				vendorAccountsToMerge.put(account.Vendor_Code__c, accountsToMerge);
			}
			
			if (accountsToMerge.size() < 2) {
				accountsToMerge.add(account);
			} else {
				
				// Merges takes up to two records to merge with the master
				// https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_database.htm
				System.debug('Maximum of accounts to merge reached.');
			}
            
        }

        System.debug('****vendor and acc map*** ');
		System.debug(vendorAccountsToMerge);

        List<account> dupacc = new list<account>();
            
        for (String vendorCode : vendorAccountsToMerge.keySet()) {
			
			List<Account> accountsToMerge = vendorAccountsToMerge.get(vendorCode);
			
            if (accountsToMerge.size() > 1) {  
			
				Account masterAccount;
				List<Id> duplicatedAccounts = new List<Id>();			
          
                for (Account account : accountsToMerge) {
					
                    if (account.Record_Type__c == 'A') {
                        
						if (masterAccount == null) {
							masterAccount = account;
						} else {
							System.debug('More than one account with the record type A found. Details:');
							System.debug('Account Ids: ' + masterAccount + ' ' + account.Id);
						}
						
                    } else if (account.Record_Type__c == 'B') {
                        duplicatedAccounts.add(account.Id);
                    }

                }

                System.debug('***Master account*** ' + masterAccount);
                System.debug('***Duplicated accounts*** ' + duplicatedAccounts);
                
                Database.MergeResult[] results = Database.merge(masterAccount, duplicatedAccounts, false);
                
                System.debug('***results merged** ' + results);
                
                for (Database.MergeResult res : results) {
                    if (res.isSuccess()) {
                        System.debug('Master record ID: ' + res.getId());
                        System.assertEquals(masterAccount.Id, res.getId());               
                        List<Id> mergedIds = res.getMergedRecordIds();
                        System.debug('IDs of merged records: ' + mergedIds);                	   
                    } else {
                        for (Database.Error err : res.getErrors()) {
                            System.debug(err.getMessage());
                        }
                    }						
                }                
			}
			
			// If the DML limit is reached, breaks the execution and continue on the next Batch execution
			if (Limits.getDMLRows() == Limits.getLimitDMLRows()) {
				System.debug('DML limit reached. Shall continue on the next execution');
				break;
			}
							
        }
    
    }
 
    global void finish(Database.BatchableContext BC) {
         
    }

}

Hope to have helped!

Regards.

Don't forget to mark your thread as 'SOLVED' with the answer that best helps you.
This was selected as the best answer
Mee SharmaMee Sharma
Hi Thank you for your help. But I am facing the following error during the merge operation of 'Record_type__c which is a formula field.

"**results merged** (Database.MergeResult[getErrors=(Database.Error[getFields=(Record_Type__c);getMessage=Unable to create/update fields: Record_Type__c. Please check the security settings of this field and verify that it is read/write for your profile or permission set.;getStatusCode=INVALID_FIELD_FOR_INSERT_UPDATE;]"
Zuinglio Lopes Ribeiro JúniorZuinglio Lopes Ribeiro Júnior
Hello,

In this case, you can quit using a formula field, like I've shown in my previous answering, by querying information of the record type directly in the query or you can change the line 53 as shown below: 
masterAccount = new Account( Id = account.Id );

Hope to have helped!

Regards.

Don't forget to mark your thread as 'SOLVED' with the answer that best helps you.
Mee SharmaMee Sharma
Hello,

your solution worked.Thank you.I just have one more doubt.I have tried this out myself but not sure on which part of the code i can implement this.

In my first post i had mentioned on how 'account of record type 'A' needs to be the master and record type 'B' needs to be the dupliacte',I also have one more condition where
In line 48, if accountstomerge contains only records of record type 'B' (no 'A' type records in the list),then the record with oldest created date needs to be the master and the rest records as 'Duplicate'. I have tried including the below code in line 62.BUt it doesnt seem to work at all.Kindly help!
if(masteraccount == null)
{
masteraccount = duplicatedAccounts[0];
for(integer i=0;i<=duplicatedAccounts.size();i++)
{
if(masteraccount.createddate < duplicatedAccounts[i].createddate){
masteraccount = duplicatedAccounts[i];
}
 
Zuinglio Lopes Ribeiro JúniorZuinglio Lopes Ribeiro Júnior
Hello,

Your code is fine but you need to change your operator from less than(<) to greater than (>):
if (masterAccount.CreatedDate > duplicatedAccounts[i].CreatedDate)

You could also implement you custom Iterator class but the way you did is also fine. I also would start my counter (Integer i=0) to start in 1, since the zero will always start as the master and by starting in 1 you avoid comparing the Account to itself.

Hope to have helped!

Regards.