Bulkification Apex - Muitas consultas SOQL: 101

Oct 05 2020

Tenho uma classe APEX que ampliei, mas ainda está me causando problemas com lotes de 200 registros. Eu esperava obter assistência para tornar o APEX mais eficiente, qualquer assistência / orientação seria muito apreciada.

Eu tenho um construtor de processo que dispara uma classe Apex no objeto principal quando um dos seguintes é atendido:

        ISCHANGED([Lead].MobilePhone),
        ISCHANGED([Lead].Phone),
        ISCHANGED([Lead].Home_Phone__c),
        ISCHANGED([Lead].Alternate_Phone__c),
        ISCHANGED([Lead].Alternate_Phone_2__c),
        ISNEW()

O Process Builder passa o [Lead] .Id para a classe APEX.

public class UpdatePhoneTrimFields {

    @InvocableMethod
    public static void invocableTrimPhoneNumbers(List<String> ids){

        String objectType = ids[0];

        if(objectType.startsWithIgnoreCase('00Q')){

            updateBulkifiedLeadPhoneTrimFields(ids); 
        }
        else if(objectType.startsWithIgnoreCase('003')){

            updateBulkifiedContactPhoneTrimFields(ids);
        } 
        else if(objectType.startsWithIgnoreCase('001')){

            updateBulkifiedAccountPhoneTrimFields(ids);
        } 
        else {

            system.debug('Unknown Record Type - Cannot Proceed');
        }

    }

    private static Decimal normalizePhoneNumber(String rawPhoneNumber){
        try{
            if(rawPhoneNumber != null){
                return Decimal.ValueOf(rawPhoneNumber.replaceAll('[^0-9]', ''));
            }
        } catch (Exception e){

            System.debug('An Unexpected error occurred trimming a phone number: ' + e.getMessage());
        }
        return null;
    }

