+ Start a Discussion
Neil KimNeil Kim 

Avoid 'Apex CPU time limit exceeded' with Batch Process

Hi exports. I encountered this error and have trouble with performance tuning.

I tried asyncronous approach, but Apex CPU time limit exceeded occurs continuously.
Anybody can make it more efficient?

Here is code and size of each Lists from static method is about 15,000.
Please help.
 
static List<Schedule__c> getScheduleList(){
		return [SELECT ID, Opportunity__c, StartTime__c FROM Schedule__c WHERE Result__c = 'Success' ORDER BY Opportunity__c];
	}

	global Database.QueryLocator start(Database.BatchableContext BC) {
		query = 'SELECT ID FROM CASE';
		return Database.getQueryLocator(query);
	}

   	global void execute(Database.BatchableContext BC, List<sObject> scope) {
   		List<Opportunity> allopps = getOppList();
   		List<Schedule__c> schedules = getScheduleList();

		List<Schedule__c> filtered = new List<Schedule__c>();
        Datetime dt;
        Boolean check;
        Boolean status;

        for ( Opportunity opp : allopps ){
       		dt = null;
 			check = false;
 			status = false;
       		for (Schedule__c schedule : schedules ){	
   				if( check == false && status == true )
   					break;

   				if( schedule.Opportunity__c == opp.id ){
   					check = true;
       				filtered.add(schedule);
       				if( dt == null || schedule.StartTime__c < dt)
       					dt = schedule.StartTime__c;

       				i++;
       			}
       			else if( check == true && status == false){
       				check = false;
       				status = true;
       			}
       		}

       		if( filtered.size() >= 1){
       			opp.SuccessDate__c = dt;
       			opp.StageName = 'Success';
       		}
       		else if( filtered.size() == 0 && opp.StageName == 'Success'){
				opp.SuccessDate__c = null;
				opp.StageName = 'Initial Stage';
       		}
        }

        System.debug('schedule size : ' + schedules.size());
        System.debug('size : ' + allopps.size() + ' count : ' + i);
        /*
		try{
			update allopps;
        }catch( Exception e){
       		System.debug('Error : ' + e.getMessage());
        }
        */
	}

Thanks in advance.
Best Answer chosen by Neil Kim
Jolly_BirdiJolly_Birdi
Hello @Neil 

Try this below Code:
 
static Map<Id,List<Schedule__c>> getScheduleList(){
        Map<Id,List<Schedule__c>> scheduleMap = new Map<Id,List<Schedule__c>>();
        List<Schedule__c> scheduleList;
        Id newId,LastId;
        for(Schedule__c sch : [SELECT ID, Opportunity__c, StartTime__c FROM Schedule__c WHERE Result__c = 'Success' ORDER BY Opportunity__c]){
            newId = sch.Opportunity__c;
            if(newId != LastId && LastId = null){
                scheduleList = new List<Schedule__c>();
            }
            else if(newId != LastId){
                scheduleMap.put(LastId,scheduleList);
                scheduleList = new List<Schedule__c>();
            }
            scheduleList.add(sch);
            LastId = newId;
        }
        scheduleMap.put(LastId,scheduleList);
		return scheduleMap;
	}
	global Database.QueryLocator start(Database.BatchableContext BC) {
		query = 'SELECT ID FROM CASE';
		return Database.getQueryLocator(query);
	}

   	global void execute(Database.BatchableContext BC, List<sObject> scope) {
   		List<Opportunity> allopps = getOppList();
   		Map<Id,List<Schedule__c>> schedulesMap = getScheduleList();

		List<Schedule__c> filtered = new List<Schedule__c>();
        Datetime dt;
        Boolean check;
        Boolean status;

        for ( Opportunity opp : allopps ){
       		dt = null;
 			check = false;
 			status = false;
            
            List<Schedule__c>  schedules =  schedulesMap.get(opp.Id);
       		for (Schedule__c schedule : schedules ){	
   				if(!check && status)
   					break;

                check = true;
                filtered.add(schedule);
                if( dt == null || schedule.StartTime__c < dt)
                    dt = schedule.StartTime__c;
                i++;
                
                if(check && !status){
       				check = false;
       				status = true;
       			}
       		}

       		if( filtered.size() >= 1){
       			opp.SuccessDate__c = dt;
       			opp.StageName = 'Success';
       		}
       		else if( filtered.size() == 0 && opp.StageName == 'Success'){
				opp.SuccessDate__c = null;
				opp.StageName = 'Initial Stage';
       		}
        }

        System.debug('schedule size : ' + schedules.size());
        System.debug('size : ' + allopps.size() + ' count : ' + i);
        /*
		try{
			update allopps;
        }catch( Exception e){
       		System.debug('Error : ' + e.getMessage());
        }
        */
	}



