Preface: this post is part of the Bulkify Your Code series.
You’ll often hear Apex developers say “bulkify your code!”
To bulkify your code means to combine repetitive tasks in Apex! It’s the only way to get around Governor Limits – especially our #1 most important limit!
Before I give you an example, it’s important to know exactly why you need to bulkify:
Bulkification Example: Let’s analyze the governor limit for DML statements.
This code is not bulkified and will go over the 150 DML statements limit:
// Remember that up to 200 records can be in Trigger.newfor (Opportunity opp : Trigger.new) {Task t = new Task(); t.Name = 'Give your prospect a free t-shirt'; t.WhatId = opp.Id;insert t; // You'll get an error after the 150th opp!}
This code is bulkified and will not hit any governor limits. It uses Lists and will only do one DML statement no matter how many records are in the trigger:
// Do an insert DML on all tasks at once using a ListList<Task> taskList = new List<Task>();for (Opportunity opp : Trigger.new) { Task t = new Task(); t.Name = 'Give your prospect a free t-shirt'; t.WhatId = opp.Id;taskList.add(t);}insert taskList; // Notice this is outside the loop
Next post: Introduction to Maps!
Hi
I want below trigger code to statisfy the bulk process also
trigger updateorderpricebookidofferrule1 on Orderitem (after insert)
{
list addinglist = new list();
for(orderitem ce:trigger.new)
{
list cc =[select Id,Status, pricebook2Id,Order_Qty_Multiplier__c ,(SELECT Id,Pricebook__c FROM Order_Pricebooks__r) from order where id =:ce.orderid];
{
for(order gt:cc)
{
for(Order_Pricebook__c lf:gt.Order_Pricebooks__r)
{
Offer_Rule__c =[SELECT id,Offer_Level__c,Pricebook2__c,Product_Group__c,Type__c FROM Offer_Rule__c where Offer_Level__c=:gt.Order_Qty_Multiplier__c and Pricebook2__r.id=:gt.pricebook2Id and Product_Group__c=:ce.Product_Group__c and Type__c=:ce.OrderType__c ];
Orderitem rs = new orderitem(id=ce.id);
rs.Offer_Rule__c = tt.id;
rs.Order_Pricebook__c = lf.id;
addinglist.add(rs);
}
}
}
}
update addinglist;
}
Hi
I want a trigger code for below code for bulkify the process code
trigger updateorderpricebookidofferrule1 on Orderitem (after insert)
{
list addinglist = new list();
for(orderitem ce:trigger.new)
{
list cc =[select Id,Status, pricebook2Id,Order_Qty_Multiplier__c ,(SELECT Id,Pricebook__c FROM Order_Pricebooks__r) from order where id =:ce.orderid];
{
for(order gt:cc)
{
for(Order_Pricebook__c lf:gt.Order_Pricebooks__r)
{
Offer_Rule__c =[SELECT id,Offer_Level__c,Pricebook2__c,Product_Group__c,Type__c FROM Offer_Rule__c where Offer_Level__c=:gt.Order_Qty_Multiplier__c and Pricebook2__r.id=:gt.pricebook2Id and Product_Group__c=:ce.Product_Group__c and Type__c=:ce.OrderType__c ];
Orderitem rs = new orderitem(id=ce.id);
rs.Offer_Rule__c = tt.id;
rs.Order_Pricebook__c = lf.id;
addinglist.add(rs);
}
}
}
}
update addinglist;
}
Hello David,
I have one doubt…
if i insert 1000 records using data loader and it makes one trigger to run, how the trigger.new will handle all the 1000 records because trigger.new can only handle 200 records?
Please explain
It’s because dataloader runs in batches. Batch size by default is 200 I believe. You can even change that in your settings.
Hi David,
I’ve been teaching myself APEX (actually started with your tutorials) and I’m trying to fix some bulkification problems in an apex class I wrote and came across this article. Good stuff. I was hoping you might be able to give me some direction in fixing it as I haven’t been able to figure out where I’ve gone wrong. Before I get into that though — interesting side note is that I was an SFDC consultant for Box shortly after you left and worked with our developers in enhancing the custom product selection tool you built. I worked with Kyle Wulff and Kyle Vermeer on that project.
Anyway, here’s my class. Do you see anything glaring?
public static void matchLead(list leads){
Set leadDomains = new Set();
Set leadCompanies = new Set();
for (Lead l : leads) {
if(l.Free_Email_Domain__c == false && l.IsMQL__c){
leadDomains.add(l.Email_Domain__c);
leadCompanies.add(l.Company);
}
}
Map accountDomainMap = new Map();
Map companyMap = new Map();
Map contactDomainMap = new Map();
try{
for(Account a : [SELECT Website_Domain__c, Name
FROM Account
WHERE Website_Domain__c in :leadDomains or Name in: leadCompanies]){
if (!accountDomainMap.containsKey(a.Website_Domain__c)) {
accountDomainMap.put(a.Website_Domain__c,a.Id);
}
if (!companyMap.containsKey(a.Name)) {
companyMap.put(a.Name,a.Id);
}
}
for(Contact c : [SELECT Email_Domain__c, AccountId
FROM Contact
WHERE Email_Domain__c in :leadDomains LIMIT 1]){
system.debug(c);
if (!contactDomainMap.containsKey(c.Email_Domain__c)) {
contactDomainMap.put(c.Email_Domain__c,c.AccountId);
}
}
}catch(Exception e){}
try{
for (Lead l : leads) {
Id accountProxy;
if(!accountDomainMap.isEmpty()){
Id a = accountDomainMap.get(l.Email_Domain__c);
accountProxy = a;
}
if(!companyMap.isEmpty() && accountProxy == null){
Id a = companyMap.get(l.Company);
accountProxy = a;
}
if(!contactDomainMap.isEmpty() && accountProxy == null){
Id a = contactDomainMap.get(l.Email_Domain__c);
accountProxy = a;
}
if (accountProxy == null) {
return;
}
l.RelatedtoAccount__c = accountProxy;
l.Status = ‘SQL’;
}
}catch(Exception e){}
}
You mentioned that many people that you interviewed don’t know about bulkifying code. Would you say that in the last few years, that % is still 90%, or has it gone down? Or is this for initial phone screen?
(How would someone be interviewing for a full-time job at a notable public company as a Salesforce Developer if they didn’t know this?)
Believe me it surprises me too =)
The number is definitely going down but it’s still more than 50%!
Definitely do NOT use that as your bar! =)
Hi David,
You made salesforce coding as easy as much possible. its really a fun to study from this site.
Thank You …
trigger try12 on BMCServiceDesk__Incident__c (after update)
{
for(BMCServiceDesk__Incident__c inc1:trigger.old)
for(BMCServiceDesk__Incident__c inc2:trigger.new)
for(BMCServiceDesk__Incident__c inc:trigger.new)
{
//retriving current incident id
BMCServiceDesk__Incident__c id1 =[select id from BMCServiceDesk__Incident__c where id in:trigger.new];
//task object and task id related to current incident whose status is not closed
LIST task = new LIST();
task=[select id,name,BMCServiceDesk__FKIncident__c,BMCServiceDesk__FKStatus__c from BMCServiceDesk__Task__c where BMCServiceDesk__FKIncident__c=:id1.id and BMCServiceDesk__FKStatus__c!=:’a3w2800000002wKAAQ’ ];
//problem object and problem id related to current incident whose status is not closed
//geting previous status and new current status
//checking for previous status is not equal to close/reswolved and current status is equal to resolved
if(inc1.BMCServiceDesk__FKStatus__c !=’a3w2800000002wKAAQ’|| inc1.BMCServiceDesk__FKStatus__c !=’a3w2800000002x9AAA’ && inc2.BMCServiceDesk__FKStatus__c ==’a3w2800000002x9AAA’ )
{
//updating staus of task to closed status whose incident status is resolved
for(BMCServiceDesk__Task__c taskupdate : task)
{
taskupdate.BMCServiceDesk__FKStatus__c = ‘a3w2800000002wKAAQ’;
update taskupdate;
}system.debug(‘hellllo u r in if’);
//probelm id
LISTproblem=[Select id,BMCServiceDesk__FKStatus__c from BMCServiceDesk__Problem__c where BMCServiceDesk__FKStatus__c!=:’a3w2800000002wKAAQ’ and (id in(select BMCServiceDesk__FKProblem__c from BMCServiceDesk__Incident_Problem_Link__c where BMCServiceDesk__FKIncident__c=:id1.id))];
LISTproblem1=new LIST();
for(BMCServiceDesk__Problem__c probupdate : problem)
{
probupdate.BMCServiceDesk__FKStatus__c = ‘a3w2800000002wKAAQ’;
problem1.add(probupdate);
//update probupdate;
}update problem1;
//change id
// List change = [Select id,BMCServiceDesk__FKStatus__c from BMCServiceDesk__Change_Request__c where BMCServiceDesk__FKStatus__c!=:’a3w2800000002wKAAQ’ and (id in(select BMCServiceDesk__FKChange__c from BMCServiceDesk__Incident_Change_Link__c where BMCServiceDesk__FKIncident__c=:id1.id))];
List change1 = new List();
for(BMCServiceDesk__Change_Request__c changeupdate1 : [Select id,BMCServiceDesk__FKStatus__c from BMCServiceDesk__Change_Request__c where BMCServiceDesk__FKStatus__c!=:’a3w2800000002wKAAQ’ and (id in(select BMCServiceDesk__FKChange__c from BMCServiceDesk__Incident_Change_Link__c where BMCServiceDesk__FKIncident__c=:id1.id))])
{
changeupdate1.BMCServiceDesk__FKStatus__c = ‘a3w2800000002wKAAQ’;
change1.add(changeupdate1);
//update changeupdate1;
}update change1;
}
}
}
THIS CODE IS GIVING SOQL 101 ERROR CAN ANY ONE HELP ME TO SOLVE THIS PROBLEM
You need to read Chapter 5 =)
You are using for loop inside for loop.And SOQL query inside for loop
Your website is really great. Thank you for all the hard work and folks like me are reaping the benefits of you easy-to-understand lessons. Keep up the good work!
Thank you!
Hi David
1st of all ‘Thanks a Lot’ for creating this website.
I am new to Apex. This is my 1st trigger. Here I am trying to do, if Contact owner name is different than Account Owner than Change the Contact Owner name to Account Owner name. Trigger that I created it works fine for single record but not for multiple record. How can I bulkify this code?
Class:
public class ContactOwnerChange {
List contacts;
List accounts;
public ContactOwnerChange(List contactList){
contacts = contactList;
}
public void contactOwnerToAccountOwner(){
for(Contact c : contacts){
Account a = [select OwnerId from Account where Id = :c.AccountId];
c.OwnerId = a.OwnerId;
}
}
}
Trigger:
trigger ConverContactOwnertri on Contact (before insert) {
List ContactOwnerToConvert = new List();
for (Contact c : Trigger.new) {
if (c.AccountId != NULL) {
ContactOwnerToConvert.add(c);
}
}
contactOwnerChange COC = new contactOwnerChange(ContactOwnerToConvert);
COC.contactOwnerToAccountOwner();
}
Thanks in advance.
Gotta take the SOQL query outside of the loop!
It’s pretty much the exact same method you’ll see as you go through the next few posts – just different variables =)
David
Hi David,
i have a question here.
This is regarding the trigger writtten by ‘GeorgeR’
Incase after update , I want to revoke the value of Join_Date__c in the old Contact………you do it.
How do it?
Lourd
This page should help you out!
https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_triggers_context_variables.htm
Yayyyyy! I made a working (but pointless) trigger!!! Woot!
Updated a field on the contact based on values in a task.
trigger removeEmailContactFlag on Task (after insert) {
//Create a set to store the contacts
List cons = new List();
System.debug(‘entering loop’);
for (task aTask : Trigger.new){
//go through each of the tasks in the trigger
//determine if the associated object is a contact (starts 003)
//and if the task subject starts with the correct text
System.debug(aTask);
System.debug(‘whoId starts with 003? :’ + String.valueOf(aTask.whoId).subString(0,3) );
if (String.valueOf(aTask.whoId).startsWith(‘003’)&& (aTask.Subject.subString(0,32).toLowerCase().equals(‘request: do not contact by email’))) {
//create a contact object and assign the contact associated to the task
Contact aContact = [SELECT ID, HasOptedOutOfEmail from Contact WHERE id =: aTask.whoId LIMIT 1 ];
//Update the email-optout field
aContact.HasOptedOutOfEmail = True;
System.debug(‘The value of a Contact is :’ + aContact);
//add it to the set
cons.add(aContact);
}
}
//update the List.
System.debug(‘cons : ‘ + cons);
if (cons.size()>0){
update cons;
}
}
YAY!!!!!!!!!!
Now go treat yourself to something nice!!!
Like a t-shirt!
hi david,
i am not t find the system audit
please tell where is it??
Gotta ask Salesforce to turn that on for ya =)
Hi David,
I have a doubt on Governor limit on SOQL. Its maximum 50,000 records. Suppose I am doing the query as
list acc= select id,name from account;
and there are suppose more than 50000 records in the org. Will it throw the error. or it will only consider first 50,000 records for the operation.
The corrected soql is: list acc= select id,name from account;
Every SOQL query always uses a WHERE clause to reduce the number of records =) There will always be some way to filter out a majority of records =)
Hi David, I’m trying to in-line-edit multiple records at a time and I’m getting the below error message:
caused by: System.DmlException: Insert failed. First exception on row 0 with id a1yZ0000000EQyxIAG; first error: INVALID_FIELD_FOR_INSERT_UPDATE, cannot specify Id in an insert call: [Id]: Trigger.createPunch: line 28, column 1
This error only occurs if the trigger is fired on more than one record at once.
What I’m getting from this is that the line items on a list have they’re own id, and for some reason this id is trying to populate as the id of the new record that I am trying to create. Is this correct? How can I get around this?
Thank you David,
Patrick
A few things you can do:
1. “upsert” chooses insert or update based on whether the record has already been created or not
2. Use this to debug your code! https://www.sfdc99.com/2014/02/22/debug-your-code-with-system-debug/
Hi David, please disregard this. My insert was inside the for loop even though I thought it was outside it.
Better formatting helped to see this.. Lots of learning.
Thank you David,
P
Its very helpful to understand how we need to bulkifying the code.!!!! Awesome
Hi David, This is the best of example and explanation I found on internet about clear cut demarcation of bulkification.Previously, I was always getting stuck @ this point.Excellent post and excellent precised explanation.
wewt!
Hi David,
Thanks for your amazing tutorials. I have actually been stuck in bulking up my code and will highly appreciate if you can help me fix this issue.
For less than 100 contacts in any Org (or somewhere in between 1 to 80) the trigger works absolutely fine. However, it is generating the SOQL limit exceed error if the contacts in any account are more than 100.
The trigger just change the field “relationship_category__r__c” to “Member – staff” or “Member – past” when the account type is changed to “member” or “member – past” respectively.
I have tried to bulk the code and update the contacts after the loop but still it’s generating error
Thanks very much for your help,
Muhammad
trigger Update_related_Contacts_On_Account_fieldchange on Account (after update) {
set ids=new set();
for(Account acc:trigger.new){
ids.add(acc.id);
}
map<id,List> acc_con_maps=new map<id,List>();
List accts=new List([select id,(select id,Relationship_Category_r__c from contacts) from Account where id=:ids]);
For(Account accts1:accts){
acc_con_maps.put(accts1.id,accts1.contacts);
}
list constoupdate=new list();
for(Account acc1:trigger.new){
if(acc_con_maps.containsKey(acc1.Id)&&acc_con_maps.get(acc1.id)!=null){
if(acc1.Type==’Member’){
for(contact con:acc_con_maps.get(acc1.Id)){
if(con.Relationship_Category_r__c !=’Member’){
con.Relationship_Category_r__c =’Member – Staff’;
constoupdate.add(con);
}
}
}
else if(acc1.type==’Member – Past’){
for(contact con1:acc_con_maps.get(acc1.Id)){
if(con1.Relationship_Category_r__c !=’Member – Past’){
con1.Relationship_Category_r__c =’Member – Past’;
constoupdate.add(con1);
}
}
}
}
}
update constoupdate;
}
Simplest way to tell if your code will hit the SOQL governor limit is to see if you have any SOQL queries inside any loops! Looks like this is definitely the case with your code. You gotta pull it outta your initial loop!
Hi David,
As you explained that 101 SOQL queries lead into governor limits.As given example,Account has 10 triggers & each one has 12 SOQL queries in it while updating the record,So obviously we can expect governor limit in this.So how can we overcome this?
Regards,
Sreeni
Gotta combine the triggers =) Also – it’s very rare for a single trigger to need 12 SOQL queries! Usually less than 5!
Hi David,
Received my Head First Java book today and have been reading your site everyday! Thank You for giving hope that the coding giant can be splayed :)
Question: Is it absolutely necessary to bulkify for every trigger I write? I am trying my hand on a trigger that will change the record type of the Event object when a certain profile creates/updates it on an Opportunity. I am planning to use a bunch of IF statements like you said in one of your posts. Any thoughts?
Thank you in advance!
Great question, really really great question!
Bulkify EVERYTHING! And I really mean everything! It is like the old saying – if something can go wrong, it will go wrong! I have been on the wrong end of that too many times =)
As a side benefit, the ability to bulkify is really the #1 differentiator for you as a dev. It’s how we separate the newbies from the vets. Think of it as job security =)
P.S. if you find yourself writing tons of IF statements, considering create a new custom object and iterating through it using a loop!
David
would u mind explaining how we do this “create a new custom object and iterating through it using a loop!” instead of writing IF staements
can we use map here
Hi David,
Thank you so much again, for all your hardwork on this site!!!
In the last line of your example:
update taskList; // Notice this is outside the loop
should it be update taskList;
OR insert taskList; ?
Thank you in advance for the clarification.
Great catch!!!!! It should definitely be “insert”!!
I’ve updated the post!
I have a trigger where I want to prevent a user from adding duplicate records in a custom object that is related to the standard Account object.
In that scenario I want to check whether any records exist by comparing the CreatedByID with the userinfo.getUserID().
I have a working trigger that does a SOQL query inside the for loop. However, based on what I’ve read on this website, this is a bad idea due to governor limits.
Is it possible to bulkify this?
trigger preventDuplicates on Target_Account__c (before insert) {
For (Target_Account__c tt : Trigger.new) {
Integer counter = [Select Count() FROM Target_Account__c WHERE Account__c = :tt.Account__c AND CreatedById = :userinfo.getUserId()];
if(counter > 0) {
String errorMessage = ‘Duplicate Top Target Found!’;
tt.addError(errorMessage);
}
}
}
Everything can be bulkified =)
But this can be done without code at all! Always go the no-code route unless you have no other option!
1. Create a custom text field “Duplicate Check”
2. In the field definition, make sure it’s marked unique (case sensitive)
3. Have a workflow always populate this field with $User.Id + Account__c
You’re good to go!
Hi David,
Thanks for this great resource. Really really helpful. I’ve been working on my first trigger, and having trouble properly bulkifying. What I’m trying to do is update the contact record when a child object is inserted or updated.
My final aim is actually more complicated than what I’m doing here, but even this simplified version doesn’t seem to be doing anything. When I test it in the sandbox, I don’t get any errors, but the join_date field is also not updated. Can you tell what I’m missing? (This is being tested in a sandbox)
Thank you!
George
trigger updateContact on Contact_child__c (before update, before insert) {
Set contactID = new Set();
for (Employment_Service__c client : Trigger.new) {
contactID.add(client.Contact__r.id);
}
List cList = new List();
for (Contact c : [SELECT id, Join_Date__c FROM Contact WHERE id IN :contactID]) {
c.Join_Date__c = Date.today();
cList.add(c);
}
update cList;
}
Oops, realized I forgot to edit the other name of the object the second time it comes up. This should be an accurate version of the code.
trigger updateContactES_playing on Contact_child__c (before update, before insert) {
Set contactID = new Set();
for (Contact_child__c client : Trigger.new) {
contactID.add(client.Contact__r.id);
}
List cList = new List();
for (Contact c : [SELECT id, Join_Date__c FROM Contact WHERE id IN :contactID]) {
c.Join_Date__c = Date.today();
cList.add(c);
}
update cList;
}
Try this – comments in line!
trigger updateContactES_playing on Contact_child__c (before update, before insert) {
Set<String> contactID = new Set<String>();
for (Contact_child__c client : Trigger.new) {
// client.Contact__c is better than client.Contact__r.Id!
// Both are exactly the same value except you need to query they second
// Technically client.Contact__r.Id is null right now
contactID.add(client.Contact__c);
}
List<Contact> cList = new List<Contact>();
for (Contact c : [SELECT id, Join_Date__c FROM Contact WHERE id IN :contactID]) {
c.Join_Date__c = Date.today();
cList.add(c);
}
update cList;
}
Thank you so much – really really helpful! Got this and the more complicated version that actually serves a purpose working!
Well done George – you learn quickly!!!!
Very happy to hear you’re enjoying the site George!
Will respond to the code question in your other comment =)
David