0% found this document useful (0 votes)
252 views9 pages

Bulk Apex Triggers

Bulk Apex triggers are designed to efficiently process large numbers of records by minimizing database operations. They provide collections like Trigger.new and Trigger.old to access old and new record values in bulk. Effective bulk trigger patterns include using maps to process data, minimizing queries and DML, and using batch Apex for large record volumes. Best practices for bulk SOQL include querying related records for all trigger context records with a single query rather than querying separately for each record.

Uploaded by

teja ganesh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
252 views9 pages

Bulk Apex Triggers

Bulk Apex triggers are designed to efficiently process large numbers of records by minimizing database operations. They provide collections like Trigger.new and Trigger.old to access old and new record values in bulk. Effective bulk trigger patterns include using maps to process data, minimizing queries and DML, and using batch Apex for large record volumes. Best practices for bulk SOQL include querying related records for all trigger context records with a single query rather than querying separately for each record.

Uploaded by

teja ganesh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 9

Bulk Apex Triggers

Bulk Trigger Design Patterns


Bulk Apex triggers are designed to handle the efficient processing of large numbers of records in the
Salesforce database. When a trigger is fired on a large number of records, the trigger logic should be
designed to minimize the number of queries, DML statements, and callouts made to the database.
This is especially important because the number of database operations that can be performed
within a single Apex transaction is limited by governor limits.

To help with this, Apex provides several collections and methods specifically for use in bulk triggers:

 Trigger.new: This is a list of the new versions of the sObject records. This collection is only
available in before triggers.
 Trigger.newMap: This is a map of IDs to the new versions of the sObject records. This
collection is only available in before triggers.
 Trigger.old: This is a list of the old versions of the sObject records. This collection is only
available in after triggers.
 Trigger.oldMap: This is a map of IDs to the old versions of the sObject records. This
collection is only available in after triggers.
 Database.BatchableContext: This is an interface that is used to pass information to and from
a batch Apex class. This is only available in batch Apex classes that implement the
Database.Batchable interface.
 By using these collections and methods, you can write efficient bulk triggers that can handle
large numbers of records without exceeding governor limits.

Efficient processing of large numbers of records is done using Bulk Trigger

1. Use Trigger.new and Trigger.newMap in before triggers and Trigger.old and Trigger.oldMap
in after triggers: These collections allow you to efficiently access and process the records
that are being inserted, updated, deleted, or undeleted.
2. Use a Map to store and process data: Maps are efficient data structures that can be used to
store and process large amounts of data. For example, you can use a Map to store the old
and new values of a field and then iterate through the Map to update the records as needed.
3. Minimize the number of queries and DML statements: To avoid exceeding governor limits, it
is important to minimize the number of queries and DML statements that are made to the
database. One way to do this is to use Maps to store and process data, as mentioned above.
4. Use Database.QueryLocator and Database.Stateful: These interfaces allow you to use SOQL
queries to retrieve large amounts of data and process it efficiently in Apex.
5. Use batch Apex: If you need to process a large number of records that cannot be efficiently
handled by a single Apex transaction, you can use batch Apex to divide the processing into
smaller chunks. This can help to avoid exceeding governor limits.

Operating on Record Sets


There are several ways to operate on record sets in Apex. Here are a few examples:
 Use a for loop: You can use a for loop to iterate through a list of records and perform
operations on each record.
 Use the map method: You can use the map method to apply a function to each element of a
list and return a new list of the results
 Use the forEach method: You can use the forEach method to apply a function to each
element of a list.

The following trigger assumes that only one record caused the trigger to fire. This trigger doesn’t
work on a full record set when multiple records are inserted in the same transaction

trigger MyTriggerNotBulk on Account(before insert) {


Account a = Trigger.new[0];
a.Description = 'New description';
}

//EXPLANATION START

 The MyTriggerNotBulk trigger is defined on the Account object and is set to fire before
records are inserted. It uses the Trigger.new collection to access the first (and only) record
that is being inserted. It then updates the Description field of that record and assigns it a
new value of 'New description'.
 Because this trigger is not defined as bulk, it will only be called once for each record that is
inserted. If multiple records are inserted at the same time, this trigger will be called once for
each record.

//EXPLANATION END

This example is a modified version of MyTrigger. It uses a for loop to iterate over all available
sObjects. This loop works if Trigger.new contains one sObject or many sObjects.

