function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Jhett JonesJhett Jones 

Trigger to update parent record when any child record is updated.

So the structure I have is this:

Record of Custom Object Type A has a related list of multiple records of Custom Object Type B.


And this is what I'm looking to do:

Object Type B has a Currency field, and Object Type A has a field that is inteded to be the Sum of that Currency field for all records in the related list. Whenever any record of Object Type B is updated, I need the Sum field on Object Type A to calculate the new sum. (Essentially it is a rollup sum, but I cannot use a rollup field because these do not have a Master-Detail relationship.)


Example:

Initial State:
  • Object Type A Record SumField = 100
    • Related ObjectB Record 1 CurrencyField = 50
    • Related ObjectB Record 2 CurrencyField = 30
    • Related ObjectB Record 3 CurrencyField = 20
User Action:
  • Related ObjectB Record 2 CurrencyField is changed from 30 to 80
Desired End State:
  • Object Type A Record SumField = 150
    • Related ObjectB Record 1 CurrencyField = 50
    • Related ObjectB Record 2 CurrencyField = 80
    • Related ObjectB Record 3 CurrencyField = 20
Best Answer chosen by Jhett Jones
Jhett JonesJhett Jones
As noted, a rollup summary cannot be used because these objects do not have a Master-Detail relationship.

But, I did find an answer elsewhere and have translated it into a generic form for anyone else who needs it:
trigger TRIGGERNAME on Child_Object__c (after insert, after delete, after update) {

    if(Trigger.isInsert){
            try {
                for (Child_Object__c co : Trigger.new){
                    Parent_Object__c po = [SELECT Id, Sum_Field__c  FROM Parent_Object__c WHERE Id = :co.Parent_Lookup__c];
                    
                    List<Child_Object__c> l_co = [SELECT Id, Amount__c FROM Child_Object__c WHERE Parent_Lookup__c = :po.Id];
                    for(Child_Object__c am_co : l_co) {
                        amount += am_co.Amount__c;  
                    }
                    po.Sum_Field__c = amount;

                    update po;
                }
            } catch (Exception e) {
                System.debug(e);
            }
        }

    if(Trigger.isAfter) {
        if(Trigger.isUpdate){
            try {
                for (Child_Object__c co : Trigger.old){
                    Parent_Object__c po = [SELECT Id, Sum_Field__c  FROM Parent_Object__c WHERE Id = :co.Parent_Lookup__c];
                    
                    List<Child_Object__c> l_co = [SELECT Id, Amount__c FROM Child_Object__c WHERE Parent_Lookup__c = :po.Id];
                    for(Child_Object__c am_co : l_co) {
                        amount += am_co.Amount__c;  
                    }
                    po.Sum_Field__c = amount;

                    update po;
                }
            } catch (Exception e) {
                System.debug(e);
            }
        }

        if(Trigger.isDelete){
            try {
                for (Child_Object__c co : Trigger.old){
                    Parent_Object__c po = [SELECT Id, Sum_Field__c  FROM Parent_Object__c WHERE Id = :co.Parent_Lookup__c];
                    
                    List<Child_Object__c> l_co = [SELECT Id, Amount__c FROM Child_Object__c WHERE Parent_Lookup__c = :po.Id];
                    for(Child_Object__c am_co : l_co) {
                        amount += am_co.Amount__c;  
                    }
                    po.Sum_Field__c = amount;

                    update po;
                }
            } catch (Exception e) {
                System.debug(e);
            }
        }
    }
}

**The __c portion of the names is left off if working with a Standard Object/Field

All Answers

Jhett JonesJhett Jones
As noted, a rollup summary cannot be used because these objects do not have a Master-Detail relationship.

