How to Bulkify Queries for Related Records?

How to Bulkify Queries for Related Records?

On April 7, 2025, Posted by , In Salesforce Technical Questions, With Comments Off on How to Bulkify Queries for Related Records?

Question:

When processing a collection of records in Salesforce, such as Trigger.new, I need to query related records either directly or indirectly. However, querying records in a loop causes me to hit Salesforce’s query limits, which is not best practice. How can I avoid this problem and bulkify my code to efficiently query related data?

Answer:

To avoid hitting query limits and ensure that your code is bulkified, you can follow different approaches based on the type of relationship between the records you’re processing and the related records you need to query.
Below are the scenarios and solutions for efficiently querying related records:

Transform your career with CRS Info Solutions’ expert-led Salesforce training, Salesforce training in Hyderabad offering hands-on experience and in-depth knowledge to help you excel in the Salesforce ecosystem.

If the input records have a direct relationship with related records (via Lookup or Master-Detail fields), you can include related data directly in the initial query. This reduces the need for multiple queries in a loop.

For example, if you need to update the OwnerId of Opportunities based on their related Accounts, you can perform the following:

List<Id> oppIds = [...]; // List of Opportunity IDs
List<Opportunity> oppsToUpdate = new List<Opportunity>();

// Query Opportunities and their related Accounts in a single query
for (Opportunity opp : [SELECT Id, OwnerId, Account.OwnerId FROM Opportunity WHERE Id IN :oppIds]) {
    // Update the Opportunity OwnerId to match the Account's OwnerId if necessary
    if (opp.Account != null && opp.OwnerId != opp.Account.OwnerId) {
        opp.OwnerId = opp.Account.OwnerId;
        oppsToUpdate.add(opp);
    }
}

// Update the Opportunities
update oppsToUpdate;

Code explanation:
The code starts by defining a list of Opportunity IDs (oppIds) and a list to store Opportunities that need updating (oppsToUpdate). It then performs a single SOQL query to fetch Opportunities and their related Account’s OwnerId in one call. Inside the loop, it checks if the Opportunity’s OwnerId is different from the related Account’s OwnerId, and if so, updates the Opportunity’s OwnerId. Finally, the Opportunities that were modified are updated in Salesforce with the update DML operation.
This approach leverages the relationship between Opportunity and Account and eliminates the need for nested queries within a loop, ensuring that the code is bulkified and avoids hitting query limits.

In triggers, you’re working with records that include only the IDs of related records (like in a before insert trigger). To bulkify your code, collect the related record IDs first, then perform a single query to retrieve the related records outside of the loop.
Here’s an example:

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

// Collect Account IDs from Trigger.new
for (Opportunity opp : Trigger.new) {
    if (opp.AccountId != null) {
        accountIds.add(opp.AccountId);
    }
}

// Query Accounts in bulk
Map<Id, Account> accountsById = new Map<Id, Account>([SELECT Id, OwnerId FROM Account WHERE Id IN :accountIds]);

// Update Opportunities' OwnerId based on their related Account's OwnerId
for (Opportunity opp : Trigger.new) {
    if (opp.AccountId != null) {
        Account acc = accountsById.get(opp.AccountId);
        if (acc != null) {
            opp.OwnerId = acc.OwnerId;
        }
    }
}

Code explanation:
This code starts by collecting all AccountId values from the Opportunity records in Trigger.new and adds them to a Set<Id>. It then queries all Account records that match the collected AccountId values in a single SOQL query, storing the results in a Map<Id, Account> for efficient lookup. After the query, the code loops through the Opportunity records again, and for each one, it retrieves the associated Account from the Map and updates the Opportunity‘s OwnerId to match the Account‘s OwnerId. This approach ensures that the code is bulkified and avoids hitting the governor limit by making just one SOQL query.

If the related records are not directly related to the input records but can be identified by other fields, you may need to perform an “over-query” and filter the results in Apex.
Here’s an example where Tasks need to be associated with Cases based on shared fields like Priority and ContactId:

List<Task> tasks = [...]; // List of Task records

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

// Collect priorities and WhoIds from Tasks
for (Task task : tasks) {
    if (task.Priority != null) priorities.add(task.Priority);
    if (task.WhoId != null) whoIds.add(task.WhoId);
}

// Query Cases with matching Priority and ContactId
List<Case> cases = [SELECT Id, Priority, ContactId FROM Case WHERE Status != 'Closed' 
                    AND Priority IN :priorities AND ContactId IN :whoIds];

// Map Cases by a composite key (Priority, ContactId)
Map<Object[], Case> casesByKey = new Map<Object[], Case>();

for (Case caseRecord : cases) {
    casesByKey.put(new Object[]{caseRecord.Priority, caseRecord.ContactId}, caseRecord);
}

// Update Tasks' WhatId with the corresponding Case ID
for (Task task : tasks) {
    Case relatedCase = casesByKey.get(new Object[]{task.Priority, task.WhoId});
    if (relatedCase != null) {
        task.WhatId = relatedCase.Id;
    }
}

// Update the Tasks
update tasks;

Code explanation:
The code starts by collecting the Priority and WhoId values from the provided list of Task records and stores them in two Set collections. It then queries Case records that are not closed and have matching Priority and ContactId values, using the IN operator for efficient bulk retrieval. Next, it creates a Map using a composite key of Priority and ContactId to group Case records for quick lookup. Finally, it iterates over the Task records, updates their WhatId field with the corresponding Case ID, and performs a bulk update on the Task records.

Summing Up:

To bulkify queries for related records, it’s essential to minimize the number of SOQL queries by leveraging bulk queries that retrieve data for multiple records at once. Instead of querying related records inside loops, collect the necessary IDs from the input records (like Trigger.new) and query the related data in bulk using the IN clause or optimized queries. This can be done for directly related records by querying the parent-child relationships or for indirectly related records by gathering values to filter and match the related records. By using efficient data structures like Maps for quick lookups and processing, you can ensure the code adheres to Salesforce’s governor limits while maintaining optimal performance.

Salesforce Training in Hyderabad: Your Gateway to Success

Unlock your potential with our in-depth Salesforce training in Hyderabad . We offer comprehensive courses covering Admin, Developer, and AI tracks to ensure you gain the skills needed to thrive in the tech industry. With structured modules, hands-on projects, and expert-led sessions, you’ll be ready for certification and interviews.

Join our free demo class today and get a glimpse of our practical, industry-oriented training. Our experienced instructors provide personalized guidance, helping you grasp complex concepts easily. Whether you’re a beginner or looking to advance your career, our program prepares you for real-world challenges and career growth.

Join our free demo class today and take the first step toward a rewarding future! Enroll now for a free demo!!!

Comments are closed.