I have a requirement where I check for all Contacts related to Opportunities via Opportunity Contact Role junction object. For an Opportunity in particular stage, I update related contacts' with checkbox = true and then for some other Opportunities, I set the Contacts' checkbox = false.

The problem I am running into is that a Contact might have multiple Opportunities, few meeting true condition and few more matching false condition. Because of this, the batch job on top of the hour is updating such Contact(s) twice in the same transaction: enter image description here

I am sorting by most latest CreatedDate of the Opportunity but doesn't look like it's helping me to filter out duplicates. This works in Sandbox where there are handful of records but fails in QA with large volumes of data.

Any help to avoid this double update in single transaction is much appreciated. Here's the batch apex code:

global class ContactTransitionBatch implements Database.Batchable<sObject>{
global Database.QueryLocator start(Database.BatchableContext BC){     
    Id advisorRecId = Schema.SObjectType.Opportunity.getRecordTypeInfosByName().get('XXXX').getRecordTypeId(); 
    return Database.getQueryLocator([select Id, StageName, CloseDate, CreatedDate, Consultant_Contact__r.Name, Consultant_Contact__c from Opportunity where RecordTypeId =:advisorRecId]);

global void execute(Database.BatchableContext BC, List<Opportunity> scope){
  Set<Id> oppIds = new set<Id>();
  Set<Id> unSelectOppIds = new set<Id>();
  Set<Id> contIds = new Set<Id>();
  Set<Id> unSelectedcontIds = new Set<Id>();
  Map<Id,String> consultantCont = new Map<Id,String>();
  Set<Id> oppIdlst = new Set<Id>();
  Map<Id,Id> ContOppMap = new Map<Id,Id>();
  Set<Id> accIds = new Set<Id>();
  for(Opportunity opp : scope){
    if(opp.StageName=='ABC' || opp.StageName=='XYZ'){
      system.debug('oppIds are: '+ oppIds);
      system.debug('unSelectOppIds are: '+ unSelectOppIds);

  List<OpportunityContactRole> ocrList = [Select Id,ContactId,OpportunityId from OpportunityContactRole where OpportunityId IN :oppIdlst order by Opportunity.CreatedDate desc];
  Set<Id> contactIdSet = new Set<Id>();
  Set<Id> contactSetCheck = new Set<Id>();

  for(OpportunityContactRole ocr : ocrList){
    system.debug('contactIdSet are: ' + contactIdSet);
    if(oppIds.isEmpty() == false && oppIds.contains(ocr.OpportunityId) && !contactSetCheck.contains(ocr.ContactId)){
      system.debug('contIds are: ' + contIds);
      system.debug('ContOppMap are: '+ ContOppMap);
      system.debug('contactSetCheck are: ' + contactSetCheck);
    if(unSelectOppIds.isEmpty() == false && unSelectOppIds.contains(ocr.OpportunityId) && !contactSetCheck.contains(ocr.ContactId)){
      system.debug(' unSelectedcontIds are: ' +  unSelectedcontIds);
      system.debug('ContOppMap are: '+ ContOppMap);
      system.debug('contactSetCheck are: ' + contactSetCheck);

    List<Contact> contactList = [select Id, AccountId, Consultant1__c, Onboarding_Advisor__c from Contact where Id IN :contactIdSet];
    List<Contact> conListToBeUpdated = new List<Contact>();
    for(Contact ca :contactList){           
      //This code is to check the "Onboarding" checkbox on Contacts to True
      if(contIds.isEmpty() == false && contIds.contains(ca.Id) && ca.Onboarding__c == false){
      Contact ca1 = new Contact();
      ca1.Onboarding__c = true;
      if(consultantCont!=null && consultantCont.get(ContOppMap.get(ca.Id))!=null){
        ca1.Consultant1__c = consultantCont.get(ContOppMap.get(ca.Id));
        ca1.Id = ca.Id;    
        system.debug('1st conListToBeUpdated are: ' + conListToBeUpdated);

      //This code is to uncheck the "Onboarding" checkbox on Contacts to False
      if(unSelectedcontIds.isEmpty() == false && unSelectedcontIds.contains(ca.id) && ca.Onboarding__c == true){
        Contact ca1 = new Contact();
        ca1.Onboarding__c = false;
        ca1.Cases_Created__c = false;         
        ca1.Id = ca.Id;
        system.debug('2nd conListToBeUpdated are: ' + conListToBeUpdated);

      //This code is to uncheck the "Onboarding" checkbox on Accounts to False if checked True MANUALLY
      List<account> acclst = new List<account>();
      for(Account acc1 :[select Id,Onboarding__c from Account where Id IN :accIds AND Onboarding__c=true]){
        Account ac = new Account();
        ac.Onboarding__c = false;
        ac.Id = acc1.Id;
        system.debug('acclst are: ' + acclst);

      if(acclst!=null && acclst.size()>0){
        Database.saveResult[] result = Database.update(acclst,false);

      system.debug('Final conListToBeUpdated are: '+ conListToBeUpdated);
        Database.saveResult[] result = Database.update(conListToBeUpdated,false);
  global void finish(Database.BatchableContext BC){        


1 Answer 1


I would implement database.stateful and have a variable live above the start method.

Public Map<Id, Contact> conIdMap = new  Map<Id,Contact>();

Then I would check to see if the contact has been processed by checking if the id exists as a key in the map:

conIdMap.put(conId, con);

// do something

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .