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
Gerry_MarlettaGerry_Marletta 

Need help limiting an Account Trigger so it doesn't exceed row limit

We have recently started using Parent/Child Account relationships in Salesforce, and have a filtered Roll-Up field to sum live open Opps with Accounts. We wanted to be able to sum that field on all child Accounts to the Parent level, so I found an Apex Class and Trigger on line that seemed like it would work.

 

However, every time an edit is made to any Account record in Salesforce, I receive a gov limit warning:

 

"Approaching limit for non-selective query against large object type (more than 100000 rows). Current size is approximately 94107 rows. When the limit is reached, the query will fail. "

 

I know this is because it's querying all Accounts (since we have approximately 94,100 Account records) every time it runs, but I am not yet comfortable enough with the Apex language to know what I need to do to prevent it from querying every Account record every time.

 

Below is the Class and the Trigger we have. Any help is greatly appreciated as I learn by trial and error.

 

CLASS:

public class RollUpSummaryUtility {
     
    //the following class will be used to house the field names
    //and desired operations
    public class fieldDefinition {
        public String operation {get;set;}
        public String childField {get;set;}
        public String parentField {get;set;}
         
        public fieldDefinition (String o, String c, String p) {
            operation = o;
            childField = c;
            parentField = p;
        }
    }
     
    public static void rollUpTrigger(list<fieldDefinition> fieldDefinitions,
    list<sObject> records, String childObject, String childParentLookupField,
    String parentObject, String queryFilter) {
         
        //Limit the size of list by using Sets which do not contain duplicate
        //elements prevents hitting governor limits
        set<Id> parentIds = new set<Id>();
         
        for(sObject s : records) {
            parentIds.add((Id)s.get(childParentLookupField));
        }
         
        //populate query text strings to be used in child aggregrator and
        //parent value assignment
        String fieldsToAggregate = '';
        String parentFields = '';
         
        for(fieldDefinition d : fieldDefinitions) {
            fieldsToAggregate += d.operation + '(' + d.childField + ') ' +
            ', ';
            parentFields += d.parentField + ', ';
        }
         
        //Using dynamic SOQL with aggergate results to populate parentValueMap
        String aggregateQuery = 'Select ' + fieldsToAggregate +
        childParentLookupField + ' from ' + childObject + ' where  ' +
        childParentLookupField + ' IN :parentIds ' + queryFilter + ' ' +
        ' group by ' + childParentLookupField;
         
        //Map will contain one parent record Id per one aggregate object
        map<Id, AggregateResult> parentValueMap =
        new map <Id, AggregateResult>();
         
        for(AggregateResult q : Database.query(aggregateQuery)){
            parentValueMap.put((Id)q.get(childParentLookupField), q);
        }
         
        //list of parent object records to update
        list<sObject> parentsToUpdate = new list<sObject>();
         
        String parentQuery = 'select ' + parentFields + ' Id ' +
         ' from ' + parentObject + ' where Id IN :parentIds';
         
        //for each affected parent object, retrieve aggregate results and
        //for each field definition add aggregate value to parent field
        for(sObject s : Database.query(parentQuery)) {
             
            Integer row = 0; //row counter reset for every parent record
            for(fieldDefinition d : fieldDefinitions) {
                String field = 'expr' + row.format();
                AggregateResult r = parentValueMap.get(s.Id);
                //r will be null if no records exist
                //(e.g. last record deleted)
                if(r != null) {
                    Decimal value = ((Decimal)r.get(field) == null ) ? 0 :
                        (Decimal)r.get(field);
                    s.put(d.parentField, value);
                } else {
                    s.put(d.parentField, 0);
                }
                row += 1; //plus 1 for every field definition after first
            }
            parentsToUpdate.add(s);
        }
         
        //if parent records exist, perform update of all parent records
        //with a single DML statement
        if(parentsToUpdate.Size() > 0) {
            update parentsToUpdate;
        }
         
    }
 
}

 TRIGGER:

 

trigger CustomeParentAccountRollUpOpenOpps on Account (after delete, after update) {
     
    if(trigger.isUpdate){
         
        list<RollUpSummaryUtility.fieldDefinition> fieldDefinitions =
        new list<RollUpSummaryUtility.fieldDefinition> {
            new RollUpSummaryUtility.fieldDefinition('SUM', 'Live_Open_Opp__c',
            'Child_Account_Open_Opp_Dollars_Trigger__c')
        };
         
        RollUpSummaryUtility.rollUpTrigger(fieldDefinitions, trigger.new,
        'Account', 'Parent_Account_Custom__c', 'Account', '');
         
    }
     
    if(trigger.isDelete){
         
        list<RollUpSummaryUtility.fieldDefinition> fieldDefinitions =
        new list<RollUpSummaryUtility.fieldDefinition> {
            new RollUpSummaryUtility.fieldDefinition('SUM', 'Live_Open_Opp__c',
            'Child_Account_Open_Opp_Dollars_Trigger__c')
        };
         
        RollUpSummaryUtility.rollUpTrigger(fieldDefinitions, trigger.old,
        'Account', 'Parent_Account_Custom__c', 'Account', '');
         
    }
     
}

 

SFAdmin5SFAdmin5

might not be possible with a trigger.  we recently tried doing this as well and found the governor limits prevented us from doing this custom rollup functionality with triggers.  we had to scrap the trigger solution and will use scheduled batch jobs to do this.  if the volume of records you are querying is in the hundreds of thousands a trigger might not be a viable solution, and probably won't work.  you'll probably need to write it as a sced batch job that runs every few hours or once a day.

Hengky IlawanHengky Ilawan

Hi Gerry,

 

You should query only those accounts that  are affected.

 

I've written something similar to this before, but I didn't put the code in a separate class though.

 

trigger RollUpFYTons on Account (after insert, after update, before delete, after undelete) {

    Set<Id> parentIds = new Set<Id>();
    if (trigger.old != null) {
        for (Account acc : trigger.old) {
            parentIds.add(acc.ParentId);
        }
    }
    if (trigger.new != null) {
        for (Account acc : trigger.new) {
            parentIds.add(acc.ParentId);
        }
    }
    Map<Id, Account> parentAccMap = new Map<Id, Account>([
        SELECT Id, Sum_Amount__c FROM Account WHERE Id IN :parentIds
    ]);
    
// run the update only if there are still parent account to be updated if (!parentAccMap.isEmpty()) { if (trigger.old != null) { for (Account acc : trigger.old) { Account pacc = parentAccMap.get(acc.ParentId); if (pacc.Sum_Amount__c == null) { pacc.Sum_Amount__c = 0; } pacc.Sum_Amount__c = pacc.Sum_Amount__c - acc.Sum_Amount__c; } } if (trigger.new != null) { for (Account acc : trigger.new) { Account pacc = parentAccMap.get(acc.ParentId); if (pacc.Sum_Amount__c == null) { pacc.Sum_Amount__c = 0; } pacc.Sum_Amount__c = pacc.Sum_Amount__c + acc.Sum_Amount__c; } } update(parentAccMap.values()); } }

It's not exactly like your requirement, but hopefully it can give you some ideas.

 

Regards,

Hengky