Trigger reads a single record from the context collection
ID |
apex.correctness.triggers_process_records_bulk |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Error Handling |
Language |
Apex |
Description
Reports a trigger body taking one record out of the context collection - Trigger.new[0], Trigger.old[0], Trigger.new.get(0) - instead of processing every record it was given.
Rationale
A trigger fires once per batch of up to 200 records, not once per record. Reading index zero works for as long as the only writes are one-at-a-time edits from the UI, and then quietly stops: a Data Loader import, a bulk API call, a flow that updates a list, a batch job - each of them hands the trigger 200 records and 199 of them are skipped with no error anywhere. The data is simply wrong, and it is wrong in the rows nobody looked at, which is why this is usually found months later during a reconciliation.
The following code illustrates the pattern detected by this rule:
trigger OpportunityTrigger on Opportunity (before insert, before update, after update) {
if (Trigger.isBefore) {
// FLAGGED: Trigger reads a single record from the context collection
Opportunity opp = Trigger.new[0];
OpportunityTriggerHandler.stamp(opp);
}
if (Trigger.isAfter && Trigger.isUpdate) {
// FLAGGED: Trigger reads a single record from the context collection
Opportunity previous = Trigger.old.get(0);
System.debug(previous.StageName);
}
if (Trigger.isBefore && Trigger.isUpdate) {
OpportunityTriggerHandler.handleBeforeUpdate(Trigger.new, Trigger.oldMap);
}
if (Trigger.isAfter && Trigger.isInsert) {
OpportunityTriggerHandler.handleAfterInsert(Trigger.new);
}
}
Remediation
Iterate the whole collection, or pass it to a handler that does: collect what needs changing into a List, query the related records once into a Map keyed by id, and issue one DML statement after the loop. Where the trigger really must act on one record, select it by a condition rather than by position.