SOQL Query Inside a For Loop (The Classic)
“Wrote a SOQL query inside a for loop. 200 records came in. 200 queries fired. Governor said no.”
What Happened
Early in my Apex career, I wrote a trigger that queried the parent Account for each Contact in a bulk insert. I tested it with one record. Beautiful. Then a data migration pushed 200 Contacts at once. 200 SOQL queries. Governor limit hit at 101. The entire batch failed, the migration rolled back, and I spent the rest of the night rewriting the trigger while the data team waited.
The Wrong Way
trigger ContactTrigger on Contact (before insert) {
for (Contact c : Trigger.new) {
// SOQL INSIDE THE LOOP - NEVER DO THIS
Account acc = [SELECT Id, BillingState
FROM Account
WHERE Id = :c.AccountId];
c.Region__c = acc.BillingState;
}
// At 101 records: System.LimitException:
// Too many SOQL queries: 101
}The Right Way
trigger ContactTrigger on Contact (before insert) {
// Collect all Account IDs first
Set<Id> accountIds = new Set<Id>();
for (Contact c : Trigger.new) {
if (c.AccountId != null) {
accountIds.add(c.AccountId);
}
}
// ONE query for ALL accounts
Map<Id, Account> accountMap = new Map<Id, Account>(
[SELECT Id, BillingState FROM Account
WHERE Id IN :accountIds]
);
// Now loop and use the map
for (Contact c : Trigger.new) {
Account acc = accountMap.get(c.AccountId);
if (acc != null) {
c.Region__c = acc.BillingState;
}
}
}The Lesson
Bulkify or die. Collect IDs first, query once, use a Map. This is Apex 101 and I still catch experienced devs doing it in code reviews.
Enjoyed this? Get more like it.
Glen's Musings — AI, investing, and building things. Occasional. Free.
More Apex Mistakes
Test Class That Only Tests One Record
Wrote a test class that inserted 1 record. Got 100% coverage. Deployed. Trigger failed on bulk operations.
Read moreAnnoyingMixed DML: Setup and Non-Setup Objects in the Same Transaction
Inserted a User and an Account in the same transaction. Salesforce said absolutely not.
Read morePainfulAll Logic in the Trigger Body (No Handler Class)
Wrote 300 lines of logic directly in the trigger. Six months later, nobody could maintain it. Including me.
Read more