Roll Up Summary Trigger To Update Child Records Count And Total Sum Of Values Of Child Records On Parent Record Using Maps

Roll up summary trigger To Update Child Records Count and total Sum of values of child records on Parent Record using Maps

, ,

In this blog post, we will explore an example that shows how to roll up child counts using maps collection. It will also demonstrate how to sum values on a parent record. This example applies to lookup relationships but you can use the same with master-detail also.

Example Scenario:

Imagine a company uses Salesforce to track projects and their associated deliverables. Each deliverable is a child record (B__c), and projects are parent records (A__c). Each project includes fields for tracking the number of deliverables (Record_Count__c) and the cumulative value of those deliverables (Total__c).

The system requires real-time updates to these project fields whenever deliverables are added, updated, deleted, or moved between projects. For example:

  1. Adding Deliverables: When a deliverable is added to a project, the project’s deliverable count and total value should increase.
  2. Updating Deliverables: If the deliverable’s value changes, the original and new projects must reflect the changes. Similarly, if it is reassigned to a different project, this change must also be updated in both projects.
  3. Deleting Deliverables: When a deliverable is deleted, the project’s deliverable count and total value must decrease accordingly.
  4. Restoring Deliverables: If a deleted deliverable is restored, the related project’s fields must be updated to include it again.

This automation ensures that project managers have accurate, up-to-date information without manual effort. The RollUpBOnA trigger manages these updates seamlessly.

Trigger Overview:

This Salesforce Apex trigger keeps parent project records (A__c) up-to-date by rolling up information from child deliverables (B__c).

What it Updates :

  • Record_Count__c: Number of child records associated with the parent.
  • Total__c: Sum of the Value__c field from all child records.

When it Runs:

The trigger fires when child records (B__c) are:

  • ✨ Created (Insert)
  • 🔄 Updated
  • ❌ Deleted
  • ↩️ Undeleted

How It Works:

Here is a step-by-step explanation of what the RollUpBOnA trigger does:

  1. Declare a Set for Parent Record IDs:
    The trigger begins by declaring a set. It uses parentIds to store the IDs of the parent A__c records. These records need to be updated.
  2. Identify Relevant Parent IDs for Insert or Undelete Events:
    • During insert or undelete events, the trigger loops through the Trigger.new collection.
    • If a child record (B__c) has a non-null parent (A_No__c), the parent ID is added to the parentIds set.
  3. Identify Relevant Parent IDs for Delete Events:
    • During delete events, the trigger loops through the Trigger.old collection.
    • If a deleted child record has a non-null parent, the parent ID is added to the parentIds set.
  4. Handle Updates to Parent IDs or Values:
    • During update events, the trigger compares the old (Trigger.oldMap) and new (Trigger.new) versions of each B__c record.
    • If the parent ID (A_No__c) changes, both the old and new parent IDs are added to the set.
    • If the parent ID remains the same but the value (Value__c) field changes, add the parent ID to the set.
  5. Exit if No Parent IDs to Update:
    If no parent IDs are collected in the set, the trigger exits. There is no further processing.
  6. Aggregate Data:
    Two maps are used:
    • childRecordCount: Tracks the number of deliverables for each parent.
    • childValuesSum: Tracks the total Value__c for each parent.
    • An efficient SOQL query retrieves child record data and populates these maps.
  7. Retrieve and Prepare Parent Records for Update:
    • The trigger queries all A__c records in the parentIds set, retrieving their Record_Count__c and Total__c fields.
    • It updates these fields based on the values stored in the maps (childRecordCount and childValuesSum). If a parent ID is not found in the maps, the corresponding field is set to 0.
  8. Collect Parent Records to Update:
    Each updated parent record is added to a list (parentsToUpdate) for processing.
  9. Perform the Update Operation:
    Finally, the trigger updates all the A__c records in the parentsToUpdate list. This ensures their Record_Count__c and Total__c fields are accurate. They are also kept up-to-date.
