How Do I Bulkify Queries for Related Records in Apex?

How Do I Bulkify Queries for Related Records in Apex?

On December 4, 2025, Posted by , In Admin Tutorial,Salesforce Admin, By , , With Comments Off on How Do I Bulkify Queries for Related Records in Apex?
Bulkify Queries for Related Records in Apex

How can I properly bulkify my Apex code when I need to query records that are directly or indirectly related to a collection of input records, such as Trigger.new or a list of SObjects? When I place queries inside loops, Salesforce throws SOQL limit errors. What are the best patterns to avoid these issues and handle different scenarios where related record data is needed?

Bulkification is essential in Salesforce Apex development because the platform heavily enforces governor limits. One of the most common mistakes made by developers is writing SOQL queries inside loops. This may work in sandbox tests with one or two records but fails in production when multiple records are processed simultaneously. To avoid this, Salesforce requires us to restructure logic so that all necessary records are queried once, in bulk, and then matched to the input records efficiently.

There are three main scenarios you must understand: when the input records already contain the relationship, when you are inside a trigger and do not control the query, and when the relationship is indirect and no direct lookup exists.

1. Direct Relationship Querying When You Control the Input Query

When the records you are processing have lookup fields to the related objects, and you are controlling the initial query, the simplest solution is to query all the required relationship fields at the same time. Salesforce SOQL supports parent-child traversal, so you can query parent fields directly.

Here is a scenario: You want to update each Opportunity’s OwnerId to match the OwnerId of its related Account.

You can write:

List<Id> oppIds = ...;
List<Opportunity> oppsToUpdate = new List<Opportunity>();

for (Opportunity opp : [
    SELECT Id, OwnerId, Account.OwnerId
    FROM Opportunity
    WHERE Id IN :oppIds
]) {
    if (opp.Account != null && opp.OwnerId != opp.Account.OwnerId) {
        opp.OwnerId = opp.Account.OwnerId;
        oppsToUpdate.add(opp);
    }
}

update oppsToUpdate;

In this approach, you query everything only once. Notice that when multiple Opportunities reference the same Account, Salesforce automatically manages the shared relationship object, which saves memory and avoids redundant queries.

2. Directly Related Records Inside a Trigger

Inside a trigger, you cannot decide what fields Salesforce loads for you. Only relationship IDs are available, not full parent records. That means you must extract the related record IDs, query all of them in a single SOQL query, and then map them back to the triggering records.

Example: Before inserting Opportunities, you need to set the OwnerId using the related Account’s OwnerId.

First, gather all Account IDs referenced by the triggering Opportunities:

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

for (Opportunity opp : Trigger.new) {
    if (opp.AccountId != null) {
        accountIds.add(opp.AccountId);
    }
}

Now query all required Accounts in one SOQL call:

for (Opportunity opp : Trigger.new) {
    if (opp.AccountId != null) {
        Account parentAcc = accsById.get(opp.AccountId);
        opp.OwnerId = parentAcc.OwnerId;
    }
}

This pattern is fundamental in trigger-based development. Querying once and using a map reduces both SOQL usage and CPU time by avoiding nested loops.

3. Indirect Relationships and Composite Key Matching

In some cases, there is no direct relationship field connecting your input records to the related records you need. Instead, a combination of fields must be used to find matching data.

This is the most complex scenario, and it often requires “over-querying”. That means querying all records that might be relevant based on input fields, then narrowing down the exact match inside Apex.

Consider this example: For new Tasks being created, you want to set WhatId to an open Case where the Case’s Priority matches the Task’s Priority and the Case’s ContactId matches the Task’s WhoId.

First gather all matching values from the input:

Set<String> priorities = new Set<String>();
Set<Id> whoIds = new Set<Id>();

for (Task task : tasks) {
    priorities.add(task.Priority);
    whoIds.add(task.WhoId);
}

Now over-query Cases:

List<Case> caseList = [
    SELECT Id, Priority, ContactId
    FROM Case
    WHERE Status != 'Closed'
    AND Priority IN :priorities
    AND ContactId IN :whoIds
];

Since a combination of two fields needs to form a match, we build a composite key to map the Cases efficiently. Apex does not have built-in tuples, but using an Object[] array works well:

Map<Object[], Case> casesByCompositeKey = new Map<Object[], Case>();

for (Case c : caseList) {
    casesByCompositeKey.put(new Object[]{c.Priority, c.ContactId}, c);
}

Now loop over the tasks and match using the composite key:

for (Task t : tasks) {
    Case matchingCase =
        casesByCompositeKey.get(new Object[]{t.Priority, t.WhoId});

    if (matchingCase != null) {
        t.WhatId = matchingCase.Id;
    }
}

This pattern is powerful and works for multi-field key matching of any length. It avoids string concatenation and relies on the fact that Object[] correctly supports equality and hashing.

Conclusion

To bulkify SOQL queries when dealing with related records—either directly via lookup fields or indirectly through field combinations—you must use one of these strategies:

  1. Query related fields directly when you can control the initial SOQL query.
  2. In triggers, gather relationship IDs first, query once, then map results efficiently.
  3. For indirect relationships, over-query using field values from input records and build composite keys for fast matching.

Each approach ensures you stay within Salesforce governor limits and maintain scalable Apex code.

Enroll for Career-Building Salesforce Training with 100% Money Back Guarantee

Our Salesforce Course is structured to give you a deep understanding of the Salesforce platform, equipping you with the essential skills to excel in the CRM industry. The curriculum covers vital modules like Salesforce Admin, Developer, and AI, integrating theoretical knowledge with hands-on practice. By working on real-world projects and practical assignments, you will gain the expertise needed to solve complex business challenges using Salesforce solutions. Our experienced trainers ensure you develop both technical proficiency and industry insights to succeed in the Salesforce ecosystem.

In addition to technical skills, our Salesforce Training in New York provides personalized mentorship, certification assistance, and interview preparation to boost your career prospects. You will have access to extensive study materials, live project experience, and dedicated support throughout your learning journey. By the end of the course, you will be fully prepared for certification exams and equipped with the practical problem-solving abilities that employers value. Begin your Salesforce journey with us and explore a world of exciting career opportunities. Enroll in a Free Demo today!

Comments are closed.