Hoje iremos entender o porque devemos evitar o uso instruções DML e SOQL dentro de laços de iteração no Apex.

Como vocês sabem o Salesforce é uma plataforma completamente WEB, ou seja, temos que sempre otimizar o funcionamento para que tenhamos um bom desempenho e sempre proporcionar uma boa experiência ao usuário.

Durante esse período de quase 2 anos atuando com desenvolvimento Salesforce me deparei muitas vezes com um “erro” comum e grave por parte de desenvolvedores que é o uso de instruções SOQL e DML dentro de laços ou loops, como preferirem chamar.

Você saberia me dizer o por que não devemos adotar essa prática em nossos desenvolvimentos? Caso saiba, parabéns você é uma pessoa que vai deixar o próximo Desenvolvedor da sua organização muito feliz, aos que não sabem, chegou a hora de descobrir.

Limitações de “Banco” Salesforce e onde isso se aplica?

Dentro do Salesforce temos diversas limitações, e algumas delas são referentes às operações DML e SOQL por transações do Apex. Abaixo uma tabela com as limitações que iremos abordar nesse post:

Limitação

Quantidade

Erro (Exceder o Limite)

Number of SOQL queries(Quantidade de operações SOQL que podemos executar em uma transação) 100 Too many SOQL queries: 101
Number of DML statements(Quantidade de operações DML que podemos executar em uma transação) 150 Too many DML statements: 151

Essas informações podem ser confirmadas no Developer Console ou clicando aqui.

Vocês devem estar pensando “Nossa, mas 100 operações SOQL e 150 operações DML são muita coisa, como isso pode afetar o funcionamento da minha organização?”. A resposta é simples, para um processo comum que seja referente a um ou dois registros não fará diferença, mas lembre-se que o Salesforce nos permite fazer carga de dados em grande quantidade quando necessário, essas cargas são conhecidas como Bulk, e é nesse caso que mora o perigo.

Não é padrão mas cada organização possui ou não regras para a inserção de um tipo específico de registro em um objeto, e algumas dessas regras podem ser validadas por uma Trigger (Acionador do Apex) validando-as registro por registro, por isso devemos fazer com que essas regras funcionem para UM ou MAIS registros.

Os problemas que envolvem operações SOQL e DML dentro de laços sempre estouram em Bulk pois passam por validações de uma trigger.

Caso queira saber mais sobre Trigger existe um módulo específico no Trailhead onde você poderá ganhar uma noção básica, chamado Acionadores do Apex e um também sobre Bulk chamado Large Data Volumes

Esses problemas são mais frequentes em triggers pois é onde podemos trabalhar com uma quantidade de dados muito grande quando necessário, realizando consultas, atualizações, deleções ou inserções para 1 ou mais objetos e registros ao mesmo tempo. Mas não é por que esses erros acontecem frequentemente nas triggers que devemos deixar as classes Apex de lado, podemos trabalhar com grandes quantidades de dados em classes também ou até mesmo fazer com que uma trigger chame uma classe do Apex para realizar alguma operação se necessário.

Vamos ver um exemplo!

Vamos verificar um exemplo simples?

Digamos que sua organização é pequena e você precisa cadastrar apenas um cliente(1), e para realizar e conseguir efetuar esse cadastro, existe uma trigger que realiza uma verificação se esse cliente já não existe na base(2), caso não exista procede com a inserção, um processo simples de cadastro.

Como pode ser visto anteriormente numerei duas operações:

Cadastro do cliente (1) – Operação DML.

Consulta na Base (2) – Operação SOQL.

Vale reforçar que o cenário que estamos vendo aqui não se aplica apenas para a inserção de registros, mas sim para todas as operações DML e SOQL

Caso queira saber mais sobre DML, SOQL e Classes do Apex existe um módulo específico no Trailhead onde você poderá ganhar uma noção básica, chamado Banco de dados e noções básicas do Apex

Abaixo iremos ver uma exemplificação de um código do processo citado acima e que já encontrei em algumas de minhas correções e que gerou muitos problemas para a organização:

for(Cliente__c cliente : listaCliente) {
 //SELECT DENTRO DO FOR, VERIFICA O CLIENTE A CADA ITERAÇÃO
 List<Cliente__c> verificaCliente = [SELECT Id, Name FROM Cliente__c WHERE CPF__c=:cliente.cpf];
 if(verificaCliente != null){
  //INSERT DENTRO DO FOR, INSERE O CLIENTE A CADA ITERAÇÃO
  insert cliente;
 }
}

