Bulkification Apex - Muitas consultas SOQL: 101
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());
}
}
}
- 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.
- 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.
- 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)
- 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
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 daIdclasse para tornar a detecção de SObject mais legível- por exemplo
SObjectType sobjType = ((Id)ids[0]).getSObjectType();eif(sobjType == Lead.SObjectType)
- por exemplo
- 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