But, I did find an answer elsewhere and have translated it into a generic form for anyone else who needs it:
trigger TRIGGERNAME on Child_Object__c (after insert, after delete, after update) {

    if(Trigger.isInsert){
            try {
                for (Child_Object__c co : Trigger.new){
                    Parent_Object__c po = [SELECT Id, Sum_Field__c  FROM Parent_Object__c WHERE Id = :co.Parent_Lookup__c];
                    
                    List<Child_Object__c> l_co = [SELECT Id, Amount__c FROM Child_Object__c WHERE Parent_Lookup__c = :po.Id];
                    for(Child_Object__c am_co : l_co) {
                        amount += am_co.Amount__c;  
                    }
                    po.Sum_Field__c = amount;

                    update po;
                }
            } catch (Exception e) {
                System.debug(e);
            }
        }

    if(Trigger.isAfter) {
        if(Trigger.isUpdate){
            try {
                for (Child_Object__c co : Trigger.old){
                    Parent_Object__c po = [SELECT Id, Sum_Field__c  FROM Parent_Object__c WHERE Id = :co.Parent_Lookup__c];
                    
                    List<Child_Object__c> l_co = [SELECT Id, Amount__c FROM Child_Object__c WHERE Parent_Lookup__c = :po.Id];
                    for(Child_Object__c am_co : l_co) {
                        amount += am_co.Amount__c;  
                    }
                    po.Sum_Field__c = amount;

                    update po;
                }
            } catch (Exception e) {
                System.debug(e);
            }
        }

        if(Trigger.isDelete){
            try {
                for (Child_Object__c co : Trigger.old){
                    Parent_Object__c po = [SELECT Id, Sum_Field__c  FROM Parent_Object__c WHERE Id = :co.Parent_Lookup__c];
                    
                    List<Child_Object__c> l_co = [SELECT Id, Amount__c FROM Child_Object__c WHERE Parent_Lookup__c = :po.Id];
                    for(Child_Object__c am_co : l_co) {
                        amount += am_co.Amount__c;  
                    }
                    po.Sum_Field__c = amount;

                    update po;
                }
            } catch (Exception e) {
                System.debug(e);
            }
        }
    }
}

**The __c portion of the names is left off if working with a Standard Object/Field
This was selected as the best answer
Jhett JonesJhett Jones

The trigger is called when the child object in the relationship is created, updated, or deleted.

When the trigger is called, it finds the parent of the child record, then from the parent reverses direction to find the full list of child records (including the child record that started the process).

Once it has all of the children, it simply adds each of their values and stores the sum in a field on the parent.
--------------------------------------------
All of the blue text above (objects names and field names) need to be replaced with the object names and field names for your organization. The variables (e.g., po, co, amount) can be left as is and they should work.

Jonathan RussellJonathan Russell
This was very helpful.  But in my use case, if the child record is changed to a new Parent, I had to add code to update the new parent as well as the old parent.
Seshu Babu 3Seshu Babu 3
Hi Jonathan,
The below code may helpful for your requirement.
trigger updateaccountfield on Contact (after insert, after update) {
    List<ID> AccID = New List<ID>();   
    for(Contact con : Trigger.new){
        if(con.Account_Name__c!=null&& con.AccountId != null){
            AccID.add(con.AccountId);
        }
    }
    
    List<Contact> conlist = [ select Account_Name__c from Contact where accountId in :AccID];
    List<Account> accList = [SELECT Name, Contact_Name__c FROM Account WHERE id in :AccID];
    for(integer i = 0 ; i < accList.size(); i++){
        String address = ''+conlist[0].get('Account_Name__c');
        accList[i].Contact_Name__c = address;
    }
    update accList;
}
TK1234TK1234
Hi Jhett Jones,

Excellent solution. I have similar requirement where I have to sum up the Price amount from Child list and update in Parent Price feild, But the relationship between child and parent is Self.  I have leveraged your code  for my requirement... but soemthign seems not working.. I see the logs getting caputered recursively... can you please advise whats wrong in the below code?
 TIA !!