Sample Code-
//Trigger to update the Total__c and Record_Count__c fields of parent records A__c
// when child records B__c are inserted, updated, undeleted or deleted.
trigger RollUpBOnA on B__c (
after insert,
after update,
after delete,
after undelete
) {
// Declare a set to store the Ids of the parent object A__c
Set<Id> parentIds = new Set<Id>();
// Handle Insert and Undelete events
if (Trigger.isInsert || Trigger.isUndelete) {
// Loop through new child records
for (B__c child : Trigger.new) {
// If the child has a parent, add parent ID to the set
if (child.A_No__c != null) {
parentIds.add(child.A_No__c);
}
}
}
// Handle Delete event
if (Trigger.isDelete) {
// Loop through old child records (before deletion)
for (B__c child : Trigger.old) {
// If the child had a parent, add parent ID to the set
if (child.A_No__c != null) {
parentIds.add(child.A_No__c);
}
}
}
// Handle Update event
if (Trigger.isUpdate) {
// Loop through new child records
for (B__c child : Trigger.new) {
// Get the old version of the child record
B__c oldChild = Trigger.oldMap.get(child.Id);
// Check if the parent reference changed
Boolean isParentChanged = child.A_No__c != oldChild.A_No__c;
// Check if the value field changed
Boolean isValueChanged = child.Value__c != oldChild.Value__c;
if (isParentChanged) {
// If parent changed, add both old and new parent IDs
if (oldChild.A_No__c != null) parentIds.add(oldChild.A_No__c);
if (child.A_No__c != null) parentIds.add(child.A_No__c);
} else if (isValueChanged) {
// If only value changed, add current parent ID
if (child.A_No__c != null) parentIds.add(child.A_No__c);
}
}
}
// If no parent IDs to update, exit early
if (parentIds.isEmpty()) return;
// Maps to store rollup results: record count and value sum per parent
Map<Id, Integer> childRecordCount = new Map<Id, Integer>();
Map<Id, Decimal> childValuesSum = new Map<Id, Decimal>();
// Query all child records related to affected parents
for (B__c child : [
SELECT A_No__c, Value__c
FROM B__c
WHERE A_No__c IN :parentIds
]) {
Id parentId = child.A_No__c;
// Increment record count for this parent
childRecordCount.put(parentId,
(childRecordCount.containsKey(parentId) ? childRecordCount.get(parentId) : 0) + 1
);
// Add child's value to the parent's total sum (handle nulls)
Decimal currentTotal = childValuesSum.containsKey(parentId)
? childValuesSum.get(parentId) : 0;
Decimal childValue = child.Value__c != null ? child.Value__c : 0;
childValuesSum.put(parentId, currentTotal + childValue);
}
// Prepare list of parent records to update
List<A__c> parentsToUpdate = new List<A__c>();
// Query parent records that need updating
for (A__c parent : [
SELECT Id, Record_Count__c, Total__c FROM A__c WHERE Id IN :parentIds
]) {
// Set record count field (default to 0 if no children)
parent.Record_Count__c = childRecordCount.containsKey(parent.Id)
? childRecordCount.get(parent.Id) : 0;
// Set total value field (default to 0 if no children)
parent.Total__c = childValuesSum.containsKey(parent.Id)
? childValuesSum.get(parent.Id) : 0;
// Add parent to update list
parentsToUpdate.add(parent);
}
// Update all affected parent records in a single DML operation
if(!parentsToUpdate.isEmpty()) {
update parentsToUpdate;
}
}
view raw RollUpBOnA.java hosted with ❤ by GitHub
/**
* This is a test class for the RollUpBOnA trigger.
* It includes various test methods to cover different scenarios:
* 1. Inserting a B with an A
* 2. Deleting a B
* 3. Undeleting a B
* 4. Updating a B's A
*/
@isTest
public class RollUpBOnA_Test {
/*
* This method sets up the test data for the test class.
* It creates two test A records that will be used in the test methods.
*/
@TestSetup
static void makeData() {
insert new List<A__c> {
new A__c(Name = 'Test1'),
new A__c(Name = 'Test2')
};
}
/*
* This method tests the insertion of a B with an A.
* It verifies that the Record Count and Total field on the A is updated correctly.
*/
@isTest
static void testInsertBWithA() {
A__c a = [SELECT Id FROM A__c WHERE Name = 'Test1' LIMIT 1];
Test.startTest();
insert new B__c(Name = 'Test B', Value__c = 10, A_No__c = a.Id);
Test.stopTest();
A__c updated = [SELECT Record_Count__c, Total__c FROM A__c WHERE Id = :a.Id];
System.assertEquals(1, updated.Record_Count__c, 'B count should be 1');
System.assertEquals(10, updated.Total__c, 'Total should be 10');
}
/*
* This method tests the deletion of a B.
* It verifies that the Record Count and Total field on A is updated correctly.
*/
@isTest
static void testDeleteB() {
A__c a = [SELECT Id FROM A__c WHERE Name = 'Test1'];
B__c b = new B__c(Name = 'Test B', Value__c = 5, A_No__c = a.Id);
insert b;
Test.startTest();
delete b;
Test.stopTest();
A__c updated = [SELECT Record_Count__c, Total__c FROM A__c WHERE Id = :a.Id];
System.assertEquals(0, updated.Record_Count__c, 'B count should be 0 after delete');
System.assertEquals(0, updated.Total__c, 'Total should be 0 after delete');
}
/*
* This method tests the undelete operation on a B.
* It verifies that the Record Count and Total field on the A is updated correctly after undelete.
*/
@isTest
static void testUndeleteB() {
A__c a = [SELECT Id FROM A__c WHERE Name = 'Test1'];
B__c b = new B__c(Name = 'Test B', Value__c = 15, A_No__c = a.Id);
insert b;
delete b;
Test.startTest();
undelete b;
Test.stopTest();
A__c updated = [SELECT Record_Count__c, Total__c FROM A__c WHERE Id = :a.Id];
System.assertEquals(1, updated.Record_Count__c, 'B count should be 1 after undelete');
System.assertEquals(15, updated.Total__c, 'Total should be 15 after undelete');
}
/*
* This method tests updating the parent A of a B.
* It verifies that the Record Count and Total field on both A records is updated correctly.
*/
@isTest
static void testUpdateBAssociation() {
List<A__c> aList = [SELECT Id FROM A__c ORDER BY Name];
B__c b = new B__c(Name = 'Test B', Value__c = 20, A_No__c = aList[0].Id);
insert b;
Test.startTest();
b.A_No__c = aList[1].Id;
update b;
Test.stopTest();
List<A__c> updated = [SELECT Record_Count__c, Total__c FROM A__c WHERE Id IN :aList ORDER BY Name];
System.assertEquals(0, updated[0].Record_Count__c, 'Old A should have 0 Bs');
System.assertEquals(0, updated[0].Total__c, 'Old A should have 0 total');
System.assertEquals(1, updated[1].Record_Count__c, 'New A should have 1 B');
System.assertEquals(20, updated[1].Total__c, 'New A should have total 20');
}
/*
* This method tests updating the Value on B.
* It verifies that the Total field on A record is updated correctly.
*/
@isTest
static void testUpdateBValue() {
A__c a = [SELECT Id FROM A__c WHERE Name = 'Test1'];
B__c b = new B__c(Name = 'Test B', Value__c = 20, A_No__c = a.Id);
insert b;
Test.startTest();
b.Value__c = 30;
update b;
Test.stopTest();
A__c updated = [SELECT Record_Count__c, Total__c FROM A__c WHERE Id = :a.Id];
System.assertEquals(1, updated.Record_Count__c, 'A should have 1 B');
System.assertEquals(30, updated.Total__c, 'Total should reflect updated value');
}
/*
* This method tests bulk insert of B records.
*/
@isTest
static void testBulkInsertB() {
A__c a = [SELECT Id FROM A__c WHERE Name = 'Test1'];
List<B__c> bList = new List<B__c>();
for (Integer i = 0; i < 200; i++) {
bList.add(new B__c(Name = 'B ' + i, Value__c = 1, A_No__c = a.Id));
}
Test.startTest();
insert bList;
Test.stopTest();
A__c updated = [SELECT Record_Count__c, Total__c FROM A__c WHERE Id = :a.Id];
System.assertEquals(200, updated.Record_Count__c, 'B count should be 200');
System.assertEquals(200, updated.Total__c, 'Total should be 200');
}
/*
* This method tests inserting B record without A.
*/
@isTest
static void testNullAId() {
B__c orphanB = new B__c(Name = 'Orphan B', Value__c = 999); // No A_No__c
Test.startTest();
insert orphanB;
Test.stopTest();
List<A__c> allA = [SELECT Record_Count__c, Total__c FROM A__c];
for (A__c a : allA) {
System.assertEquals(true, a.Record_Count__c == null || a.Record_Count__c == 0, 'Should not affect any A record');
System.assertEquals(true, a.Total__c == null || a.Total__c == 0, 'Should not affect any A record');
}
}
}
Key Benefits:
  • 🚀 Efficiency: Uses maps to efficiently calculate roll-up data, minimizing database interactions and avoiding unnecessary processing.
  • 🎯 Accuracy: Ensures parent records reflect real-time changes in child records.
  • 💪 Robustness: Handles null values and edge cases gracefully.
  • 🔄 Automation: Eliminates manual updates to roll-up fields.

Note – The above code shows an example of using map for calculations. If you want to use the aggregate query approach, please refer to this blog post Roll up summary trigger To Update Child Records Count and total Sum of values of child records on Parent Record using aggregate query.

For more helpful articles please visit – https://thesalesforcedev.in

One response to “Roll up summary trigger To Update Child Records Count and total Sum of values of child records on Parent Record using Maps”

  1. […] Roll up summary trigger To Update Child Records Count and total Sum of values of child records on Pa… […]

    Like

Leave a comment