You need to sign in to do that
Don't have an account?
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', ''); } }
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.
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.
It's not exactly like your requirement, but hopefully it can give you some ideas.
Regards,
Hengky