    private static void updateBulkifiedLeadPhoneTrimFields(List<String> ids){
        
        system.debug('Attempting to Normalize the Phone numbers for a list of ' + ids.size() + ' leads.');

        list<Lead> leadsToUpdate = new List<lead>(); 

        for(Id recordId: ids){
            
            String stringRecordId = Id.ValueOf(recordId);

            if(stringRecordId.startsWithIgnoreCase('00Q')){
                try {
                    Lead l = [SELECT Phone, MobilePhone, Home_Phone__c, Alternate_Phone__c, Alternate_Phone_2__c, Phone_Trim__c, MobilePhone_Trim__c, Home_Phone_Trim__c, Alternate_Phone_Trim__c, Alternate_Phone_2_Trim__c FROM Lead WHERE Id =: recordId];

                    l.Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Phone);
                    l.MobilePhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.MobilePhone);
                    l.Home_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Home_Phone__c);
                    l.Alternate_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone__c);
                    l.Alternate_Phone_2_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone_2__c);

                    system.debug('Adding Lead to List to Normalize: '+ l);
                    leadsToUpdate.add(l);
                }catch(Exception e){
                    System.debug('An Unexpected error occurred normalizing Lead Phone Fields: ' + e.getMessage());
                }
            } else {
                System.debug('Cannot Normalize Phone for this record: ' + recordId + '. Not a Lead');
            }   
        }

        try {
            system.debug('Updading a list of Leads with ' + leadsToUpdate.size() + ' records');
            system.debug(leadsToUpdate);
            update leadsToUpdate; 
        } catch (Exception e) {
            System.debug('An Unexpected error occurred normalizing Lead Phone Fields: ' + e.getMessage());
        }
    }

    private static void updateBulkifiedContactPhoneTrimFields(List<String> ids){

        system.debug('Attempting to Normalize the Phone numbers for a list of ' + ids.size() + ' Contacts.');

        list<Contact> contactsToUpdate = new List<Contact>();

        for(Id recordId: ids){

            String stringRecordId = Id.ValueOf(recordId);

            if(stringRecordId.startsWithIgnoreCase('003')){
                try{
                    Contact c = [SELECT Phone, MobilePhone, HomePhone, OtherPhone, Phone_Trim__c, MobilePhone_Trim__c, HomePhone_Trim__c, OtherPhone_Trim__c FROM Contact Where Id =: recordId];

                    c.Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(c.Phone);
                    c.MobilePhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(c.MobilePhone);  
                    c.HomePhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(c.HomePhone);
                    c.OtherPhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(c.OtherPhone);

                    system.debug('Adding Contact to List to Normalize: '+ c);
                    contactsToUpdate.add(c);

                }catch(Exception e){
                    System.debug('An Unexpected error occurred normalizing Lead Phone Fields: ' + e.getMessage());
                }
            }else {
                System.debug('Cannot Normalize Phone for this record: ' + recordId + '. Not a Contact');
            }
        }

        try {
            system.debug('Updating List of Contacts with ' + contactsToUpdate.size() + ' records');
            system.debug(contactsToUpdate);
            update contactsToUpdate;
        } catch (Exception e) {
            System.debug('An Unexpected error occurred trimming Contact Phone Fields: ' + e.getMessage());
        }
    }

    private static void updateBulkifiedAccountPhoneTrimFields(List<String> ids){

        system.debug('Attempting to Normalize the Phone numbers for a list of ' + ids.size() + ' Accounts.');

        list<Account> accountsToUpdate = new List<Account>();

        for(Id recordId: ids){

            String stringRecordId = Id.ValueOf(recordId);

            if(stringRecordId.startsWithIgnoreCase('001')){
                try{
                    Account a = [SELECT Phone, Alternate_Phone__c, Alternate_Phone_2__c, Phone_Trim__c, Alternate_Phone_Trim__c, Alternate_Phone_2_Trim__c FROM Account WHERE Id =: recordId];
            
                    a.Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(a.Phone);
                    a.Alternate_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(a.Alternate_Phone__c);
                    a.Alternate_Phone_2_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(a.Alternate_Phone_2__c);
            
                    system.debug('Adding Account to List to Normalize: '+ a);
                    accountsToUpdate.add(a);
                }catch(Exception e) {
                    System.debug('An Unexpected error occurred normalizing Account Phone Fields: ' + e.getMessage());
                }
            } else {
                System.debug('Cannot Normalize Phone for this record: ' + recordId + '. Not an Account');
            }
        }

        try {
            system.debug('Updating List of Accounts with ' + accountsToUpdate.size() + ' records');
            system.debug(accountsToUpdate);
            update accountsToUpdate;
        } catch (Exception e)   {
            System.debug('An Unexpected error occurred trimming Account Phone Fields: ' + e.getMessage());
        }      
    }

}
  1. Esta classe Apex pode ser disparada a partir de três construtores de processos relacionados ao objeto Lead / Account / Contact. Portanto, o construtor Process chama um @invocablemethod que determina o Tipo de Objeto. Com base no tipo de objeto, ele chamará um método privado.
  2. Este método privado é updateBulkifiedLeadPhoneTrimFields. Ele percorre o array / lista de registros - procura as informações apropriadas, atualiza o registro e o envia para um novo array. Assim que o Loop estiver concluído, atualizamos a lista de registros juntos.
  3. As atualizações que estão sendo feitas são as mesmas em todos os métodos - isso atualiza os campos de fonetrim (normaliza os campos de telefone). O método chamado é normalizePhoneNumber

updateBulkifiedLeadPhoneTrimFields

private static void updateBulkifiedLeadPhoneTrimFields(List<String> ids){

        system.debug('Attempting to Normalize the Phone numbers for a list of ' + ids.size() + ' leads.');

        list<Lead> leadsToUpdate = new List<lead>(); 

        for(Id recordId: ids){

            String stringRecordId = Id.ValueOf(recordId);

            if(stringRecordId.startsWithIgnoreCase('00Q')){
                try {
                    Lead l = [SELECT Phone, MobilePhone, Home_Phone__c, Alternate_Phone__c, Alternate_Phone_2__c, Phone_Trim__c, MobilePhone_Trim__c, Home_Phone_Trim__c, Alternate_Phone_Trim__c, Alternate_Phone_2_Trim__c FROM Lead WHERE Id =: recordId];

                    l.Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Phone);
                    l.MobilePhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.MobilePhone);
                    l.Home_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Home_Phone__c);
                    l.Alternate_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone__c);
                    l.Alternate_Phone_2_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone_2__c);

                    system.debug('Adding Lead to List to Normalize: '+ l);
                    leadsToUpdate.add(l);
                }catch(Exception e){
                    System.debug('An Unexpected error occurred normalizing Lead Phone Fields: ' + e.getMessage());
                }
            } else {
                System.debug('Cannot Normalize Phone for this record: ' + recordId + '. Not a Lead');
            }   
        }

        try {
            system.debug('Updading a list of Leads with ' + leadsToUpdate.size() + ' records');
            system.debug(leadsToUpdate);
            update leadsToUpdate; 
        } catch (Exception e) {
            System.debug('An Unexpected error occurred normalizing Lead Phone Fields: ' + e.getMessage());
        }
    }

