Another approach that's situated between the two existing ones (rollup to the account + some formula fields, and sort ascending + overwrite map values) could be to use an extra query + some extra heap space to reduce the amount of rows/work that SOQL needs to process.
Since Process_Date__c
takes priority here, if you figure out what the most recent date is per account then you can (with the help of a formula field) ignore the rest of the child records on that that don't have that date.
"Account_plus_date__c" (formula field on Commissions_Import__c
, Return Type: "Text")
Provider_Account_relationship__c & '-' & TEXT(Process_Date__c)
Important note here: TEXT()
always turns a date into "YYYY-MM-DD" format, regardless of the user's locale.
// Step 1: figure out the max date per account, and store that information
Set<String> accountMaxDates = new Set<String>();
// We can LIMIT 1 here in the subquery to reduce the number of rows returned
// We're just interested in getting the AccountId + date combo here
for(Account acct :[SELECT Id, (SELECT Id, Process_Date__c FROM Commissions_Imports__r ORDER BY Process_Date__c DESC LIMIT 1) FROM Account WHERE Id IN :accounts]){
// Taking care to match the "YYYY-MM-DD" format for the date and
// being careful not to mix local time & GMT/UTC processing
accountMaxDates.add(String.format('{0}-{1}', new List<String>{
acct.Id,
// Directly accessing the child record list like this is ok here because
// the "LIMIT 1" ensures we will never encounter a "queryMore"
Datetime.newInstance(acct.Commissions_Imports__r.get(0)?.Process_Date__c, Time.newInstance(0, 0, 0).format('YYYY-MM-DD')
}));
}
// Step 2: Do the summation only for the account-date pairs we previously found
// (to reduce the work and rows that this query needs to process)
Map<Id, Account> accountsToUpdate = new Map<Id, Account>();
// Generally better to use `__c` rather than `__r.Id`
// If Salesforce complains about query selectivity, then just capture the Account Ids
// in a separate collection in the loop above and then add it as a filter to this query
// We should only need to order by revenue date here (since the previous step already
// got our target process dates)
for(AggregateResult agg :[SELECT Provider_Account_relationship__c, SUM(Commission_Received__c) commission FROM Commissions_Import__c WHERE Account_plus_date__c IN :accountMaxDates GROUP BY Provider_Account_relationship__c ORDER BY Revenue_Date__c ASC){
Id accountId = (Id)agg.get('Provider_Account_relationship__c'));
accountsToUpdate.put(accountId, new Account(
Id = accountId,
Last_Commission__c = (Double)agg.get('commission')
));
}
You could also ORDER BY Revenue_Date__c DESC
and then skip the row if the Account has already been seen instead
for(AggregateResult agg :[SELECT Provider_Account_relationship__c, SUM(Commission_Received__c) commission FROM Commissions_Import__c WHERE Account_plus_date__c IN :accountMaxDates GROUP BY Provider_Account_relationship__c ORDER BY Revenue_Date__c DESC){
Id accountId = (Id)agg.get('Provider_Account_relationship__c'));
if(accountsToUpdate.containsKey(accountId)){
// skips the rest of this iteration and moves to the next result row
continue;
}
accountsToUpdate.put(accountId, new Account(
Id = accountId,
Last_Commission__c = (Double)agg.get('commission')
));
}
The tradeoffs here are:
- you're burning an extra query
- but you'd likely use more queries if you used DLRS or a rollup
summary field, so this is likely query neutral (or maybe even net negative) compared to that approach
- still one more query than the "sort asc + overwrite map" approach
- you're using a bit of extra heap space to store the Account Ids + dates
- but it keeps everything on
Commissions_Import__c
- and it reduces the number of rows that need to be queried/processed (though not as much as the rollup + 2 formula approach does)