Para um cadastro de um único registro, não teríamos nenhum problema pois estaríamos dentro dos limites e com folga, correto?

Agora vamos imaginar que estamos fazendo uma inclusão de 500 novos clientes, nesse exato momento passamos a ter 500 operações de cada (DML e SOQL), ultrapassando assim os limites impostos pela plataforma Salesforce e impactando no funcionamento do sistema, de nossa organização, gerando novos problemas para o futuro e possivelmente impacto financeiro.

Como posso evitar que isso aconteça?

Agora vamos para a parte divertida do nosso post, que é a explicação de como fazer isso da forma correta para evitar esses futuros problemas.

Utilizando o trecho de código anterior, você consegue identificar uma forma de melhorá-lo para que não seja mais feito uma consulta e uma inserção para cada iteração do FOR?

Caso não tenha conseguido visualizar, irei te ajudar nesse desafio, abaixo tentarei exemplificar de uma forma de fácil entendimento:

//Set usado para consultar os clientes.
Set<String> setCpf = new Set<String>();

//Lista usada para incluir os clientes que não foram encontrados.
List<Cliente__c> insertCliente = new List<Cliente__c>();

//Map usado para identificar o clientes que já são cadastrados.
Map<String, Cliente__c> mapValidacaoClientes = new Map<String, Cliente__c>();

//Inclui os CPFs que serão consultados no Set.
for (Cliente__c cliente : listaCliente) {
   setCpf.add(cliente.cpf);
}

//Realiza consulta dos clientes pelo CPF.
List<Cliente__c> verificaClienteLista = [SELECT Id, Name, CPF__C FROM Cliente__c WHERE CPF__c IN:setCpf];

//Popula o Map com os clientes que já estão cadastrados.
for (Cliente__c clienteCadastrado : verificaClienteLista) {
   map.put(clienteCadastrado.CPF__c, clienteCadastrado);
}

//Verifica se o cliente está presente no map(cadastrado), caso não esteja ele é adiciona na lista de inserção.
for (Cliente__c cliente : listaCliente) {
   if (!mapValidacaoClientes.get(cliente.CPF__C)) {
      insertCliente.add(cliente);
   }
}

//Insere os clientes que não são cadastrados.
insert insertCliente;

Como pode ser visto no exemplo acima, passamos a realizar a consulta de TODOS os clientes fora do FOR de uma única vez, conseguindo que de agora em diante façamos apenas UMA operação SOQL independente da quantidade de clientes estejam sendo cadastrados, e no final realizamos a inclusão de uma ÚNICA lista contendo todos os clientes que estão aptos de serem incluídos e não mais um INSERT para cada cliente, obtendo o mesmo resultado para uma inclusão simples ou para uma inclusão em massa.

É nítido que passamos a ter um código um pouco mais complexo e mais trabalhoso, porém garantimos que no futuro não teremos problemas de inclusão em massa caso necessário e evitaremos perca de tempo desnecessária para correção e adequação de código.

Indo um pouco além…

Em nosso exemplo realizamos uma inserção simples onde só é verificado se o cliente já existe, mas podemos nos deparar com outros diversos cenários, por exemplo, verificar se o cliente existe na base e caso exista, analisar se existem diferenças de dados e realizar um UPDATE no cliente cadastrado para atualizar algumas informações existentes. Caso desejássemos incluir esse cenário em nosso exemplo teríamos uma TERCEIRA operação e se nós seguirmos a linha de solução aqui apresentada ainda não teríamos problemas pois estamos dentro dos limites da plataforma.

E também devemos salientar que em nosso exemplo trabalhamos com apenas UM objeto e em um ambiente produtivo podemos trabalhar com N objetos realizando diversas operações SOQL ou DML ao mesmo tempo.

Espero ter alertado a você os problemas que esse tipo de prática pode trazer, para você e sua equipe, e além disso ajudar a vocês a resolverem esse problema caso estejam passando por isso.

Um grande abraço e até a próxima!

 

Igor Bressan

Salesforce Developer at Connekt

Bacharel em Ciência da Computação pela Universidade São Judas Tadeu.

Atuando a 4 anos com sustentação e pequenos projetos, em 2014 iniciei minha experiência com sustentação desenvolvendo em Informix 4GL.
Em 2016 iniciei com Salesforce atuando em sustentação novamente, onde tive a oportunidade de aprender sobre Desenvolvimento e Administração da plataforma, e desde então mergulhei de vez nesse mundo o que me ajudou a conquistar minha certificação Platform Developer I.

Acompanhe meu Trailhead aqui.