[Delphi] Dicas para agilizar a consulta de dados em uma DBGrid

[Delphi] Dicas para agilizar a consulta de dados em uma DBGridOlá, pessoal!
Depois que publiquei os artigos sobre dicas do componente DBGrid e sobre a crueldade de um software lento, recebi algumas perguntas de leitores sobre como melhorar o desempenho de consultas e exibição de dados. O artigo de hoje é uma sugestão dor Artur, um destes leitores, que deixou um comentário recente relacionado a esse assunto. Confira!

 

A lentidão ao carregar dados em uma DBGrid pode ser causada por vários fatores que, muitas vezes, nos passam despercebidos. As seis orientações abaixo podem ser úteis para agilizar o carregamento de dados ou, talvez, para evitar futuras lentidões.

1) Revise a instrução Select que consulta os dados
Assim como mencionei no artigo sobre práticas de otimização em banco de dados, as consultas SQL devem trazer apenas os dados que são necessários na visão da DBGrid. Por exemplo, se existem 10 colunas em uma tabela e somente 4 são exibidas em uma DBGrid, as outras 6 podem (e devem) ser retiradas da consulta. Para isso, substitua o asterisco pelas colunas requisitadas, conforme a comparação abaixo:

-- Seleciona TODAS as colunas da tabela, desnecessário na maioria das consultas
SELECT * FROM PRODUTOS
 
-- Seleciona somente as colunas necessárias para exibir os dados
SELECT Codigo, Descricao, Valor, Qtde FROM PRODUTOS

Além disso, em alguns casos, há tabelas com colunas do tipo BLOB, que armazenam dados binários de imagens e documentos ou textos longos. Se essas colunas forem adicionadas na consulta, o tempo de retorno será ainda mais demorado. A minha recomendação é trazer estes dados sob demanda em uma instrução separada, como comentei no artigo sobre eventos de tela.

 

2) Evite abrir e fechar o DataSet repetidas vezes
Quando for necessário aplicar um filtro nos dados, como um intervalo de datas, procure utilizar as propriedades Filter e Filtered, ao invés de comandos que fazem acesso ao banco, como CommandText, Close/Open e ExecSQL. A explicação é que a propriedade Filter trabalha com dados em memória, isto é, que já foram carregados.
Entrando mais em detalhes técnicos, o ideal é substituir esses tipos de consulta:

DataSet.Close;
DataSet.CommandText := 'Select * from CLIENTES where Nome like ' + QuotedStr(Edit1.Text + '%');
DataSet.Open;

Por filtros como esse:

DataSet.Filter := 'Nome like ' + QuotedStr(Edit1.Text + '%');
DataSet.Filtered := True;

 

3) Atente-se aos eventos do DataSet, dos Fields e de pintura da DBGrid
Por falar em eventos de tela, o desenvolvedor também deve ser prudente ao utilizá-los, já que podem impactar no tempo de carregamento dos dados. Por exemplo, você sabia que o evento OnDrawColumnCell do componente DBGrid é chamado para cada registro que é carregado? Insira um ShowMessage neste evento, abra o DataSet e observe a quantidade de vezes que a mensagem é exibida. Logo, se houver um processamento neste evento, é evidente que os dados levarão um tempo maior para serem carregados.
A mesma orientação é válida para eventos do DataSet conectado à DBGrid, como BeforeOpen, AfterOpen, BeforeGetRecords e AfterGetRecords. Embora sejam executados apenas uma vez ao consultar os dados, podem apresentar lentidões se os processamentos forem extensos.
Seguindo a mesma lógica, o evento OnGetText do TField (campo da tabela), como trata a exibição dos dados de uma coluna, também é executado para cada registro e deve ser observado.

 