trigger OpportunityCalc on Opportunity (after insert, after delete, after update) {
double amount =0;
    if(Trigger.isInsert){
            try {
                for (Opportunity co : Trigger.new){
                    Opportunity po = [SELECT Id, Total_value__c  FROM Opportunity WHERE Id = :co.Opp__c];
                    system.debug('get Parent PO'+po);
                    List<Opportunity> l_co = [SELECT Id, Total_value__c FROM Opportunity WHERE Opp__c = :po.Id];
                    system.debug('get the child '+l_co);
                    for(Opportunity am_co : l_co) {
                        amount += am_co.Total_value__c;  
                    }
                    po.Total_value__c = amount;
                     system.debug('get the total insert '+po.Total_value__c);
                    update po;
                }
            } catch (Exception e) {
                System.debug(e);
            }
        }

    if(Trigger.isAfter) {
        
        if(Trigger.isUpdate){
            try {
                for (Opportunity co : Trigger.old){
                   Opportunity  po = [SELECT Id, Total_value__c  FROM Opportunity WHERE Id = :co.Opp__c];
                    
                    List<Opportunity> l_co = [SELECT Id, Total_value__c FROM Opportunity WHERE Opp__c = :po.Id];
                    for(Opportunity am_co : l_co) {
                        amount += am_co.Total_value__c;  
                    }
                    po.Total_value__c = amount;
                     system.debug('get the total update '+po.Total_value__c);
                    update po;

                }
            } catch (Exception e) {
                System.debug(e);
            }
        }

        if(Trigger.isDelete){
            try {
                for (Opportunity co : Trigger.old){
                 Opportunity   po = [SELECT Id, Total_value__c  FROM Opportunity WHERE Id = :co.Opp__c];
                    
                    List<Opportunity> l_co = [SELECT Id, Total_value__c FROM Opportunity WHERE Opp__c = :po.Id];
                    for(Opportunity am_co : l_co) {
                        amount += am_co.Total_value__c;  
                    }
                    po.Total_value__c = amount;
                     system.debug('get the total delete '+po.Total_value__c);

                    update po;
                }
            } catch (Exception e) {
                System.debug(e);
            }
        }
    }
}
Jhett JonesJhett Jones

TK1234

This trigger goes off anytime the parent object type is updated, so I don't think you can use it as is if the parent object and child object are the same. For instance, in your case the trigger goes off when an Opportunity is updated, but the trigger itself updates an Opportunity, so it would cause the recursive loop you described. 

I'd think the loop would break once it tried to run and could find no suitable parent record though. 

Sorry this isn't much help, but I haven't looked at this code in almost 2 years.

TK1234TK1234
Thanks for the response...Make sense about the recursive.. but is there any other way we can achieve this? if you could pass on soem hint , I can try building the logic... Appreciate...

 
Sumanta SatpathySumanta Satpathy
is it the correct way that SOQL is used within for loop? Can anybody explain?
DUGLODUGLO
Yes, to echo Sumanta Satpathy's question - I'd like to ask Jhett Jones to explain the SOQL query inside a for loop. This trigger doesn't appear to be properly bulkified. Could anyone provide a solution that won't run into limits in bulk scenarios?
Ramanjaneya Reddy kalutlaRamanjaneya Reddy kalutla
Hi,
 I have bulkfied the after update trigger part find the code below.