trigger MyTriggerBulk on Account(before insert) {


for(Account a : Trigger.new) {
a.Description = 'New description';
}
}

//EXPLANATION START

 The MyTriggerBulk trigger is defined on the Account object and is set to fire before records
are inserted. It uses a for loop to iterate through the Trigger.new collection, which contains
all of the records that are being inserted. For each record, it updates the Description field
and assigns it a new value of 'New description'.
 Because this trigger is defined as bulk, it will be called once for all records that are inserted
at the same time. This allows the trigger to efficiently process large numbers of records and
avoid exceeding governor limits.
//EXPLANATION END
Performing Bulk SOQL

 It is often more efficient to use a single SOQL query to retrieve a large number of records,
rather than using multiple smaller queries. This is known as performing bulk SOQL.
 SOQL queries can be powerful. You can retrieve related records and check a combination of
multiple conditions in one query. By using SOQL features, you can write less code and make
fewer queries to the database. Making fewer database queries helps you avoid hitting query
limits, which are 100 SOQL queries for synchronous Apex or 200 for asynchronous Apex.

The following trigger shows a SOQL query pattern to avoid.

trigger SoqlTriggerNotBulk on Account(after update) {


for(Account a : Trigger.new) {
// Get child records for each account
// Inefficient SOQL query as it runs once for each account!
Opportunity[] opps = [SELECT Id,Name,CloseDate
FROM Opportunity WHERE AccountId=:a.Id];
// Do some other processing
}
}

//EXPLANATION START

 The SoqlTriggerNotBulk trigger is defined on the Account object and is set to fire after
records are updated. It uses a for loop to iterate through the Trigger.new collection, which
contains all of the updated Account records.
 For each Account record, the trigger performs a SOQL query to retrieve all related
Opportunity records. This SOQL query is inefficient because it runs once for each updated
Account record. If multiple Account records are updated at the same time, this trigger will
run the SOQL query multiple times.
 Performing multiple SOQL queries in this way can lead to inefficient code and potentially
exceed governor limits, particularly if a large number of records are being processed. It is
generally best practice to perform bulk SOQL, which uses a single query to retrieve a large
number of records, rather than using multiple smaller queries.

//EXPLANATION END

This example is a modified version of the previous one and shows a best practice for running SOQL
queries.

trigger SoqlTriggerBulk on Account(after update) {


// Perform SOQL query once.
// Get the accounts and their related opportunities.
List<Account> acctsWithOpps =
[SELECT Id,(SELECT Id,Name,CloseDate FROM Opportunities)
FROM Account WHERE Id IN :Trigger.new];
// Iterate over the returned accounts
for(Account a : acctsWithOpps) {
Opportunity[] relatedOpps = a.Opportunities;
// Do some other processing
}
}

//EXPLANATION START

 The SoqlTriggerBulk trigger is defined on the Account object and is set to fire after records
are updated. It performs a single SOQL query to retrieve all updated Account records and
their related Opportunity records. The query uses a subquery to retrieve all Opportunity
records that are related to each Account record.
 The results of the SOQL query are stored in a list of Account objects, which includes all
related Opportunity records for each Account. The trigger then uses a for loop to iterate
through the Account records and access the related Opportunity records for each account.
 Performing a single SOQL query in this way can be more efficient than using multiple smaller
queries, particularly if a large number of records are being processed. It is generally best
practice to perform bulk SOQL, which uses a single query to retrieve a large number of
records, rather than using multiple smaller queries.

//EXPLANATION END

Alternatively, if you don’t need the account parent records, you can retrieve only the opportunities
that are related to the accounts within this trigger context. This list is specified in the WHERE clause
by matching the AccountId field of the opportunity to the ID of accounts in Trigger.new: WHERE
AccountId IN :Trigger.new. The returned opportunities are for all accounts in this trigger context and
not for a specific account. This next example shows the query used to get all related opportunities.

trigger SoqlTriggerBulk on Account(after update) {


// Perform SOQL query once.
// Get the related opportunities for the accounts in this trigger.
List<Opportunity> relatedOpps = [SELECT Id,Name,CloseDate FROM Opportunity
WHERE AccountId IN :Trigger.new];
// Iterate over the related opportunities
for(Opportunity opp : relatedOpps) {
// Do some other processing
}
}

