function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
LBartonLBarton 

Why is Apex CPU Time Limit exceeded for this trigger?

I have a rather simple trigger. It updates a custom Account field when the opportunity is updated. I don't know why I get an error about a CPU time limit?

"Apex trigger: UpdateLateStageOppField caused an unexpected exception. SystemLimitException: Apex CPU time limit exceeded. "

When you answer this please, could you actually type in some coding changes?  I hardly use Apex and am really not familiar with it. General advice won't be sufficient. Thanks for your help!

trigger UpdateLateStageOppField on Opportunity (after delete, after insert, after update) {

try{
    //the trigger will update the account's SAM Marketing Customer field
    //and also the Late_Stage_Opps__c, too
   
    if (! trigger.isDelete) {
   
   List<Id> oppIds = new List<Id>() ;
   List<Id> AccountIds = new List<Id>() ;
   List<Account> AcctToUpdate = new List<Account>() ;
  
            for (opportunity op : trigger.New)
      {
    oppIds.add(op.Id);
       AccountIds.add(op.AccountId);
      }
  
      Map<Id,Account> acctMap = new Map<Id,Account>([select id, SAM_Marketing_Customer__c, name, Closed_Won_Opps__c, Type from Account where id in :AccountIDs]);
   Map<Id,Opportunity> oppMap2 = new Map<Id,Opportunity>([Select id, StageName, Opportunity__c from Opportunity where Accountid in :AccountIDs]) ;    
      
      for (opportunity op : trigger.New){

        //Find the account for this opportunity which is being update
        Account oAccount =  acctMap.get(op.AccountId);
  Opportunity StageOpportunities = oppMap2.get(op.Id);
 
  string stagelist;
         stagelist = '';
         if (oppMap2.isEmpty()){  //No opportunities for that account
    stagelist = '';
         }
         else {
          stagelist = '';
          System.debug('In Else StageList ');
          for (Opportunity stageMap: oppMap2.values()) {
           system.debug ('Oppor. ' + StageMap.Opportunity__c);
        if (stageMap.StageName=='Closed Won' || stageMap.StageName=='Negotiation/Review' || stageMap.StageName=='Perception Analysis' || stageMap.StageName=='Value Proposition'){
      stagelist +=  stageMap.Opportunity__c + ',';
        }
          }
   
         }
         oAccount.Late_Stage_Opps__c = stagelist;         
         AcctToUpdate.add(oAccount);
       }
       update AcctToUpdate;
   }

if (trigger.isDelete) {
   
   
   List<Id> oppIds = new List<Id>() ;
   List<Id> AccountIds = new List<Id>() ;
   List<Account> AcctToUpdate = new List<Account>() ;
  
            for (opportunity op : trigger.Old)
      {
    oppIds.add(op.Id);
       AccountIds.add(op.AccountId);
      }
  
   Map<Id,Account> acctMap = new Map<Id,Account>([select id, SAM_Marketing_Customer__c, name, Closed_Won_Opps__c, Type from Account where id in :AccountIDs]);
   Map<Id,Opportunity> oppMap2 = new Map<Id,Opportunity>([Select id, StageName, Opportunity__c from Opportunity where Accountid in :AccountIDs]) ;    
      
      for (opportunity op : trigger.Old){

        //Find the account for this opportunity which is being update
        Account oAccount =  acctMap.get(op.AccountId);
        //Opportunity SamOpportunity = oppMap.get(op.Id);
  Opportunity StageOpportunities = oppMap2.get(op.Id);
 
  string stagelist;
       
         if (oppMap2.isEmpty()){  //No opportunities for that account
    stagelist = '';
         }
         else {
          stagelist = '';
           System.debug('in stage list');
          for (Opportunity stageMap: oppMap2.values()) {
            System.debug('in values list');
        if (stageMap.StageName=='Closed Won' || stageMap.StageName=='Negotiation/Review' || stageMap.StageName=='Perception Analysis' || stageMap.StageName=='Value Proposition'){
      stagelist +=  stageMap.Opportunity__c + ',';
        }
          }
   
         }
         oAccount.Late_Stage_Opps__c = stagelist;     
         AcctToUpdate.add(oAccount);
       }

     update AcctToUpdate;   
   }
}
catch (Exception e ){
            System.debug('Create customer field trigger exception ' + e.getMessage());
           
      }
}



