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
Ryan BallRyan Ball 

Trigger Error in Production, but not in test..

Hello,

I'm having difficultly with a trigger that we are running in our instance of salesforce. It runs perfectly in test, but gives me an error in production. I didn't write this, so I was hoping that someone with more experiance than I have could help me figure out where the error is, and what I need to do to fix it. Any help would be appreciated!

Thanks,

Ryan

The error that I recieve is:
NVMTaskTrigger: execution of AfterUpdate
 
caused by: System.QueryException: Aggregate query has too many rows for direct assignment, use FOR loop
 
Class.ActivityUtils.updateActivityHistory: line 75, column 1
Class.ActivityUtils.updateAccountActivityCount: line 28, column 1
Trigger.NVMTaskTrigger: line 9, column 1

Trigger:
trigger NVMTaskTrigger on Task (after insert, after update, after delete, after undelete) {
     
    sObject[] triggerRecords;
    if(!trigger.isDelete) triggerRecords = trigger.new;
    else triggerRecords = trigger.old;
     
    //Update Open Activity Count
    ActivityUtils au = new ActivityUtils(triggerRecords);
    au.updateAccountActivityCount();
    au.updateContactActivityCount();
    au.updateLeadActivityCount();
    au.updateOpportunityActivityCount();
    au.updateCaseActivityCount();
}

Class:
public class ActivityUtils {
     
    //config
     
    String fieldToUpdate = 'NVM_Activities__c'; //this field must be added to each object we're updating
    String fieldOpenToUpdate = 'Open_Activity_Count__c'; //this field must be added to each object we're updating
     
    //state
    set<id> accountIds;
    set<id> contactIds;
    set<id> opportunityIds;
    set<id> leadIds;
    set<id> caseIds;
     
    public ActivityUtils(sObject[] records) {
        accountIds = new set<id>();
        contactIds = new set<id>();
        opportunityIds = new set<id>();
        leadIds = new set<id>();
        caseIds = new set<id>();
        captureWhatAndWhoIds(records);
        addAccountIdsFromRlatedObjects();
    }
     
    public void updateAccountActivityCount() {
        if(accountIds.size() == 0) return;
        updateActivityCount('Account','WhatId', getStringFromIdSet(accountIds));
         updateActivityHistory('Account','WhatId', getStringFromIdSet(accountIds));
         
         
    }
    public void updateContactActivityCount() {
        if(contactIds.size() == 0) return;
        updateActivityCount('Contact','WhoId', getStringFromIdSet(contactIds));
                updateActivityHistory('Contact','WhoId', getStringFromIdSet(contactIds));
 
    }
    public void updateOpportunityActivityCount() {
        if(opportunityIds.size() == 0) return;
        updateActivityCount('Opportunity','WhatId', getStringFromIdSet(opportunityIds));
                updateActivityHistory('Opportunity','WhatId', getStringFromIdSet(opportunityIds));
 
    }
    public void updateLeadActivityCount() {
        if(leadIds.size() == 0) return;
        updateActivityCount('Lead','WhoId', getStringFromIdSet(leadIds));
                updateActivityHistory('Lead','WhoId', getStringFromIdSet(leadIds));
 
    }
    public void updateCaseActivityCount() {
        if(caseIds.size() == 0) return;
        //updateActivityCount('Case','WhoId', getStringFromIdSet(caseIds));
        //updateActivityHistory('Case','WhoId', getStringFromIdSet(caseIds));
 
    }
    private void updateActivityCount(String objToUpdate, String queryFld, String updateIds) {
        string strQuery = 'SELECT Id, (SELECT Id FROM OpenActivities) FROM ' + objToUpdate + ' WHERE Id IN (' + updateIds + ')';
        sObject[] sobjects = new list<sobject>();
        for(sObject so : database.query(strQuery)) {
            OpenActivity[] oActivities = so.getSObjects('OpenActivities');
            Integer openActivityCount = oActivities == null ? 0 : oActivities.size();
            sObject obj = createObject(objToUpdate, so.Id);
            obj.put(fieldOpenToUpdate, openActivityCount);
            sobjects.add(obj);
            system.debug('openActivityCount: ' + openActivityCount);
        }
        update sobjects;
    }
      
    private void updateActivityHistory(String objToUpdate, String queryFld, String updateIds) {
        string strQuery = 'SELECT Id, (SELECT Id FROM ActivityHistories) FROM ' + objToUpdate + ' WHERE Id IN (' + updateIds + ')';        
System.debug(strQuery);
        sObject[] sobjects = new list<sobject>();
        for(sObject so : database.query(strQuery)) {
            ActivityHistory[] oActivities = so.getSObjects('ActivityHistories');
            Integer closedActivityCount = oActivities == null ? 0 : oActivities.size();
            sObject obj = createObject(objToUpdate, so.Id);
            obj.put(fieldToUpdate, closedActivityCount);
            sobjects.add(obj);
            system.debug('ActivityHistoryCount: ' + closedActivityCount);
        }
        update sobjects;
    }
     
