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
WalidWalid 

Bulkify a Trigger

Hi there,

I am new to Triggrs, and trying to bulkify the below Trigger. The Trigger will create a Task when stage is Closed Won. It will also check if the Opportunity already has a Task with the same Subject, and it won't create the tasks in this case.

The Trigger is:
trigger ClosedOpportunityTrigger on Opportunity (after insert, after update) {
	
    //create a list of tasks to be added
    List<Task> listTasks = new List<Task>();
    
    for (Opportunity opp : Trigger.New){
 		//create a list of Tasks already created with same subject on opp
        List<Task> existingTasks = [SELECT Id
                                      FROM Task
                                     WHERE WhatId = :opp.Id
                                       AND Subject = 'Follow Up Test Task'];
        
        //add new task only if stage is Closed Won and no existing task with same subject on opp
        if ((opp.StageName == 'Closed Won') && (existingTasks.size() == 0) ){
        Task newTask    = new Task();
        newTask.WhatId  = opp.Id; 
        newTask.Subject = 'Follow Up Test Task';
        newTask.Status  = 'Not started';
        newTask.OwnerId = opp.OwnerId;
        listTasks.add(newTask); 
        }
    }
    insert listTasks;
}
I want to move the SOQL query our of the For loop, but if I do it, it will look for Tasks with this subject throughout the whole Trigger.New opportunity list. How can I move it out and keep count of each opportunity having this task with this subject?

Thanks.
Walid
 
bob_buzzardbob_buzzard
So your bulkification issue is that you have a SOQL query inside a for loop. You'll need to move this outside, query for all opportunities in one go and then process the query results to group the existing tasks by opportunity. Should be something like:

 
// get all tasks for all opportunities being processed by the trigger
List<Task> allTasks = [SELECT Id
                       FROM Task
                       WHERE WhatId IN :Trigger.New
                         AND Subject = 'Follow Up Test Task'];

// group them by opportunity id and store in a map
Map<Id, List<Task>> tasksByOppId=new Map<Id, List<Task>>();
for (Task tsk : allTasks)
{
   Id oppId=tsk.whatId;
   List<Task> oppTasks=tasksByOppId.get(oppId);
   if (null==oppTasks)
   {
      oppTasks=new List<Task>();
      tasksByOppId.put(oppId, oppTasks);
   }
   oppTasks.add(task);
}

for (Opportunity opp : trigger.New)
{
   List<Task> existingTasks=tasksByOppId.get(opp.id);
   if (null!=existingTasks)
   {
      // add new task only if ....
   }


 
Vijay NagarathinamVijay Nagarathinam
HI,

Try the below it will work for bulk load.
 
trigger ClosedOpportunityTrigger on Opportunity (after insert, after update) {
	
    //create a list of tasks to be added
    List<Task> listTasks = new List<Task>();
	Set<Id> oppId = new Set<Id>();
	List<Task> existingTasks = new List<Task>();
	for(Opportunity oppRec : Trigger.new){
		oppId.add(oppRec.Id);
	}
    
	if(oppId.size() > 0){
		existingTasks = [SELECT Id FROM Task WHERE WhatId IN: oppId AND Subject = 'Follow Up Test Task'];
	}
	
	if(existingTasks.isEmpty()){
		for (Opportunity opp : Trigger.New){
			//add new task only if stage is Closed Won and no existing task with same subject on opp
			if ((opp.StageName == 'Closed Won')){
			Task newTask    = new Task();
			newTask.WhatId  = opp.Id; 
			newTask.Subject = 'Follow Up Test Task';
			newTask.Status  = 'Not started';
			newTask.OwnerId = opp.OwnerId;
			listTasks.add(newTask); 
			}
		}
	}
    insert listTasks;
}

Thanks,
Vijay

 
WalidWalid
@Bob, thanks for the code, but it seems it has some bugs:
1- Line 11 (Id oppId=tsk.whatId;): tsk.whatId should be added in the SOQL right? So, the SOQL would become: SELECT Id, whatId... ?
2- Line 12 (List<Task> oppTasks=tasksByOppId.get(oppId);): Isn't tasksByOppId still empty atthis stage? What will asksByOppId.get(oppId); return?
3- Line 18: what did you add? oppTasks.add(task);

Regards,
Walid
WalidWalid
@Vijay.

This is what I was doing, but in your code, existingTasks will search for these tasks across ALL opportunities. and the task will not be added unless ALL opportunities don't have such task. So in other words, if Trigger.New is 10 opportunities, and 1 of them has this task, none of these 10 will have a task added.

I guess a map is the way to go.
Amit Chaudhary 8Amit Chaudhary 8
Hi ,

Please try below code.
trigger ClosedOpportunityTrigger on Opportunity (after insert, after update) 
{

	Set<Id> oppId = new Set<Id>();
	 = new List<Task>();
	for(Opportunity oppRec : Trigger.new)
	{
		if( oppRec.StageName == 'Closed Won' )
		{
			oppId.add(oppRec.Id);
		}	
	}
    
	if(oppId.size() > 0)
	{
		List<Task> existingTasks = [SELECT Id,WhatId FROM Task WHERE WhatId IN: oppId AND Subject = 'Follow Up Test Task'] ;
		Map<String , Task > mapOppWiseTask = new Map<String , Task >();
		List<Task> listTasks = new List<Task>();
		
		for( Task t : existingTasks )
		{
			mapOppWiseTask.put( t.WhatId , t );
		}
	
		for (Opportunity opp : Trigger.New)
		{
			if ( opp.StageName == 'Closed Won' && mapOppWiseTask.containsKey(opp.id) == false )
			{
				Task newTask    = new Task();
				newTask.WhatId  = opp.Id; 
				newTask.Subject = 'Follow Up Test Task';
				newTask.Status  = 'Not started';
				newTask.OwnerId = opp.OwnerId;
				listTasks.add(newTask); 
			}
		}
		if(listTasks.size() > 0 )
		{
			insert listTasks;
		}
	}
}

Let us know if this will help you
 
Sachin Ks 5Sachin Ks 5
I cleared this challenge with this simple code. Hope it helps you.
 
trigger ClosedOpportunityTrigger on Opportunity (after insert, after update) {
    List<Task> taskList = new List<Task>();
    for(Opportunity opp:Trigger.new){
        if(opp.StageName=='Closed Won') {
            tasklist.add(new Task(Subject='Follow Up Test Task'));
        }
    }   insert taskList;
}