0

I have a trigger class that updates an Account checkbox field as either true or false depending upon whether or not any of the related records in a custom object contain a certain value. All works fine, but I am trying to get the Account field to update as FALSE if the custom object related list is empty after a record is deleted. I imagine this is fairly simple, but I'm stumped how to do it. Can anyone help?

Utility class:

public static void deleteAccountImpacted(List<Encounters__c> oldEncounterList){
    List<KeyWords__c> listKeywords = [SELECT Name FROM KeyWords__c];
    
    Set<Id> accountIds = new Set<Id>();
    Set<String> keyWords = new Set<String>();
    
    for(KeyWords__c listKW : listKeywords){
        keyWords.add(listKW.Name);
    }
    
    for(Encounters__c encounter : oldEncounterList){
        if(encounter.Account__c != null){
            accountIds.add(encounter.Account__c);
        }
    }
    
    Map<Id,Account> accountMap = new Map<Id,Account>([SELECT Id,Impacted__c FROM Account WHERE Id IN: accountIds]);
    
    integer impact = 0;
    integer counter = 0;
    List<Encounters__c> encList = [SELECT Id, Account__c, Notes__c, Reason__c FROM Encounters__c WHERE Account__c IN: accountIds];

    for(Encounters__c encounter2 :encList){
        counter += 1;
        Account acct = accountMap.get(encounter2.Account__c);
        if(encounter2.Notes__c != null || encounter2.Reason__c != null){
            for(String kw :keyWords){
                if((encounter2.Notes__c != null && encounter2.Notes__c.contains(kw) == TRUE) || (encounter2.Reason__c != null && encounter2.Reason__c.contains(kw) == TRUE)){
                    impact += 1;                                    
                    break;
                }
            }
        }

        if(impact < counter || encounter2 == null){
            acct.Impacted__c = FALSE;
            break;
        }else{
            acct.Impacted__c = TRUE;
        }
    }
    update accountMap.values();
}
3
  • Its not really clear from the code snippet as to what you intended + there are few flaws in your code snippet. For example, (1) Why use encounter2.Notes__c != null twice in the code? it's a redundant check. (2) If your first Encounters__c record's Notes__c & Reason__c did not have any of the keywords, then impact < counter will be TRUE and the execution will break out of the outer FOR loop, thereby not processing rest of the Encounters__c records. I'm not sure if this was your requirement. I would suggest you to improve the post with relevant details.
    – arut
    Dec 12 '20 at 6:45
  • Just small tip - use debug log/add system.debug statement if needed and walk thru the values of impact/counter/Note__c. See if the values during run time are the values you expecting to get.
    – Liron C
    Dec 12 '20 at 8:16
  • Rollup summary field of MD then workflow rule to set value of checkbox??
    – Eric
    Dec 12 '20 at 8:26
2

Handling the deletion case (more specifically, handling the case where the last child record is deleted or otherwise becomes unrelated to a given record) is a part of the reason why the general recommendation is to just use rollup summary fields or DLRS.

It's not particularly hard to handle, it's just an extra consideration that needs to be made. The basic idea (which you seem to already be using) is that you need to figure out which parent records are affected before trying to gather all of the children. The reason being that if you simply do something like [SELECT ParentId, SUM(child_field__c) FROM Child__c GROUP BY ParentId], you won't get any results for parents that no longer have any children.

The general approach to take here sounds something like this:

  • Declare a map to hold parent records (to be updated)
  • Gather the parent Ids
  • As you're gathering parent Ids, add the default "no children" state to the map
  • Query for the child records
  • Iterate over child records, and update the record in the parent map accordingly
  • Perform DML on the parent records

Another way to think of that is that we first assume the parent record is in some base state, and then we try to prove that it shouldn't be in that state. If there are no child records to work with, then the parent will remain in the base state.

Applying this to your scenario...