normalizePhoneNumber

    private static Decimal normalizePhoneNumber(String rawPhoneNumber){
        try{
            if(rawPhoneNumber != null){
                return Decimal.ValueOf(rawPhoneNumber.replaceAll('[^0-9]', ''));
            }
        } catch (Exception e){

            System.debug('An Unexpected error occurred trimming a phone number: ' + e.getMessage());
        }
        return null;
    }

Ao executar em lotes de 200, estou recebendo o seguinte erro: An Apex error occurred: System.LimitException: Too many SOQL queries: 101 ---

Questões)

  1. Existe uma maneira de tornar isso mais eficiente? Se sim, como? Meu objetivo final seria que isso pudesse ser executado em lotes de 200 registros.

Atualizar

Fiz a seguinte alteração e parece que funcionou. Alguma recomendação sobre mais atualizações?

        
        system.debug('Attempting to Normalize the Phone numbers for a list of ' + ids.size() + ' leads.');

            List<Lead> leadsToUpdate = [SELECT Id, Phone, MobilePhone, Home_Phone__c, Alternate_Phone__c, Alternate_Phone_2__c, Phone_Trim__c, MobilePhone_Trim__c, Home_Phone_Trim__c, Alternate_Phone_Trim__c, Alternate_Phone_2_Trim__c FROM Lead WHERE Id =: ids]; 

            for(Lead l: leadsToUpdate){
                try{
                    l.Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Phone);
                    l.MobilePhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.MobilePhone);
                    l.Home_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Home_Phone__c);
                    l.Alternate_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone__c);
                    l.Alternate_Phone_2_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone_2__c);
                } catch(Exception e){
                    System.debug('An Unexpected error occurred normalizing Lead with Id: ' + l.Id + '. Error' + e.getMessage());
                }
            }
            system.debug('Updading a list of Leads with ' + leadsToUpdate.size() + ' records');
            system.debug(leadsToUpdate);
            update leadsToUpdate; 
    }

Respostas

3 DerekF Oct 05 2020 at 20:09

O problema é que você tem consultas dentro de um loop.

Em sua tentativa de aumentar o volume, vemos isso

for(Id recordId: ids){
    String stringRecordId = Id.ValueOf(recordId);

    if(stringRecordId.startsWithIgnoreCase('00Q')){
        try {
            // This query is inside of a loop
            // That's a problem
            Lead l = [SELECT Phone, MobilePhone, Home_Phone__c, Alternate_Phone__c, Alternate_Phone_2__c, Phone_Trim__c, MobilePhone_Trim__c, Home_Phone_Trim__c, Alternate_Phone_Trim__c, Alternate_Phone_2_Trim__c FROM Lead WHERE Id =: recordId];
   // ...

Como você só tem um único filtro em sua consulta, é fácil sair do loop usando a abordagem padrão iterate-collect-query.

// First, set up a collection to hold the data you want to query against
Set<Id> recordIds = new Set<Id>();

// Iterate over your records to gather the data
for(SomeObject__c someObj :trigger.new){
    // gather your data
    recordIds.add(someObj.Relationship_Field__c);
}

// Query and store for later use
Map<Id, SObject> sobjMap = new Map<Id, SObject>([SELECT Id, Field1__c, Field2__c FROM OtherObject__c WHERE Id IN :recordIds);

// Iterate over the records again
// This time, you can pull the related data from the map you made earlier
for(SomeObject__c someObj :trigger.new){
    OtherObject__c relatedRecord = sobjMap.get(someObj.Relationship_Field__c);

    // do other work
}

Você provavelmente não precisa passar por tantos problemas, pois você está trabalhando apenas com 1 SObject por vez, não deve haver nada que o impeça de simplesmente alimentar um loop for com uma consulta que usa sua idslista diretamente .

for(Lead myLead :[SELECT Id, Field2__c, Field3__c FROM Lead WHERE Id IN :ids]){
    // do work
}

Além disso ...

  • você pode considerar o uso do getSObjectType()método da Idclasse para tornar a detecção de SObject mais legível
    • por exemplo SObjectType sobjType = ((Id)ids[0]).getSObjectType();eif(sobjType == Lead.SObjectType)
  • Engolir exceções geralmente é uma coisa ruim. Isso torna mais difícil detectar quando você tem um problema e torna mais difícil depurar o problema quando você sabe que tem um. Considere eliminar todas as suas instruções try / catch