trigger OpportunityCalc on Opportunity (after update) {

            List<Id> Opp_CC = new list<Id>();            
            try {
                for (Opportunity co : Trigger.old){
                    Opp_CC.add(co.Opp_c);
                    }
                    
                   List<Opportunity>  po = [SELECT Id, Total_value__c  FROM Opportunity WHERE Id = :Opp_CC];
                   List<Opportunity> l_co = [SELECT Id, Total_value__c FROM Opportunity WHERE Opp__c = :po.Id];
                    
                    for(Opportunity am_co : l_co) {
                        amount += am_co.Total_value__c;  
                    }
                    po.Total_value__c = amount;
                     system.debug('get the total update '+po.Total_value__c);
                    update po;

                }
             catch (Exception e) {
                System.debug(e);
            }
        
Richa Paliwal 18Richa Paliwal 18
This post was helpful, but now I am struggling to get the code coverage. Can someone please help?

Here is my test classs:

@istest
public class TestContractSourceUpdateOnOpp {
    static testmethod void validateContractSource()
    {
        
        Test_SharedNewAccount.createAccount('Test Account');
                        // Test Creation of the Shared Account
        Account acct = [SELECT Id FROM Account WHERE Name = 'Test Account' LIMIT 1];
        System.Assert(acct != null, 'Account was not inserted properly!');
        

 //create opportunity
        Opportunity Op = new Opportunity(ACCOUNTID = acct.Id, Name = 'TestOp',Anticipatged_1st_Test_Season__c = 'Fall 2017',ForecastCategoryName = 'Prospect & Discovery',Imp_Case_Created__c = null ,Closed_Won_Reason__c = 'Relationships', Closed_Won_Detail__c = 'Relationship with Influencer', Region__c = 'East Enrollments' , Type = 'Renewal',Test_Alignment_Option__c = 'Other (Specify in Notes)' , StageName = 'Prospect', License_Start_Date__c = date.newInstance(2018,03,01), License_End_Date__c = date.newInstance(2018,03,31), CloseDate = Date.newInstance(2018,03,30), MAP_Growth_MAP_Skills_Implementation__c = 'Standard');
        insert Op;
        system.debug('Insert opportunity' + Op.Id);
        system.debug('Insert opportunity' + Op.contract_Source__c);  
        
        Sales_Order__c SO = new Sales_Order__c(Name = 'Test Sales Order', Opportunity__c = Op.Id, Account__c = acct.id, Contract_Source__c = 'Test Contract Source');
        insert SO;
            
        system.debug('Sales order inserted' +SO);
        Test.StartTest();   
        
        {        
            
                                        system.debug('Try updating Sales order with Contract Status'+SO.Contract_Source__c);
            
             system.debug('opp contract source' + Op.Contract_Source__c);
        
           System.assert(Op.Contract_Source__c == 'Test Contract Source');            
                                system.debug('Catch the validation'+Op);
        }
 
        Test.StopTest();
    }

}
System Admin 1130System Admin 1130
@Jhett Jones ,
The code which you gave was working fine. I have one more requirement with same scenario i.e, if the contact is moved from one account to other account . The field in account record need to be update . Can you give any suggestions.
Rakesh Sahoo 22Rakesh Sahoo 22
That code not working for me. It shows Variable does not exist:amount
Can you please help?

Thanks,
Charlton HuaCharlton Hua
Hi, I'm completely inexperienced in trigger coding, maybe tried a few basics but I have a use case where I want to use this code with Parent-Child Opporunities and I'm wondering what you all mean when discussing 'bulkified'? do I need to consider this for Parent-Child Opporunities? Also will this work if the field I'm attempting or wanting to roll-up is a native forumla field?
Brad Huston 15Brad Huston 15
I tried using this to update child opps on a parent opp.  Didn't work.
Vikram PillarisettyVikram Pillarisetty
trigger UpdateParentWhenChildChanged on Contact (after insert, after update) {
    List<Account> accounts = new List<Account>();
    Set<ID> accountIDs = new Set<ID>();
    for(Contact con : Trigger.new){
        accountIDs.add(con.AccountId);
    }    
    for(Account acc : [select ID, Rollup_Amount__c, (select Id, Amount_X__c from Contacts) from Account where ID IN: accountIDs]){
        Decimal TotalAmount = 0;
        for(Contact con : acc.Contacts){
            TotalAmount+=con.Amount_X__c;
        }
        acc.Rollup_Amount__c = TotalAmount;
        accounts.add(acc);
    }
    update accounts;
}