Set<String> keywordsSet = new Set<String>();
// A query can be used to directly feed a loop
// This is called a "SOQL for loop", and is the generally recommended way to handle
//   query results (Salesforce can do internal optimizations, and keep the impact
//   on heap space used to a minimum)
for(Keywords__c kw :[SELECT Name FROM Keywords__c]){
    keywordsSet.add(kw.Name);
}

Map<Id, Account> impactedAccountMap = new Map<Id, Account>();

for(Encounters__c encounter : oldEncounterList){
    // You could do a check for Account__c == null here, but you don't really need to.
    // We can just remove the "null" key from the map after the loop is done.
    // The other thing worth mentioning is that you don't need to query for the parent
    //   records (having the record Id is all we need to have to do a DML update)
    impactedAccountMap.put(encounter.Account__c, new Account(Id = encounter.Account__c, Impacted__c = false));
}

impactedAccountMap.remove(null);

// Using a parent-child subquery here is possible when you're not using Aggregate Functions
//  (SUM(), MAX(), AVG(), etc...) to do your rollup.
// You don't need to do things this way, but I think it's nice (and makes my code simpler)
//   when I let SOQL take care of grouping children under their parents.
// I'm guessing that the child relationship name is "Encounters__r", you may need to
//   adjust this.
for(Account acct :[SELECT Id, (SELECT Id, Notes__c, Reason__c FROM Encounters__r) FROM Account WHERE Id IN :impactedAccountMap.keySet()]){
    for(Encounters__c encounter :acct.Encounters__r){
        // Some string processing can help make keyword detection simpler and faster.
        // Assuming Notes and Reason contain multiple words (and punctuation), if
        //   we get rid of the punctuation, we can use String.split to create a List
        //   of words used.
        // That can be fed into a Set, and then we can use Set methods to do the heavy
        //   lifting for us.
        Set<String> notesAndReasonsWords = new Set<String>();

        if(String.isNotBlank(encounter.Notes__c)){
            // \p{Punct} is the shorthand for the punctuation character class
            //   in Java regexp (and any escaped characters need an extra '\' in Apex)
            notesAndReasonsWords.addAll(encounter.Notes__c.replaceAll('\\p{Punct}', '').split('\\s+'));
        }

        if(String.isNotBlank(encounter.Reasons__c)){
            notesAndReasonsWords.addAll(encounter.Reasons__c.replaceAll('\\p{Punct}', '').split('\\s+'));
        }

        // No need to track counts here
        // As soon as one child record indicates the parent Account is impacted, we
        //   can stop processing the rest of the children (we've proven that our
        //   assumed initial state is wrong, and have corrected it)
        // The removeAll() method returns TRUE if it modifies the set it was called on.
        // In other words, if removeAll() returns true, then notesAndReasonsWords contained
        //   at least one keyword.
        if(notesAndReasonsWords.removeAll(keywordsSet)){
            impactedAccountMap.get(acct.Id).Impacted__c = true;
            break;
        }
    }
}

update impactedAccountMap.values();

33 lines here (not counting my comments) vs 43 lines in your original and, perhaps more importantly, 2 queries vs 3 queries.

3
  • Thanks for the explanation and code adjustment. I haven't used the String.split function before and I am getting errors for those 2 lines: Method does not exist or incorrect signature: void split(String, String) from the type String. What does that mean, is it just incorrect syntax or something different?
    – J. Neilan
    Dec 12 '20 at 20:15
  • 1
    @J.Neilan I got the syntax wrong (I conflated the split() and join() methods), and have edited my answer to fix it (I hope). A method signature tells you 1) The return type. 2) The name of the method. 3) The argument types it takes. If you try to pass too many arguments to a method, too few arguments, or arguments of the wrong type, you'll get that error. It's basically a small nudge saying "hey, I don't know what you're trying to do here. Double check to make sure you're calling the method correctly".
    – Derek F
    Dec 13 '20 at 0:23
  • That did the trick! Thanks so much for all your help.
    – J. Neilan
    Dec 13 '20 at 4:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.