Mark this as best answer if you find it positive.
Thanks
Jolly Birdi

All Answers

Neil KimNeil Kim
1 List query is omitted. There is another static method for querying List.
Jolly_BirdiJolly_Birdi
Hello @Neil

Use Map instead of List for all opps and Schedules, It will provide you the quick results.

Thanks
Jolly Birdi.
Neil KimNeil Kim
Hi @Jolly_Birdi. Thanks but I cannot understand your comment.

Of cource, I consider using Map. However, I cannot imagine it can be reduce CPI usage.

If possible, please write some code making map and loop for this map with my variables?
Jolly_BirdiJolly_Birdi
Hello @Neil 

Try this below Code:
 
static Map<Id,List<Schedule__c>> getScheduleList(){
        Map<Id,List<Schedule__c>> scheduleMap = new Map<Id,List<Schedule__c>>();
        List<Schedule__c> scheduleList;
        Id newId,LastId;
        for(Schedule__c sch : [SELECT ID, Opportunity__c, StartTime__c FROM Schedule__c WHERE Result__c = 'Success' ORDER BY Opportunity__c]){
            newId = sch.Opportunity__c;
            if(newId != LastId && LastId = null){
                scheduleList = new List<Schedule__c>();
            }
            else if(newId != LastId){
                scheduleMap.put(LastId,scheduleList);
                scheduleList = new List<Schedule__c>();
            }
            scheduleList.add(sch);
            LastId = newId;
        }
        scheduleMap.put(LastId,scheduleList);
		return scheduleMap;
	}
	global Database.QueryLocator start(Database.BatchableContext BC) {
		query = 'SELECT ID FROM CASE';
		return Database.getQueryLocator(query);
	}

   	global void execute(Database.BatchableContext BC, List<sObject> scope) {
   		List<Opportunity> allopps = getOppList();
   		Map<Id,List<Schedule__c>> schedulesMap = getScheduleList();

		List<Schedule__c> filtered = new List<Schedule__c>();
        Datetime dt;
        Boolean check;
        Boolean status;

        for ( Opportunity opp : allopps ){
       		dt = null;
 			check = false;
 			status = false;
            
            List<Schedule__c>  schedules =  schedulesMap.get(opp.Id);
       		for (Schedule__c schedule : schedules ){	
   				if(!check && status)
   					break;

                check = true;
                filtered.add(schedule);
                if( dt == null || schedule.StartTime__c < dt)
                    dt = schedule.StartTime__c;
                i++;
                
                if(check && !status){
       				check = false;
       				status = true;
       			}
       		}

       		if( filtered.size() >= 1){
       			opp.SuccessDate__c = dt;
       			opp.StageName = 'Success';
       		}
       		else if( filtered.size() == 0 && opp.StageName == 'Success'){
				opp.SuccessDate__c = null;
				opp.StageName = 'Initial Stage';
       		}
        }

        System.debug('schedule size : ' + schedules.size());
        System.debug('size : ' + allopps.size() + ' count : ' + i);
        /*
		try{
			update allopps;
        }catch( Exception e){
       		System.debug('Error : ' + e.getMessage());
        }
        */
	}



Mark this as best answer if you find it positive.
Thanks
Jolly Birdi
This was selected as the best answer
Neil KimNeil Kim
@Jolly Birdi
Thank you. I got your approach using Map and it works.

Thanks!