You can reduce the previous example in size by combining the SOQL query with the for loop in one
statement: the SOQL for loop. Here is another version of this bulk trigger using a SOQL for loop.

trigger SoqlTriggerBulk on Account(after update) {


// Perform SOQL query once.
// Get the related opportunities for the accounts in this trigger,
// and iterate over those records.
for(Opportunity opp : [SELECT Id,Name,CloseDate FROM Opportunity
WHERE AccountId IN :Trigger.new]) {
// Do some other processing
}
}
Performing Bulk DML

 Bulk DML refers to the practice of performing Data Manipulation Language (DML) operations
on a large number of records at once, rather than on each record individually. DML
operations include insert, update, upsert, delete, and undelete.
 Using bulk DML can be more efficient than performing DML operations on each record
individually, particularly if a large number of records are being processed. It can also help to
avoid exceeding governor limits, which are limits on the amount of database processing that
can be performed in a single Apex transaction.
 When performing DML calls in a trigger or in a class, perform DML calls on a collection of
sObjects when possible. Performing DML on each sObject individually uses resources
inefficiently. The Apex runtime allows up to 150 DML calls in one transaction.

This trigger performs an update call inside a for loop that iterates over related opportunities. If
certain conditions are met, the trigger updates the opportunity description. In this example, the
update statement is inefficiently called once for each opportunity. If a bulk account update
operation fired the trigger, there can be many accounts. If each account has one or two
opportunities, we can easily end up with over 150 opportunities. The DML statement limit is 150
calls.

trigger DmlTriggerNotBulk on Account(after update) {


// Get the related opportunities for the accounts in this trigger.
List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM
Opportunity
WHERE AccountId IN :Trigger.new];
// Iterate over the related opportunities
for(Opportunity opp : relatedOpps) {
// Update the description when probability is greater
// than 50% but less than 100%
if ((opp.Probability >= 50) && (opp.Probability < 100)) {
opp.Description = 'New description for opportunity.';
// Update once for each opportunity -- not efficient!
update opp;
}
}
}

This next example shows how to perform DML in bulk efficiently with only one DML call on a list of
opportunities. The example adds the Opportunity sObject to update to a list of opportunities
(oppsToUpdate) in the loop. Next, the trigger performs the DML call outside the loop on this list
after all opportunities have been added to the list. This pattern uses only one DML call regardless of
the number of sObjects being updated.

trigger DmlTriggerBulk on Account(after update) {


// Get the related opportunities for the accounts in this trigger.
List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM
Opportunity
WHERE AccountId IN :Trigger.new];
List<Opportunity> oppsToUpdate = new List<Opportunity>();
// Iterate over the related opportunities
for(Opportunity opp : relatedOpps) {
// Update the description when probability is greater
// than 50% but less than 100%
if ((opp.Probability >= 50) && (opp.Probability < 100)) {
opp.Description = 'New description for opportunity.';
oppsToUpdate.add(opp);
}
}
// Perform DML on a collection
update oppsToUpdate;
}

Bulk Design Pattern in Action: Example of Getting Related Records

List<Account> toProcess = null;


switch on Trigger.operationType {
when AFTER_INSERT {
// do stuff
}
when AFTER_UPDATE {
// do stuff
}
}

//EXPLANATION START

 This code defines a list of Account objects called toProcess and initializes it to null. It then
uses a switch statement to perform different actions based on the value of
Trigger.operationType.
 The switch statement evaluates the value of Trigger.operationType, which is a string that
specifies the type of operation that caused the trigger to fire. If the value is AFTER_INSERT,
the code block following the when clause for AFTER_INSERT is executed. If the value is
AFTER_UPDATE, the code block following the when clause for AFTER_UPDATE is executed. If
neither of these values are encountered, no action is taken.
 The code blocks following the when clauses are not specified in this example, so it's not clear
what actions would be taken in each case. However, it's possible that the toProcess list
would be populated with Account objects in these code blocks.

//EXPLANATION END
For all account inserts, we simply assign the new accounts to the toProcess list:

when AFTER_INSERT {
toProcess = Trigger.New;
}

For updates, we must figure out which existing accounts in this trigger don’t already have a related
opportunity. Because this trigger is an after trigger, we can query the affected records from the
database. Here’s the SOQL statement, the results of which we assign to the toProcess list.

