Roll Up Summary Trigger To Update Total Contact On Account In Salesforce

Roll Up Summary Trigger To Update Total Contact On Account In Salesforce

, ,

Introduction:

In this blog post, we will see an example on how to roll up child values on a parent record. This applies to both master-detail and lookup relationships.

In this example. we will demonstrate updating total contacts count on Account. This will be done using roll up summary apex trigger and aggregate query. The same logic can be used update the child record count on a parent record in a lookup relationship. This will be achieved using an aggregate query and maps approach.

Example Scenario:

Imagine a company using Salesforce to manage customer accounts (Account records) and their related contacts (Contact records). Each Account has a field (Number_of_Contacts__c) to track the total number of associated Contact records.

A Contact might be added, updated, deleted, or restored. The Number_of_Contacts__c field on the related Account must then reflect these changes accurately. For example:

Adding a New Contact: If a new Contact is linked to an Account. The contact count on that Account should go up. The account should reflect this increase.

Updating a Contact: If a Contact is reassigned to another Account, the original Account’s contact count should decrease. The new Account’s count should increase.

  • Adding a New Contact: If a new Contact is linked to an Account. The contact count on that Account should go up. The account should reflect this increase.
  • Updating a Contact Parent: If a Contact is reassigned to another Account, the original Account’s contact count should decrease. The new Account’s count should increase.
  • Deleting a Contact: When a Contact is removed, the associated Account’s contact count should decrease.
  • Restoring a Contact: If a deleted Contact is restored, the Account’s contact count should include it again.

This ensures that Account managers always have up-to-date information about the number of Contacts linked to each Account. The UpdateContactCountOnAccount trigger automates these updates, improving data accuracy and minimizing manual effort.

Trigger Overview:

The UpdateContactCountOnAccount trigger is designed to maintain the Number_of_Contacts__c field on Account records. It dynamically updates this field based on changes to related Contact records.

What It Updates:

  • Number_of_Contacts__c: Reflects the total number of Contact records linked to each Account.

When It Runs:

The trigger fires during the following DML events on Contact records:

  • Insert: A new Contact is added.
  • 🔄 Update: A Contact is reassigned to a different Account.
  • Delete: A Contact is removed.
  • ↩️ Undelete: A previously deleted Contact is restored.

How It Works:

Here’s a step-by-step breakdown of the trigger:

  1. Declare a Set for Account IDs:
    • A set (accountIds) is initialized to collect the IDs of Accounts whose Number_of_Contacts__c field needs updating.
  2. Identify Relevant Account IDs for Insert or Undelete Events:
    • For insert or undelete events, the trigger iterates through the Trigger.new collection.
    • If a Contact has a non-null AccountId, its associated Account ID is added to the set.
  3. Identify Relevant Account IDs for Delete Events:
    • For delete events, the trigger loops through the Trigger.old collection.
    • It adds the AccountId of each deleted Contact to the set if it is not null.
  4. Handle Updates to Account IDs:
    • For update events, the trigger compares the AccountId in Trigger.oldMap and Trigger.new.
    • If the AccountId changes, both the old and new Account IDs are added to the set.
    • If the AccountId remains unchanged, no action is needed unless additional logic is introduced.
  5. Exit if No Account IDs to Update:
    • If the set of accountIds is empty, the trigger exits early to avoid unnecessary processing.
  6. Aggregate Queries to Count Contacts:
    • An aggregate query counts the number of Contacts (COUNT(Id)) for each Account in accountIds.
    • The results are stored in a map (contactsCount) for easy reference.
  7. Retrieve and Prepare Accounts for Update:
    • The trigger queries all Accounts in accountIds, retrieving their current Number_of_Contacts__c value.
    • It updates this field based on the data in contactsCount. If an Account ID is not found in the map, the field is set to 0.
  8. Collect Accounts for Update:
    • Updated Account records are added to a list (accountsToUpdate).
  9. Perform the Update Operation:
    • The trigger updates all Accounts in the accountsToUpdate list to ensure their Number_of_Contacts__c field is accurate.
Sample Code :
/**
* UpdateContactCountOnAccount Trigger
* —————-
* This trigger is designed to update the Number_of_Contacts__c field on the Account object
* whenever a Contact record is inserted, updated, deleted, or undeleted.
* The purpose is update the Number_of_Contacts__c field on the related Account record.
* Key Features:
* – Identifies Contact records linked to an Account record (via AccountId).
* – Counts the number of Contact records for each Account record.
* – Updates the Number_of_Contacts__c field on the Account record.
* – Demonstrates the use of aggregate queries to optimize performance.
*/
trigger UpdateContactCountOnAccount on Contact(after delete, after insert, after update, after undelete) {
// Declare a set to store the Ids of the parent object Account
Set<Id> accountIds = new Set<Id>();
// Check if the trigger is in the 'insert' or 'undelete' context
if (Trigger.isInsert || Trigger.isUndelete) {
// Loop through the new records
for (Contact child: Trigger.new) {
// Check if the contact record has a Account ID
if (child.AccountId != null) {
accountIds.add(child.AccountId);
}
}
}
// Check if the trigger is in the 'delete' context
if (Trigger.isDelete) {
// Loop through the old records
for (Contact child: Trigger.old) {
// Check if the contact record has a Account ID
if (child.AccountId != null) {
// Add the parent account Id of each deleted contact record to the set of parent Ids to be updated.
accountIds.add(child.AccountId);
}
}
}
// Check if the trigger is in the 'update' context
if (Trigger.isUpdate) {
// Loop through the new records
for (Contact child: Trigger.new) {
// Check if the parent Id is changed
if (child.AccountId != Trigger.oldMap.get(child.Id).AccountId) {
// Check if either the old parent Id or the value field of the child record has been changed
// and if so, it adds the old parent Id to the set of parent Ids to be updated.
if (Trigger.oldMap.get(child.Id).AccountId != null) {
accountIds.add(Trigger.oldMap.get(child.Id).AccountId);
}
// Check if the new parent account Id is not null
if (child.AccountId != null) {
// Add the parent account Id of each updated contact record to the set of parent account Ids
accountIds.add(child.AccountId);
}
}
}
}
// If there are no account IDs to update, exit the trigger
if (accountIds.isEmpty()) return;
// Declare maps to store the number of contacts for each parent
Map<Id,Integer> contactsCount = new Map<Id,Integer>();
// Query the aggregate data using aggregate queries for count and loop through the results
// to populate the contactsCount map with the number of contacts for each parent account
for (AggregateResult ar: [SELECT AccountId, COUNT(Id) contactCount FROM Contact WHERE AccountId IN: accountIds GROUP BY AccountId]) {
contactsCount.put((Id) ar.get('AccountId'), (Integer) ar.get('contactCount'));
}
// Declare a list to hold the account records to update
List<Account> accountsToUpdate = new List<Account>();
// Query the account records and loop through them to update the Number_of_Contacts__c fields om account records
for (Account parent: [
SELECT Id, Number_of_Contacts__c
FROM Account WHERE Id IN: accountIds
]) {
// Update the Number_of_Contacts__c on the account record
// If the parent ID is in the count record contactsCount map, set the Number_of_Contacts__c to that value or 0
parent.Number_of_Contacts__c = contactsCount.containsKey(parent.Id) ? contactsCount.get(parent.Id) : 0;
// Add the parent object to the list of accounts to update
accountsToUpdate.add(parent);
}
// If there are any parent records to update, proceed with the update
if (!accountsToUpdate.isEmpty()) {
update accountsToUpdate;
}
}
/**
* This is a test class for the UpdateContactCountOnAccount trigger.
* It includes various test methods to cover different scenarios:
* 1. Inserting a contact with an account
* 2. Deleting a contact
* 3. Undeleting a contact
* 4. Updating a contact's account
*/
@isTest
private class UpdateContactCountOnAccountTest {
/*
* This method sets up the test data for the test class.
* It creates two test accounts that will be used in the test methods.
*/
@TestSetup
static void makeData(){
// Create test accounts
Account acc1 = new Account(Name = 'Test Account 1');
Account acc2 = new Account(Name = 'Test Account 2');
insert new List<Account>{acc1, acc2};
}
/*
* This method tests the insertion of a contact with an account.
* It verifies that the Number_of_Contacts__c field on the account is updated correctly.
*/
@isTest
static void testInsertContactWithAccount() {
Account acc = [SELECT Id, Number_of_Contacts__c FROM Account WHERE Name = 'Test Account 1' LIMIT 1];
Test.startTest();
Contact cont = new Contact(
LastName = 'Test Contact',
AccountId = acc.Id
);
insert cont;
Test.stopTest();
Account updatedAcc = [SELECT Number_of_Contacts__c FROM Account WHERE Id = :acc.Id];
System.assertEquals(1, updatedAcc.Number_of_Contacts__c, 'Contact count should be 1');
}
/*
* This method tests the deletion of a contact.
* It verifies that the Number_of_Contacts__c field on the account is updated correctly.
*/
@isTest
static void testDeleteContact() {
Account acc = [SELECT Id FROM Account WHERE Name = 'Test Account 1' LIMIT 1];
Contact cont = new Contact(LastName = 'Test Contact', AccountId = acc.Id);
insert cont;
Test.startTest();
delete cont;
Test.stopTest();
Account updatedAcc = [SELECT Number_of_Contacts__c FROM Account WHERE Id = :acc.Id];
System.assertEquals(0, updatedAcc.Number_of_Contacts__c, 'Contact count should be 0');
}
/*
* This method tests the undelete operation on a contact.
* It verifies that the Number_of_Contacts__c field on the account is updated correctly after undelete.
*/
@isTest
static void testUndeleteContact() {
Account acc = [SELECT Id FROM Account WHERE Name = 'Test Account 1' LIMIT 1];
Contact cont = new Contact(LastName = 'Test Contact', AccountId = acc.Id);
insert cont;
delete cont;
Test.startTest();
undelete cont;
Test.stopTest();
Account updatedAcc = [SELECT Number_of_Contacts__c FROM Account WHERE Id = :acc.Id];
System.assertEquals(1, updatedAcc.Number_of_Contacts__c, 'Contact count should be 1');
}
/*
* This method tests the update of a contact's account.
* It verifies that the Number_of_Contacts__c field on both accounts is updated correctly.
*/
@isTest
static void testUpdateContactAccount() {
List<Account> accounts = [SELECT Id FROM Account];
Contact cont = new Contact(LastName = 'Test Contact', AccountId = accounts[0].Id);
insert cont;
Test.startTest();
cont.AccountId = accounts[1].Id;
update cont;
Test.stopTest();
List<Account> updatedAccounts = [SELECT Number_of_Contacts__c FROM Account WHERE Id IN :accounts ORDER BY Name];
System.assertEquals(0, updatedAccounts[0].Number_of_Contacts__c, 'First account should have 0 contacts');
System.assertEquals(1, updatedAccounts[1].Number_of_Contacts__c, 'Second account should have 1 contact');
}
/*
* This method tests the bulk operations on contacts.
* It verifies that the Number_of_Contacts__c field on the account is updated correctly after bulk insert.
*/
@isTest
static void testBulkOperations() {
Account acc = [SELECT Id FROM Account WHERE Name = 'Test Account 1' LIMIT 1];
List<Contact> contacts = new List<Contact>();
for(Integer i = 0; i < 200; i++) {
contacts.add(new Contact(
LastName = 'Test Contact ' + i,
AccountId = acc.Id
));
}
Test.startTest();
insert contacts;
Test.stopTest();
Account updatedAcc = [SELECT Number_of_Contacts__c FROM Account WHERE Id = :acc.Id];
System.assertEquals(200, updatedAcc.Number_of_Contacts__c, 'Contact count should be 200');
}
/*
* This method tests the scenario where a contact is created without an account.
* It verifies that the Number_of_Contacts__c field on the account remains unchanged.
*/
@isTest
static void testNullAccountId() {
Test.startTest();
Contact cont = new Contact(LastName = 'Test Contact');
insert cont;
Test.stopTest();
List<Account> accounts = [SELECT Number_of_Contacts__c FROM Account];
for(Account acc : accounts) {
System.debug(acc.Number_of_Contacts__c);
//Make sure Number_of_Contacts__c on account has default value 0
System.assertEquals(0, acc.Number_of_Contacts__c, 'Accounts should have 0 contacts');
}
}
}

Note – You can reuse this trigger on any objects which has a look-up relationship between parent and child.

Key Benefits:

  • 🚀 Efficient: Uses aggregate queries to minimize the number of queries.
  • 🎯 Accurate: Ensures the Number_of_Contacts__c field reflects real-time data.
  • 💪 Robust: Handles all DML scenarios, including null values and edge cases.
  • 🔄 Real-Time Updates: Automatically syncs Contact changes to Account fields.

This trigger enhances data integrity, reduces manual intervention, and ensures Account records always display the correct contact count.

Thanks for reading……

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

One response to “Roll Up Summary Trigger To Update Total Contact On Account In Salesforce”

  1. […] take an example or our sample code – Updating total contacts count on Account using apex trigger and aggregate query (updating child reco…, and try to implement the handler pattern as below […]

    Like

Leave a comment