4) Adicione filtros na tela para evitar a consulta de vários registros
Muitos desenvolvedores costumam executar a consulta de dados logo quando a tela é aberta. Sendo assim, se houverem 10 mil registros, todos eles serão consultados de uma vez só! Bom, nem preciso comentar que isso é uma falha de desempenho indiscutível, não é?
Além da demora, esse procedimento também não deixa de ser uma questão de usabilidade. Muitas vezes, o usuário entra na tela para visualizar os dados de apenas 1 registro, e precisa esperar, desnecessariamente, a consulta de todos os registros da tabela.
A recomendação é disponibilizar filtros na tela (como código, descrição, tipo, período, etc…), e realizar a consulta somente quando estes filtros forem informados. Com essa alteração, já consigo apontar 3 vantagens: 1) não haverá “gargalos” ao abrir a tela; 2) o usuário visualiza somente o(s) registro(s) desejado(s); 3) reduz o tráfego de dados em um ambiente cliente/servidor.

 

5) Ative a paginação de registros com a propriedade PacketRecords
Quem disse que não dá para fazer paginação com Delphi? A propriedade PacketRecords do DataSet permite definir a quantidade de registros que serão “paginados”, ou carregados por vez.
Faça um teste: preencha a propriedade PacketRecords com o valor 100 e abra um DataSet que tenha aproximadamente 1000 registros. Observe que apenas 100 registros serão exibidos inicialmente na DBGrid, porém, ao navegar até a última linha, os próximos 100 registros da tabela serão carregados e exibidos automaticamente.
Este recurso pode ser bastante útil quando a tela exige a consulta de milhares de registros, por uma questão técnica ou por solicitação do cliente, ou mesmo quando a terceira dica, sobre filtros na tela, é inviável.

 

6) Modere na utilização de componentes de terceiros
Às vezes precisamos de algum comportamento adicional, não existente no componente TDBGrid nativo do Delphi. Como solução, normalmente instalamos componentes de terceiros para atender a nossa necessidade. No entanto, vale ressaltar que estes componentes trazem vários outros comportamentos que, em sua maior parte, pode não nos interessar no momento. Por exemplo, suponha que um componente de terceiro monte toda uma estrutura de agrupamento dinâmico, ative campos Lookup ou faça uma indexação do conteúdo para buscas, quando, o que realmente precisamos, é só um totalizador de valores no rodapé. Por estarem acoplados ao componente, estes outros comportamentos “paralelos” também podem impactar indiretamente no carregamento dos dados.
A minha sugestão é criar um novo componente herdado da classe TDBGrid e adicionar os comportamentos desejados, mesmo que exija um pouco mais de tempo. Além de dispensar a instalação de componentes de terceiros, o desenvolvedor ganha a liberdade de customizar o componente de acordo com suas próprias necessidades.

 

Espero que o artigo seja útil, pessoal!
Grande abraço! 


 

Compartilhe!
Share on FacebookTweet about this on TwitterShare on LinkedInShare on Google+Pin on PinterestEmail this to someone