    private void captureWhatAndWhoIds(sObject[] objects) {
        for(sObject o : objects) {
            Id whatId = (Id)o.get('WhatId');
            Id whoId = (Id)o.get('WhoId');
            if(whatId != null) {
                String objectName = getObjectNameFromId(whatId);
                if(objectName == 'account') accountIds.add(whatId);
                if(objectName == 'opportunity') opportunityIds.add(whatId);
            }
            if(whoId != null) {
                String objectName = getObjectNameFromId(whoId);
                if(objectName == 'contact') contactIds.add(whoId);
                if(objectName == 'lead') leadIds.add(whoId);
            }
        }
    }
     
    private void addAccountIdsFromRlatedObjects() {
        for(Opportunity o : [SELECT AccountId FROM Opportunity WHERE Id IN :opportunityIds]) accountIds.add(o.AccountId);
        for(Contact c : [SELECT AccountId FROM Contact WHERE Id IN :contactIds]) accountIds.add(c.AccountId);
    }
     
    private String getObjectNameFromId(Id objId) {
        String preFix = String.valueOf(objId).left(3).toLowercase();
        if(prefix == '001') return 'account';
        if(prefix == '003') return 'contact';
        if(prefix == '006') return 'opportunity';
        if(prefix == '00q') return 'lead';
        //if(prefix == '500') return 'case';
        return '';
    }
     
    private String getStringFromIdSet(set<id> idSet) {
        string idString = '';
        for(Id i : idSet) idString+= '\'' + i + '\',';
        return idString == '' ? idString : idString.left(idString.length()-1); //If idString contains some ids we want to ensure we strip out the last comma
    }
     
    //The main part of the method below was taken from //Taken from http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dynamic_dml.htm
    //However we've modified this to accept an object id
    private sObject createObject(String typeName, Id objId) {
        Schema.SObjectType targetType = Schema.getGlobalDescribe().get(typeName);
        if (targetType == null) {
            // throw an exception
        }
         
        // Instantiate an sObject with the type passed in as an argument
        //  at run time.
        return targetType.newSObject(objId);
    }
     
}
MythiliMythili
Hi,
1) You are getting this error in Production but not in Sandbox because you might have more data in Prod than the sandbox.
2) You get this error because of query in Line 71 in class ActivityUtils. The sub-Query which is (SELECT Id FROM ActivityHistories) is returning more records for some parent record and that is the reason you might be getting this error. To confirm, this is causing the issue, please add some limit to the query, for eg., SELECT Id FROM ActivityHistories limit 10 and test your class. If the trigger executes fine, then you can confirm that this query is causing the issue and it can be resolved.
Thanks.
MythiliMythili
Please let me know if this approach helps.
Ryan BallRyan Ball
Hi Mythill,

Thank you so much for your reply!

That makes a lot of sense. Would adding a limit be as simple as changing line 71 from 

string strQuery = 'SELECT Id, (SELECT Id FROM ActivityHistories) FROM ' + objToUpdate + ' WHERE Id IN (' + updateIds + ')'; 
TO
string strQuery = 'SELECT Id, (SELECT Id FROM ActivityHistories LIMIT 10) FROM ' + objToUpdate + ' WHERE Id IN (' + updateIds + ')'; 
?

I apologize if that is a trival question, I am very inexperianced at this.

Thanks,

Ryan
MythiliMythili
Hi Ryan, Yep you are right, the query is correct. But I am not sure whether you can can test this issue in your production org. If you have a full copy sandbox which holds as much data as Production now, you can test it there. Thanks.
Ryan BallRyan Ball
Thank you Mythili, I'm going to refresh my sandbox and try and test this again. You have been a great help. I will respond here with my results!
MythiliMythili
Great thanks! Please make sure that it is a full copy sandbox.
Ryan BallRyan Ball
Hi Mythili,

So I'm not sure if it worked or not - My issue is even without the Limit adjustment the trigger still operated properly in my sandbox enviroment. I'm going to have to test it during off hours in production to see if it actually worked.

Another option would be to bulkify it, which is the suggustion that I am seeing on all of the posts I've looked at. That however is above my skill level, so I would have to enlist help to acomplish that.

Thank you for all of your help, I will comment again if I run into any issues on my after hours test.

Ryan