Você precisa conhecer o ArrayDML do FireDAC!

Você precisa conhecer o ArrayDML do FireDAC!

Olá, leitores!
Voltei com mais uma dica para vocês. Dessa vez, o assunto é FireDAC!
Se você já utiliza essa tecnologia de conexão e insere grandes volumes de dados na sua aplicação, convido-o a ler este artigo!

 

Quem um dia já não precisou inserir 5, 10, 50 mil registros no banco de dados de uma vez só? Essa necessidade é relativamente comum em ambientes de migração, ambientes centralizados, tabelas associativas (de junção) ou até mesmo por conta da regra de negócio do cliente.
A solução pode parecer um tanto quanto óbvia: inserir um registro de cada vez dentro de um loop. Porém, não é tão óbvia assim. O FireDAC traz um recurso muito útil, chamado ArrayDML, que permite a execução de instruções SQL em lote, reduzido radicalmente o tempo decorrido com a operação.

 

Exemplo

Para exemplificar este recurso, gerei um mock com 10 mil linhas em formato CSV, composto pelos campos “ID”, “Name”, “Email”, “Company”, “Occupation”, “City” e “University”. A intenção é percorrer o arquivo e inserir cada linha em uma tabela de um banco de dados local. Para isso, utilizaremos um componente TFDQuery com a seguinte instrução SQL:

INSERT INTO Tabela (ID, Name, Email, Company, Occupation, City, University)
VALUES (:ID, :Name, :Email, :Company, :Occupation, :City, :University)

 

A princípio, faríamos essa funcionalidade de uma forma, digamos, “tradicional”, executando um INSERT para cada linha, conforme o código a seguir. Apenas como fator de parâmetro, adicionei duas linhas recebendo a data e hora atual (Now) para calcular o tempo despendido na operação:

var
  lStringListFile: TStringList;
  lStringListLine: TStringList;
  lLine: string;
  lStart, lEnd: TDateTime;
begin
  // Armazena o início da operação
  lStart := Now;
 
  // TStringList que carrega todo o conteúdo do arquivo
  lStringListFile := TStringList.Create;
 
  // TStringList que carrega o conteúdo da linha
  lStringListLine := TStringList.Create;
  try
    // Carrega o Mock
    lStringListFile.LoadFromFile('MOCK.csv');
 
    for lLine in lStringListFile do
    begin
      lStringListLine.StrictDelimiter := True;
 
      // TStringList recebe o conteúdo da linha atual
      lStringListLine.CommaText := lLine;
 
      // Preenche os parâmetros da Query
      FDQuery.ParamByName('ID').AsString := lStringListLine[0];
      FDQuery.ParamByName('Name').AsString := lStringListLine[1];
      FDQuery.ParamByName('Email').AsString := lStringListLine[2];
      FDQuery.ParamByName('Company').AsString := lStringListLine[3];
      FDQuery.ParamByName('Occupation').AsString := lStringListLine[4];
      FDQuery.ParamByName('City').AsString := lStringListLine[5];
      FDQuery.ParamByName('University').AsString := lStringListLine[6];
 
      // Executa o comando INSERT
      FDQuery.ExecSQL;
    end;
 
  finally
    lStringListLine.Free;
    lStringListFile.Free;
  end;
 
  // Armazena o fim da operação
  lEnd := Now;
 
  // Exibe o tempo total da operação
  ShowMessage('Time elapsed: ' + FormatDateTime('hh:nn:ss:zzz', lEnd - lStart));
end;

 

Ao executar essa rotina, recebemos o seguinte resultado do tempo:

Tempo das inserções sem utilizar o ArrayDML

 

Em seguida, faremos a mesmo operação, porém, utilizando o ArrayDML. Para isso, é necessário apenas 3 ajustes:

  • Indicar o tamanho do “lote” (ou array), no qual equivale à quantidade de inserções que serão realizadas;
  • Usar métodos de preenchimento de parâmetros no “plural” (por exemplo, AsStrings ao invés de AsString);
  • Chamar o método Execute do componente TFDQuery, informando a quantidade de inserções desejadas.

Essas três alterações no código são apresentadas abaixo:

var
  lStringListFile: TStringList;
  lStringListLine: TStringList;
  lCounter: integer;
  lStart, lEnd: TDateTime;
