Fixing the Infamous “Maximum SOQL Limit” Error in Apex

|
| By Webner

The “Maximum SOQL Limit” error, often accompanied by the message System.LimitException: Too many SOQL queries: 101 is one of the most common and frustrating problems faced by Salesforce developers. It signifies a violation of a fundamental governing rule (governor limit) designed to ensure that no single piece of code monopolizes resources on the shared multi-tenant platform.

Salesforce imposes a limit of 100 SOQL queries per synchronous Apex transaction. This error usually occurs not in simple use cases, but when a piece of code is executed in a context involving multiple records (e.g., mass data uploads, bulk updates, or running code on every record in a list).

The Root Cause: Lack of Bulkification

The error almost always stems from a lack of bulkification. This means the code is executing a SOQL query inside a for loop that iterates over a list of records.

The Non-Bulkified (Bad) Code Pattern

Consider a scenario where a trigger attempts to find the corresponding Account name for every Contact being inserted:

Apex

// Code that will fail if more than 100 contacts are inserted

trigger ContactTrigger on Contact (before insert) {

    for (Contact c : Trigger.new) {

        // BAD PRACTICE: SOQL query inside the loop

        Account acc = [SELECT Name FROM Account WHERE Id = :c.AccountId LIMIT 1];

        // … do something with the account name

    }

}

If 101 contacts are inserted in a single transaction, the loop executes 101 times, and the query is executed 101 times. Since the governor limit is 100, the transaction immediately fails on the 101st iteration.

The Solution: Apex Bulkification Using Collections

The fix is to restructure the code to execute a single SOQL query outside the loop, collecting the necessary data identifiers first. This is achieved using Apex collections—specifically, Sets and Maps.

The Bulkified (Best Practice) Code Pattern

To fix the example above, we use a two-step process:

    • Collect IDs into a Set (Pre-query): Iterate through the incoming records once to gather all necessary foreign key IDs (AccountId in this case) into a Set<Id>. Sets automatically handle duplicates and are highly efficient.
    • Execute Single Query: Run one single SOQL query using the IN operator against the collected Set of IDs.
    • Use a Map for Retrieval (Post-query): Store the results of the single query in a Map<Id, SObject> for fast, efficient retrieval by ID within the second loop.

Apex

// Code that handles up to 200 records or more (safely)

trigger ContactTrigger on Contact (before insert) {

    // 1. Collect all necessary IDs into a Set

    Set<Id> accountIds = new Set<Id>();

    for (Contact c : Trigger.new) {

        if (c.AccountId != null) {

            accountIds.add(c.AccountId);

        }

    }

    // 2. Execute a single SOQL query to retrieve ALL related records

    //    We now use only ONE query against the 100-query limit.

    Map<Id, Account> accountMap = new Map<Id, Account>(

        [SELECT Id, Name FROM Account WHERE Id IN :accountIds]

    );

    // 3. Iterate again, using the Map to access related data efficiently

    for (Contact c : Trigger.new) {

        Account relatedAcc = accountMap.get(c.AccountId);

        if (relatedAcc != null) {

            // … do something with the account name using the map

            // No queries are executed here!

        }

    }

}

Strategic Takeaways for Developers

The Map and Set pattern is the standard for bulkification. By applying this simple, three-step logic, you adhere to the governor limits and create high-performance, scalable Apex code capable of handling transactions involving hundreds of records.

    • Always Assume Bulk: When writing any Apex code (especially triggers), always assume that 200 records will be processed simultaneously, as this is the maximum batch size for DML operations.
    • The Golden Rule: Never place a SOQL query or a DML statement (insert, update, delete) inside an iteration loop.
    • Alternative Solutions: For very complex or resource-intensive operations, consider moving the logic to an asynchronous context (like a Queueable or Batch Apex), which resets the governor limits and grants 100 new SOQL queries.

Leave a Reply

Your email address will not be published. Required fields are marked *