Bulk Apex Triggers
Bulk Apex Triggers
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.
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.
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
//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.
//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.
//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.
//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.
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.
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.
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.
//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)];
}
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 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.
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
//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