1

I've just written my second trigger and it seems that I'm needing to learn more about how to optimize my code. My trigger runs through the campaign members of a campaign and changes the member status of any email duplicates (but leaves one) so that an email isn't sent twice to one emails. As a nonprofit many of our donors share an email address in their household so we removing the email addresses from the contacts isn't possible. To help with this, I wrote the trigger below. It worked great in testing on a few hundred campaign members but when I tested it on a few thousand I received the dreaded 'System.LimitException: Apex CPU time limit exceeded'.

Could anyone help me optimize this code? I'd love to learn how and apply this knowledge to future triggers.

trigger CleanEmailAddresses on Campaign (after update) {

    for (Campaign camp : Trigger.new) {

        // Check for clean email addresses trigger
        if (camp.clean_emails__c == true) {

            if (camp.IsActive && camp.Status != 'Completed' && camp.Status != 'Aborted') {

            // Counters used later
            Integer uniqueEmailCount = 0;
            Integer dupeCounter = 0;

            // Initialize list that will hold duplicate email addresses
            List<CampaignMember> theseAreTheDupes = new List<CampaignMember>();

            // Check to see if campaign has member status 'Email Duplicate'
            List<CampaignMemberStatus> campaignStatuses = [SELECT Id,
                                                                  Label,
                                                                  CampaignId
                                                             FROM CampaignMemberStatus
                                                            WHERE CampaignId = :camp.Id AND
                                                                  Label      = 'Email Duplicate'];

            // No? Then create it
            if (campaignStatuses.size() == 0) {

                CampaignMemberStatus newCampaignMemberStatus = new CampaignMemberStatus();
                newCampaignMemberStatus.Label                = 'Email Duplicate';
                newCampaignMemberStatus.CampaignId           = camp.Id;
                insert newCampaignMemberStatus;

            }

            // Create list of email addresses already in campaign that do not have duplicate status
            List<CampaignMember> campaignMembers = [SELECT Id,
                                                           Email,
                                                           Status
                                                      FROM CampaignMember
                                                     WHERE CampaignId   = :camp.Id AND
                                                           Status      != 'Email Duplicate' AND
                                                           HasResponded = false];


            // Initialize string set that will hold list of unique email addresses
            Set<string> uniqueEmails = new Set<string>();


            // Populate set with unique emails
            for (CampaignMember addToUniqueEmailSet : CampaignMembers) {

                uniqueEmails.add(addToUniqueEmailSet.Email);

            }

            // Loop through each unique email address
            for (String uniqueEmailList: uniqueEmails){

                // Loop through email campaign member
                for (Integer i = 0; i < campaignMembers.size(); i++) {


                    // Check to see if current campaign member's email address matches unique address of current iteration
                    if (campaignMembers.get(i).Email == uniqueEmailList) {

                        // Count number of dupes
                        uniqueEmailCount = uniqueEmailCount + 1;

                        // Check to see if duplicate is the first one or note
                        if (uniqueEmailCount > 1) {

                            // If dupe is not the first, add to list of campaign members to change later
                            theseAreTheDupes.add(campaignMembers.get(i));


                        }

                    }

                }

                // Reset counter
                uniqueEmailCount = 0;

            system.debug ('dupe list size: '+ theseAreTheDupes.size()); 

            }

            if (theseAreTheDupes.isEmpty() == false) {

                for (CampaignMember cmChangeStatus : theseAreTheDupes) {

                    theseAreTheDupes.get(dupeCounter).Status = 'Email Duplicate';
                    dupeCounter = dupeCounter + 1;

                }

                update theseAreTheDupes;

            }

            } else {

                camp.addError('Whoa there partner! 🐴  Emails can not be cleaned from inactive, aborted or completed campaigns!');

            }

        }

    }

}
  • You have a few queries in for loops, so even if you resolve the CPU errors this may hit others. That said, this logic may be better moved to a batch/scheduled process. – Adrian Larson May 26 '17 at 16:07
2

Your main problem is the for-for loop in the middle of the code. As commented, this won't solve all of your problems, but you can drastically reduce CPU time by using a map:

Map<String, List<CampaignMember>> membersByEmail = new Map<String, List<CampaignMember>>();
// Prepopulate map
for(String email: uniqueEmails) {
    membersByEmail.put(email, new CampaignMember[0]);
}
// Sort into email groups
for(CampaignMember member: campaignMembers) {
    membersByEmail.get(member.Email).add(member);
}
for(String email: uniqueEmails) {
    CampaignMember[] emailMembers = membersByEmail.get(email);
    // More than 1 member
    if(emailMembers.size() > 1) {
        // First is not a duplicate
        emailMembers.remove(0);
        // The rest of them are
        theseAreDupes.addAll(emailMembers);
    }
}
// Update status
for(CampaignMember member: theseAreDupes) {
    member.Status = 'Email Duplicate';
}
// Commit changes
update theseAreDupes;

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.