begin
  // Armazena o início da operação
  lStart := Now;
 
  // TStringList que carrega todo o conteúdo do arquivo
  lStringListFile := TStringList.Create;
 
  // TStringList que carrega o conteúdo da linha
  lStringListLine := TStringList.Create;
  try
    // Carrega o Mock
    lStringListFile.LoadFromFile('MOCK.csv');
 
    // Configura o tamanho do array de inserções
    FDQuery.Params.ArraySize := lStringListFile.Count;
 
    for lCounter := 0 to Pred(lStringListFile.Count) do
    begin
      lStringListLine.StrictDelimiter := True;
 
      // TStringList recebe o conteúdo da linha atual
      lStringListLine.CommaText := lStringListFile[lCounter];
 
      // Preenche os parâmetros em cada posição do array
      FDQuery.ParamByName('ID').AsStrings[lCounter] := lStringListLine[0];
      FDQuery.ParamByName('Name').AsStrings[lCounter] := lStringListLine[1];
      FDQuery.ParamByName('Email').AsStrings[lCounter] := lStringListLine[2];
      FDQuery.ParamByName('Company').AsStrings[lCounter] := lStringListLine[3];
      FDQuery.ParamByName('Occupation').AsStrings[lCounter] := lStringListLine[4];
      FDQuery.ParamByName('City').AsStrings[lCounter] := lStringListLine[5];
      FDQuery.ParamByName('University').AsStrings[lCounter] := lStringListLine[6];
    end;
 
    // Executa as inserções em lote
    FDQuery.Execute(lStringListFile.Count, 0);
  finally
    lStringListLine.Free;
    lStringListFile.Free;
  end;
 
  // Armazena o fim da operação
  lEnd := Now;
 
  // Exibe o tempo total da operação
  ShowMessage('Time elapsed: ' + FormatDateTime('hh:nn:ss:zzz', lEnd - lStart));
end;

 

Agora, observem só o tempo total com o ArrayDML:

Tempo das inserções utilizando o ArrayDML

 

Menos de 1 segundo?!

Na verdade, menos de meio segundo! Com o ArrayDML, as 10 mil inserções foram executadas praticamente 7 vezes mais rápido!
Para evidenciar essa diferença, fiz questão de gravar um pequeno vídeo da execução deste código. Neste vídeo, executei a aplicação em outro computador, então os tempos estão um pouco diferentes.
Pessoal, é a primeira vez que me “aventuro” na gravação de vídeos, portanto, não reparem na qualidade, ok? =D

 

Fico por aqui, leitores!
Um abraço e até breve.


 

14 comentários

  1. Muito bom! Eu mesmo ja passei por essa necessidade de incluir vários registros de uma unica vez e na época não conhecia esse fantástico recurso do FireDAC… Parabéns pelo conteúdo…

    1. Obrigado pelo comentário, Vinicius!
      Eu também tive essa mesma necessidade, mas na época utilizava o DBX. Já migrei boa parte dos meus projetos para FireDAC justamente para tirar proveito de recursos com este. 🙂
      Um abraço!

  2. Parabéns mestre Celestino, realmente quando migrei as replicaçoes para arraydml a carga caiu absurdamente, caso de carga de 5 a 10 minutos cair para 10 segundos.
    Show, forte abraço

    1. Eu que agradeço, Panda!
      A ideia desse artigo partiu de uma necessidade que você tinha, lembra? 🙂
      O recurso realmente é fantástico!
      Abraço!

  3. Boa tarde André,

    Mais uma vez nos surpreendendo com seus artigos, muito bom, parabéns e muito obrigado por doar seu tempo para compartilhar seus conhecimentos.

    Grande abraço.

    1. Opa, muito obrigado, Daniel!
      Agradeço novamente por sempre estar acompanhando as publicações do blog!
      Abração!

    1. Obrigado, Olivetti!
      Gostei bastante deste recurso do FireDAC! 😉

  4. Muito interessante esse recurso.
    Porém me surgiu uma duvida. Se nessas 10 mil inserções algumas delas falharem, irá comprometer todas as outras ?

    1. Boa noite, Everton!
      As falhas nas inserções podem ser capturadas no evento OnExecuteError do componente TFDQuery. Neste evento, é possível definir 3 possíveis comportamentos:
      aeOnErrorUndoAll: A operação é interrompida na primeira falha e todas as inserções são desfeitas;
      aeUpToFirstError: A operação é interrompida na primeira falha e as inserções já realizadas são salvas;
      aeCollectAllErrors: Todas as inserções são realizadas, exceto as que retornaram falha. Neste caso, é possível rastrear o índice de cada inserção que falhou.

      Grande abraço!

  5. Ótimo artigo!
    Me surgiu uma dúvida, existe uma Quantidade máxima de registros para este recurso?

    1. Ótima pergunta, Eduardo!
      Segundo a documentação da Embarcadero, a quantidade máxima de registros depende do banco de dados utilizado. Cada SGBD possui um tamanho máximo específico de pacote de dados.
      De qualquer forma, caso haja um estouro do limite, é possível capturar essa exceção no evento OnExecuteError e exigir uma quantidade menor de inserções.

      Abraço!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *