Roll-Up summary functionality between lookup related objects using Triggers

The ask is a field has to be created on Account and that should show the count of contact records for that specific Account.

In a way we need to create Roll-Up summary functionality between Account and Contact.

It's a famous interview question, isn't it!

Let me break it down.

  • When a new contact record is created the count field on the parent Account should be updated.
  • When the contact is updated and if the AccountId field is modified to some other Account then we need to update the count field on the Account object.
  • When a contact is deleted the count field on the parent Account should be updated as well.
  • Last, when the contact is undeleted the count filed on tha parent Account has to be updated as well.

Here is the Trigger to implement it, am using Trigger handler design pattern so the Trigger itself is going to be lean and the handler class is going to have the business logic.

trigger ContactTrigger on Contact(
  after insert,
  after update,
  after delete,
  after undelete
) {

  //when the record is inserted and undeleted increment count
  if (
    (Trigger.isAfter && Trigger.isInsert) ||
    (Trigger.isAfter && Trigger.isUndelete)
  ) {
    ContactHandler.incrementCount(Trigger.new);
  }
  //when updated and the parent is changed decrement it
  if ((Trigger.isAfter && Trigger.isUpdate)) {
    ContactHandler.decrementCount(Trigger.oldMap, Trigger.newMap);
  }
  //when deleted dercement it
  if (Trigger.isAfter && Trigger.isDelete) {
    ContactHandler.decrementDeleteCount(Trigger.oldMap);
  }

}
This is the Trigger code
public with sharing class ContactHandler {
  public ContactHandler() {
  }

  public static void incrementCount(List<Contact> contactsList) {
    List<Account> tobeUpdatedAccountsMap = new List<Account>();
    List<Id> accountIds = new List<Id>();

    for (Contact c : contactsList) {
      accountIds.add(c.AccountId);
    }

    Map<Id, Account> accountsMap = new Map<Id, Account>(
      [SELECT Count__c FROM Account WHERE Id IN :accountIds]
    );

    for (Contact c : contactsList) {
      if (accountsMap.get(c.AccountId) != null) {
        Account a = new Account();
        a.Id = c.AccountId;
        System.debug(' 🚀 ' + c.Account.Count__c);
        a.count__c = accountsMap.get(c.AccountId).Count__c + 1;
        tobeUpdatedAccountsMap.add(a);
      }
    }

    update tobeUpdatedAccountsMap;
  }

  public static void decrementCount(
    Map<Id, Contact> oldContactsMap,
    Map<Id, Contact> newContactsMap
  ) {
    List<Id> accountIds = new List<Id>();

    for (Contact c : oldContactsMap.values()) {
      accountIds.add(c.AccountId);
    }
    for (Contact c : newContactsMap.values()) {
      accountIds.add(c.AccountId);
    }

    Map<Id, Account> accountsMap = new Map<Id, Account>(
      [SELECT Count__c FROM Account WHERE Id IN :accountIds]
    );

    List<Account> tobeUpdatedAccountsMap = new List<Account>();
    for (Contact c : newContactsMap.values()) {
      Id oldAccountId = oldContactsMap.get(c.Id).AccountId;
      Id newAccountId = newContactsMap.get(c.Id).AccountId;
      if (
        (oldAccountId != null && newAccountId != null) &&
        (oldAccountId != newAccountId)
      ) {
        Account a = new Account();
        a.Id = c.AccountId;
        a.count__c = accountsMap.get(c.AccountId).Count__c + 1;
        tobeUpdatedAccountsMap.add(a);
      }
    }

    for (Contact c : oldContactsMap.values()) {
      Id oldAccountId = oldContactsMap.get(c.Id).AccountId;
      Id newAccountId = newContactsMap.get(c.Id).AccountId;
      if (
        (oldAccountId != null && newAccountId != null) &&
        (oldAccountId != newAccountId)
      ) {
        Account a = new Account();
        a.Id = c.AccountId;
        a.count__c = accountsMap.get(c.AccountId).Count__c - 1;
        tobeUpdatedAccountsMap.add(a);
      }
    }
    update tobeUpdatedAccountsMap;
  }

  public static void decrementDeleteCount(Map<Id, Contact> oldContactsMap) {
    List<Id> accountIds = new List<Id>();

    for (Contact c : oldContactsMap.values()) {
      accountIds.add(c.AccountId);
    }

    Map<Id, Account> accountsMap = new Map<Id, Account>(
      [SELECT Count__c FROM Account WHERE Id IN :accountIds]
    );

    Map<Id, Account> tobeUpdatedAccountsMap = new Map<Id, Account>();
    for (Contact c : oldContactsMap.values()) {
      Id oldAccountId = oldContactsMap.get(c.Id).AccountId;
      if ((oldAccountId != null)) {
        Account a = new Account();
        a.Id = c.AccountId;
        a.count__c = accountsMap.get(c.AccountId).Count__c + 1;
        tobeUpdatedAccountsMap.put(a.Id, a);
      }
    }

    for (Contact c : oldContactsMap.values()) {
      Id oldAccountId = oldContactsMap.get(c.Id).AccountId;
      if ((oldAccountId != null)) {
        Account a = new Account();
        a.Id = c.AccountId;
        a.count__c = accountsMap.get(c.AccountId).Count__c - 1;
        tobeUpdatedAccountsMap.put(a.Id, a);
      }
    }
    update tobeUpdatedAccountsMap.values();
  }
}

This is the Trigger handler file

Let me you if you have any better way or refactored way of doing it!