10 comentários

  1. Saudoso amigo André, gostaria de deixar minha contribuição para o artigo. Prefira utilizar parâmetros nas consultas para aproveitar o cache do banco, principalmente em colunas do tipo cadeia de caracteres. Seja MySQL, FireBird, Oracle com dbExpress, ADO, IBX, ZeosDB, AnyDac ou FireDac. Como:
    ‘SELECT campos FROM tabela WHERE descricao = :descricao’
    oQuery.ParamByName(‘descricao’).AsString := ‘Foo’;

    1. Ótima contribuição, Marcão!
      Já elaborei um artigo sobre parâmetros em Queries mas, por falha minha, não comentei sobre a vantagem do cache do banco de dados.
      Essa técnica evidentemente contribui muito para o desempenho da aplicação.

      Obrigado, meu caro! Abraço!

  2. Olá André,
    Primeiramente parabéns pelo blog, realmente muito objetivo e “limpo”, que é o que precisamos.
    Gostaria de saber sobre o uso do PacketRecords… Supondo que estou trazendo uma lista “paginada” com 100 registros, caso o usuário faça uma busca pela descrição num Edit, e caso o produto exista mas não esteja nessa faixa dos 100 registros, como fazer para localiza-lo sem ser por um filtro, pois acontece de ser mais prático para o usuário ver vários registros ao mesmo tempo, no caso que comecem com aquela string!
    De já muito obrigado, e parabéns novamente!

    1. Olá, Gleickson, tudo bem?
      Primeiramente, obrigado pelo feedback sobre o artigo!
      Bom, para localizar um registro em um conjunto de dados, mesmo com a propriedade PacketRecords configurada com um valor maior que zero, basta utilizar o comando Locate informando a opção loPartialKey no último parâmetro:

      DataSet.Locate('Descricao', Edit1.Text, [loPartialKey]);

      Vale ressaltar que o comando acima irá localizar somente os registros nos quais o início da descrição seja igual ao valor digitado no campo “Edit1”. Por exemplo, ao digitar “José” na “Edit1”, este será o resultado:

      – José da Silva: será localizado
      – Maria José: não será localizado

      Se o objetivo é localizar comparações dentro de um string, o ideal é utilizar o Filter.

      Abraço!

    2. André, Muito obrigado pela rapidez e gentileza em responder!
      Mas acho que não me fiz entender muito bem. No caso preciso localizar um registro que está fora da faixa que está carregada no cach do PacketRecords, isto é, estou exibindo os 100 primeiros registros, mas quando eu faço uma busca, o primeiro registro iniciando com essa String no banco de dados estaria no registro número 2500(por exemplo). Esse registro será localizado, mesmo sem essa faixa de registros ter sido “paginada” ainda?
      Mais uma vez obg!!

    3. Isso mesmo, Gleickson!
      Por isso que no comentário anterior fiz questão de frisar:

      “para localizar um registro em um conjunto de dados, mesmo com a propriedade PacketRecords configurada com um valor maior que zero […]”

      Abraço!

  3. Boa tarde André,
    Fiz os teste e como você falou, realmente o registro é localizado sem problemas, mesmo se estiver fora da faixa já carregada pelo PacketRecords naquele momento, Show! Só tem um inconveniente, que ao utilizar o Locate todos os registros são carregados e o PacketRecords perde sua função, mesmo que esse registro seja o primeiro da lista ou mesmo que não seja localizado o valor que estou buscando. Pena que o uso que eu estava pensando em fazer do PacketRecords não vai ficar legal assim, mas com certeza apareceram outras situações que sim.
    Mais uma vez muito obrigado pela paciência e gentileza, prometo não perturbar mais (pelo menos sobre PacketRecords kkkk).
    Grande abraço e sucesso.

    1. Boa tarde, Gleickson!
      Exato. A função “Locate” irá acessar os registros fora da faixa estipulada pela propriedade PacketRecords, caso seja necessário. Mas, ao meu ver, este é o comportamento correto. Se a intenção é mostrar apenas o registro encontrado, a propriedade Filter deve ser utilizada.
      O PacketRecords é apenas um mecanismo de paginação que possibilita a exibição de poucos registros por vez, evitando várias chamadas de métodos de pintura, validações de valores em Grid, etc. Por isso que mencionei este recurso no artigo! 🙂

      Abraço!

  4. Bom dia amigo, tudo bem? muito legal seu artigo, não é o primeiro que eu leio e já salvei sua pagina em meus favoritos para consultas.
    Me ajuda com uma dúvida. Fiz uma pequena aplicação utilizando o Delphi Xe8 com base Firebird, porem quando uso pesquisas do tipo “select nome_campo from base where…….” ele da erro de execução. Quando substituo o “nome_campo” po ” * ” o programa funciona normalmente e me retorna o resultado da consulta.
    O que pode ser? Será alguma função do Firebird que precisa ser habilitada, já que faço o mesmo tipo de pesquisa utilizando o MySql e funciona normalmente.
    Apenas como informativo, para conexão com o Firebird uso o Firedac e para conexão com o MySql uso os componentes da palheta ADO.
    Obrigado pela ajuda.
    Abraços!!!

    1. Boa noite, Rogério, tudo bem?
      Fico grato em saber que salvou o blog nos favoritos! Obrigado!
      Este erro de execução não deveria ocorrer. Vou entrar em contato com você para solicitar mais detalhes, ok?

      Abraço!

Deixe uma resposta

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

Preencha o campo abaixo * Time limit is exhausted. Please reload CAPTCHA.