Best Answer chosen by LBarton
ShashankShashank (Salesforce Developers) 
I have rewritten the (!trigger.isdelete) part. I couldn't  manage to remove the nested FOR loop, but this should take lesser processing time by reducing the number of iterations. Please try it out and if it works fine, you can adopt it for the isDelate part as well:

trigger UpdateLateStageOppField on Opportunity (after delete, after insert, after update) {
    try{
       set<Id> AccountIds = new set<Id>();
       set<Id> oppIds = new set<Id>();
       List<Account> AcctToUpdate = new List<Account>() ;
        if (! trigger.isDelete) {
            for(opportunity o:trigger.new){
                AccountIds.add(o.accountId);
                oppIds.add(o.Id);
            }
            for(account a:[select Late_Stage_Opps__c,
                             (
                             select stagename,opportunity__c 
                             from opportunities 
                             where stageName IN ('closed won', 'negotiation/review','perception analysis','value proposition')
                             ) 
                           from account 
                           where Id IN :AccountIds]){
                string stagelist = '';
                account ac = new account();
                ac.Id = a.Id;
                if(a.opportunities.size()>0){ 
                    for(opportunity op:a.opportunities){
                        stagelist += op.opportunity__c + ',';
                    }
                }
                    ac.Late_Stage_Opps__c = stagelist;
                    acctToUpdate.add(ac);                
            }
        if(AcctToUpdate.size()>0) update AcctToUpdate;
        }
    }
    catch (Exception e ){
            System.debug('Create customer field trigger exception ' + e.getMessage());           
    }
}


All Answers

ShashankShashank (Salesforce Developers) 
Handling this governor limit exception would need some experience with APEX coding.

We need to determine which part of the code might be causing this and in what circumstances this error occurs (like during a bulk upload or single record update) and should be able to replicate it. Once we know how to easily replicate it, we can use the following line after every couple of lines and find out in the debug logs which part of the code is faulty and make that code asynchronous (by using @future annotation).

system.debug(limits.getcputime());
LBartonLBarton
Thanks for your response!  I appreciate your help.
The trigger works with just a few records. I have updated up to 100 records at once.  Here is a list of tests that I have done in updating a group of opportunities.

 200 records did not work   - 18.74 secs.
150 records did not work - 17 seconds
125 records did not work 16 secs.
100 records worked  -  11 secs.

AS you see, it is quite slow, but doesn't seem to do all that much.I am simply updating a custom field.  I will add the system.debug line that you have suggested.

Is there a way to do this with a formula field?  Would it be possible to write a formula that creates a comma-delimited list of the opportunities in an account? that's all I need.

LBartonLBarton
I need the list to include only certain opportunities that have specific stage names and not all the opportunities.
ShashankShashank (Salesforce Developers) 
I see a FOR loop within a FOR loop. This should definitely be the reason. We should find a way to avoid the nested FOR loop.


ShashankShashank (Salesforce Developers) 
Can you explain your exact requirement in detail? I will try to rewrite the trigger? I'm afraid we cannot do it using formulas.
LBartonLBarton
 This is the requirement.  There is a new custom field in Accounts called "Late Stage Opps" which is a long text area field.

The field label is Late Stage Opps and the field name is Late_Stage_Opps.  The API Name is Late_Stage_Opps__c.

When an opportunity is updated then this new Accounts custom field is updated with a comma delimited list of opportunities which have only the following stages:  

closed won, negotiation/review, perception analysis or value proposition.

 

For instance if there were three opportunities in a certain Account:
MobiLab   closed/Won
Accelero   qualification
IatriScan  Negotiation/Review

Then the new custom field would contain "MobiLab,IatriScan"
If Accelero later had a stage of "closed won" then the field would be updated with a delimited list of three names.

Is this clear enough?  Thanks!


ShashankShashank (Salesforce Developers) 
I have rewritten the (!trigger.isdelete) part. I couldn't  manage to remove the nested FOR loop, but this should take lesser processing time by reducing the number of iterations. Please try it out and if it works fine, you can adopt it for the isDelate part as well:

trigger UpdateLateStageOppField on Opportunity (after delete, after insert, after update) {
    try{
       set<Id> AccountIds = new set<Id>();
       set<Id> oppIds = new set<Id>();
       List<Account> AcctToUpdate = new List<Account>() ;
        if (! trigger.isDelete) {
            for(opportunity o:trigger.new){
                AccountIds.add(o.accountId);
                oppIds.add(o.Id);
            }
            for(account a:[select Late_Stage_Opps__c,
                             (
                             select stagename,opportunity__c 
                             from opportunities 
                             where stageName IN ('closed won', 'negotiation/review','perception analysis','value proposition')
                             ) 
                           from account 
                           where Id IN :AccountIds]){
                string stagelist = '';
                account ac = new account();
                ac.Id = a.Id;
                if(a.opportunities.size()>0){ 
                    for(opportunity op:a.opportunities){
                        stagelist += op.opportunity__c + ',';
                    }
                }
                    ac.Late_Stage_Opps__c = stagelist;
                    acctToUpdate.add(ac);                
            }
        if(AcctToUpdate.size()>0) update AcctToUpdate;
        }
    }
    catch (Exception e ){
            System.debug('Create customer field trigger exception ' + e.getMessage());           
    }
}


This was selected as the best answer
LBartonLBarton
Thanks! I get an error that  field is not writeable (Account.Id) for line 21. What needs to be changed to remove the error?
ShashankShashank (Salesforce Developers) 
I didn't get the error in my test org. Did you try with only the code I provided without any addittional code?
LBartonLBarton
I did use exactly what you provided. I cut and pasted the code.  I changed the line to  ac = a; and that worked for me.  The field seems to be updating correctly. 
LBartonLBarton
The code worked!  I was able to update 200 records at once, which is the most another employee said that he would be updating in a batch. Thanks.  How do I let your supervisor know how helpful you have been! I will close this case after somebody else does some testing as well.
ShashankShashank (Salesforce Developers) 
Hi,

You can leave your feedback as a comment here and my manager will be able to see it :)

Thanks,
Shashank
LBartonLBarton
Shashank, you have solved the issue of my trigger not updating a large batch of reords at once!  You have also been quick to reply and had great advice.  I appreciate all you have done!
LBartonLBarton
This is the final version. It is nearly all of what Shashank has developed but I had to have an else statement that had the stagelist = '' for bulk updates, inserts and deletes because otherwise the stagelist list of opportunities grew to be too many and were not appropriate. 
trigger UpdateLateStageOppField on Opportunity (after delete, after insert, after update) {
	
	try{
    //the trigger will update the account's SAM Marketing Customer field
    //and also the Late_Stage_Opps__c, too

        if (! trigger.isDelete) {
        	
    
       		set<Id> AccountIds = new set<Id>();
       		set<Id> oppIds = new set<Id>();
       		List<Account> AcctToUpdate = new List<Account>() ;
               	
            for(opportunity o:trigger.new){
                AccountIds.add(o.accountId);
                oppIds.add(o.Id);
            }
            for(account a:[select Late_Stage_Opps__c,
                             (
                             select stagename,opportunity__c 
                             from opportunities 
                             where stageName IN ('closed won', 'negotiation/review','perception analysis','value proposition')
                             ) 
                           from account 
                           where Id IN :AccountIds]){
                string stagelist = '';
                account ac = new account();
                ac = a;
                //ac.Id = a.Id;
                if(a.opportunities.size()>0){ 
                    for(opportunity op:a.opportunities){
                        stagelist += op.opportunity__c + ',';
                    }
                }
                else{
                	stagelist='';
                }
                
                    ac.Late_Stage_Opps__c = stagelist;
                    acctToUpdate.add(ac);                
            }
        if(AcctToUpdate.size()>0) update AcctToUpdate;
        }

	
	if (trigger.isDelete) {
    
       		set<Id> AccountIds = new set<Id>();
       		set<Id> oppIds = new set<Id>();
       		List<Account> AcctToUpdate = new List<Account>() ;
               	
            for(opportunity o:trigger.old){
                AccountIds.add(o.accountId);
                oppIds.add(o.Id);
            }
            for(account a:[select Late_Stage_Opps__c,
                             (
                             select stagename,opportunity__c 
                             from opportunities 
                             where stageName IN ('closed won', 'negotiation/review','perception analysis','value proposition')
                             ) 
                           from account 
                           where Id IN :AccountIds]){
                string stagelist = '';
                account ac = new account();
                ac = a;
                //ac.Id = a.Id;
                if(a.opportunities.size()>0){ 
                    for(opportunity op:a.opportunities){
                        stagelist += op.opportunity__c + ',';
                    }
                }
                else {
                	stagelist='';
                }
                    ac.Late_Stage_Opps__c = stagelist;
                    acctToUpdate.add(ac);                
            }
        if(AcctToUpdate.size()>0) update AcctToUpdate;
        }  
  	}

catch (Exception e ){
            System.debug('Create customer field trigger exception ' + e.getMessage());
            
      }
 }