when AFTER_UPDATE {
toProcess = [SELECT Id,Name FROM Account
WHERE Id IN :Trigger.New AND
Id NOT IN (SELECT AccountId FROM Opportunity WHERE AccountId
in :Trigger.New)];
}

AddRelatedRecord trigger with Trigger.OperationType

trigger AddRelatedRecord on Account(after insert, after update) {


List<Opportunity> oppList = new List<Opportunity>();
// Add an opportunity for each account if it doesn't already have one.
// Iterate over accounts that are in this trigger but that don't have opportunities.
List<Account> toProcess = null;
switch on Trigger.operationType {
when AFTER_INSERT {
// All inserted Accounts will need the Opportunity, so there is no need to perform the
query
toProcess = Trigger.New;
}
when AFTER_UPDATE {
toProcess = [SELECT Id,Name FROM Account
WHERE Id IN :Trigger.New AND
Id NOT IN (SELECT AccountId FROM Opportunity WHERE AccountId
in :Trigger.New)];
}
}
for (Account a : toProcess) {
// Add a default opportunity for this account
oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
StageName='Prospecting',
CloseDate=System.today().addMonths(1),
AccountId=a.Id));
}
if (oppList.size() > 0) {
insert oppList;
}
}
//EXPLANATION START

 This Apex trigger, called "AddRelatedRecord," is designed to create a new opportunity for an
account if the account doesn't already have an opportunity. The trigger is set to fire after an
account is inserted or updated.
 The trigger begins by initializing an empty list of opportunities called "oppList." It then
defines a list of accounts called "toProcess" that will be processed by the trigger. This list is
set to null initially.
 The trigger then enters a switch statement that checks the operation type of the trigger. If
the operation type is an "AFTER_INSERT," the trigger sets "toProcess" to the list of all
accounts that are in the trigger. If the operation type is an "AFTER_UPDATE," the trigger sets
"toProcess" to a list of accounts that are in the trigger, but do not have any related
opportunities. This list is obtained by querying the Account object and using a subquery to
exclude accounts that have a related opportunity.
 The trigger then iterates over the accounts in "toProcess" using a for loop. For each account,
the trigger adds a new opportunity to "oppList." The opportunity is given a default name
based on the name of the account, a stage name of "Prospecting," a close date that is one
month from today's date, and an account ID that matches the ID of the current account.
 After the for loop has completed, the trigger checks the size of "oppList." If the size is greater
than 0, it inserts all of the opportunities in the list. If the size is 0, no opportunities are
inserted.

//EXPLANATION END

Hands-on Challenge

Create a Bulk Apex trigger

Create a bulkified Apex trigger that adds a follow-up task to an opportunity if its stage is Closed Won.
Fire the Apex trigger after inserting or updating an opportunity.

Create an Apex trigger:

 Name: ClosedOpportunityTrigger
 Object: Opportunity
 Events: after insert and after update
 Condition: Stage is Closed Won
 Operation: Create a task:
o Subject: Follow Up Test Task
o WhatId: the opportunity ID (associates the task with the opportunity)
 Bulkify the Apex trigger so that it can insert or update 200 or more opportunities

Solution

trigger ClosedOpportunityTrigger on Opportunity (after insert, after update) {


// Create a list to hold the follow-up tasks
List<Task> followUpTasks = new List<Task>();
// Iterate over the opportunities that were inserted or updated
for (Opportunity opp : Trigger.new) {
// Check if the opportunity's stage is "Closed Won"
if (opp.StageName == 'Closed Won') {
// Create a new follow-up task for the opportunity
Task followUpTask = new Task(
Subject = 'Follow Up Test Task',
WhatId = opp.Id
);
// Add the follow-up task to the list
followUpTasks.add(followUpTask);
}
}

// Insert the follow-up tasks in bulk


if (followUpTasks.size() > 0) {
insert followUpTasks;
}
}

//EXPLANATION START

This trigger first creates an empty list to hold the follow-up tasks. Then, it iterates over the
opportunities that were inserted or updated and checks if their stage is "Closed Won". If it is, it
creates a new follow-up task for the opportunity and adds it to the list. Finally, it inserts all the
follow-up tasks in bulk using a single DML operation. This allows the trigger to handle large numbers
of opportunities efficiently.

//EXPLANATION END

You might also like