Clique para saber mais...
  Home     Download     Produtos / Cursos     Revista     Vídeo Aulas     Fórum     Contato   Clique aqui para logar | 07 de Setembro de 2026
  Login

Codinome
Senha
Salvar informações

 Esqueci minha senha
 Novo Cadastro

  Usuários
62 Usuários Online

  Revista ActiveDelphi
 Assine Já!
 Edições
 Sobre a Revista

  Conteúdo
 Apostilas
 Artigos
 Componentes
 Dicas
 News
 Programas / Exemplos
 Vídeo Aulas

  Serviços
 Active News
 Fórum
 Produtos / Cursos

  Outros
 Colunistas
 Contato
 Top 10

  Publicidade

  [Artigos]  Para aqueles que utilizam FieldByName.
Publicado por fabiofs : Quarta, Outubro 04, 2006 - 09:06 GMT-3 (10520 leituras)
Comentários 16 Comentários   Enviar esta notícia a um amigo Enviar para um amigo   Versão para Impressão Versão para impressão
Fábio Silva Isto é para ser mais um ponto de discussão entre nós, desenvolvedores Delphi.
Sempre fui fã do FieldByName(). Sempre achei que o código ficava muito mais claro com expressões do tipo FieldByName('nome_do_campo').asAlgumaCoisa do que Fields[indice].asAlguma coisa...

Há pouco tempo, em um projeto que estou trabalhando, um amigo do trabalho me pediu que evitasse a utilização de FieldByName e de imediato questionei o porquê de tal decisão. O mesmo me pediu para que eu desse uma olhada na implementação do FieldByName nos fontes da VCL do Delphi. Vou colar aqui função para vocês:

function TDataSet.FieldByName(const FieldName: string): TField;
begin
  Result := FindField(FieldName);
  if Result = nil then DatabaseErrorFmt(SFieldNotFound, [FieldName], Self);
end;


Bom, até agora nada. Mas vamos olhar como é implementado o método FindField:

function TDataSet.FindField(const FieldName: string): TField;
begin
  Result := FFields.FindField(FieldName);
  if (Result = nil) and ObjectView then
    Result := FieldList.Find(FieldName);
  if Result = nil then
    Result := FAggFields.FindField(FieldName);
end;


Até agora ainda não temos nada de concreto sobre o motivo da não utilização do FieldByName a mim solicitada. Sendo um pouco mais persistente, vamos ver o método FindField do objeto FFields que é do tipo TField:

function TFields.FindField(const FieldName: string): TField;
var
  I: Integer;
  begin
    for I := 0 to FList.Count - 1 do
    begin
      Result := FList.Items[I];
      if AnsiCompareText(Result.FFieldName, FieldName) = 0 then Exit;
    end;
    Result := nil;
end;


Agora sim podemos concluir alguma coisa. Observando o código à cima, vamos pensar na seguinte situação. Imaginem que temos um dataset com 60 campos e temos na posição 60 um campo valorado com o qual precisamos fazer uma soma do tipo:

valor := 0;
while not DataSet.Eof do
  begin
    Valor := valor + DataSet.FieldByName('campo_valorado').asCurrency;
    DataSet.Next;
end;


Se tivermos neste DataSet 100000 registros, teremos que passar pela linha de código

...
Valor := valor + DataSet.FieldByName('campo_valorado').asCurrency;
...

100000 vezes. Um processamento rasoável. Mas e o FieldByName? Observem que na implementação do método FindField da classe TField é utilizado um for de 0 até o número de campos para se encontrar o campo desejado e assim retornar o valor. Sendo, o nosso campo desejado, o campo de número 60, cada chamada de FieldByName - em nosso caso - ocasionaria um processamento de uma repetição 60 vezes até que o campo seja encontrado. Agora vamos fazer uma conta simples:

100000 registros x 60 vezes (FieldByname) = 6000000 instruções processadas.

Poxa, chegamos a um valor alto né.

Mas qual a solução? Fields[60]?

Vamos ver a implementação da classe TFields para ver como o mesmo processa a instrução Fields[indice]:

TFields = class(TObject)
private
FList: TList;
...
protected
...
function GetField(Index: Integer): TField;
...
public
...
property Fields[Index: Integer]: TField read GetField write SetField; default;
end;


Já podemos ver que Fields é uma property indexada. Opá, algo já nos mostra que isto pode ser mais rápido que a pesquisa com o for do método FieldByName mas vamos mais a fundo. Vamos dar uma olhadinha no método de acesso GetField:

if FSparseFields > 0 then
begin
  if Index >= FSparseFields then
    DatabaseError(SListIndexError, DataSet);
  Result := FList[0];
  Result.FOffset := Index;
end else
  Result := FList[Index];


Reparem quem em nosso caso, que apenas a linha Result := FList[Index]; será acionada utilizando um TList onde são armazenados os campos de um DataSet. E como será a implementação da propriedade que define os itens de um TList?

TList = class(TObject)
private
FList: PPointerList;
...
protected
function Get(Index: Integer): Pointer;
...
public
...
property Items[Index: Integer]: Pointer read Get write Put; default;
...
end;


Por fim chegamos ao método de acesso Get da property items da classe TList:

function TList.Get(Index: Integer): Pointer;
begin
  if (Index < 0) or (Index >= FCount) then
    Error(@SListIndexError, Index);
  Result := FList^[Index];
end;

Observem a diferença. Aqui se trabalha com Ponteiros para a localização do campo desejado. Sendo assim, o processamento desta instrução terá peso 1, mesmo que tenhamos 60 campos em nosso DataSet. Vamos voltar a conta que fizemos anteriormente:

100000 registros x 1 vez (Fields[indice]) = 100000 instruções processadas.

Olha que diferença entre executar 6000000 de instruções e 100000. Por isto digo, dentro de Loops envolvendo um campo de um DataSet com vários campos, pensem bem se vale a pena utilizar

valor := 0;
while not DataSet.Eof do
begin
  Valor := valor + DataSet.FieldByName('campo_valorado').asCurrency;
  DataSet.Next;
end;


ou

valor := 0;
while not DataSet.Eof do
begin
  Valor := valor + DataSet.Fields[60].asCurrency; //campo_valorado
  DataSet.Next;
end;


Querem algo para arrepiar os cabelos? Pensem em algo do tipo:

FieldByName('A').asInteger :=
((FieldByName('B').asInteger + FieldByName('C').asInteger)/ FieldByName('D').asInteger) * FieldByName('E')

Isto para 1000 registros, em um DataSet com 5 campos (algo bem pequeno) daria no pior caso:

1(A) x 2(B) x 3(C) x 4(D) x 5(E) x 100 = 120000 instruções processadas

Agora transportem esta situação para um DataSet com um pouco mais de campos e um pouco mais de registros. (Sai até um palavrão neste momento do pensamento de vocês, não sai?)

Observem que um comentário já torna o código mais claro. Não estou desaconselhando a utilização do FieldByName porém, temos que avaliar muito bem mesmo quando formos utilizar um simples método como este.

Por último, quando tiverem algum problema de performance em um código fonte e já tiverem tentado de tudo, procurem dentro da VCL do Delphi o que vocês estão pedindo para que ele faça pois, talvez lá, esteja a solução do seu problema. Vai aqui também um agradecimento ao amigo Cristiano pela dica.

 

Fábio Silva - fabiofs@hotmail.com



Comentários Comentários
   Ordem:  
Comentários pertencem aos seus respectivos autores. Não somos responsáveis pelo seus conteúdos.


por: cruzeirense : Out 04, 2006 - 12:15
(Informações sobre o membro | Enviar uma mensagem)
Interessante, vou utilizar, mas apenas para casos críticos porque ficar buscando campo por índice é @#!@#@, principalmente quando você tem que alterar posteriormente a instrução sql que gera o dataset ou a mesma é criada dinâmicamente, Qualquer mudança na posição dos campos e pode ter certeza que vão ser horas de debug. De qualquer forma é uma ótima dica.

PS. E não é que saiu o palavrão mesmo?


por: fabiofs : Out 04, 2006 - 01:08
(Informações sobre o membro | Enviar uma mensagem) http://
Oi grande Cruzeirense. Esse também é meu time de coração, como bom Mineiro.

É claro que existem casos onde a utilização do FieldByName é totalmente necessário. Mas é bom avaliar em que momentos podemos evitá-lo.


por: joemil : Out 04, 2006 - 07:34
(Informações sobre o membro | Enviar uma mensagem)
caraca, desse jeito, vai demorar mesmo.

o unico problema q vejo eh qdo a query eh dinamica, como ja disseram antes.

mas onde puder retirar, vou retira-lo.

ou criar um array pra colocar os campos dinamicos selecionados e "recriar" o fieldbyname hehehe


por: fabiofs : Out 04, 2006 - 10:12
(Informações sobre o membro | Enviar uma mensagem) http://
Em caso de querys dinâmicas, o jeito acho que até o mais lógico é usar o FieldByName mesmo. Mas mesmo em querys dinâmicas, pode-se usar o Fields mesmo.

Uma maneira de se evitar muita manutenção no código quando se alteram os Campos persistentes de uma query ou tabela é usar constantes com as posições dos campos.

E da mesma forma que o FieldByName tem uma performance pior que o Fields, o ParamByName o é também.


por: mmpaulo : Out 06, 2006 - 11:37
(Informações sobre o membro | Enviar uma mensagem) http://
Desculpe, mas discordo totalmente de voce.
É claro que Fields é mais rápido que FieldByName, mas voce fez testes para ver se a diferença é perceptivel e se justifica a troca de FieldByName por Fields?
Outra coisa, independente, de usar FieldByName ou Fields, no exemplo que você deu, você podia ter armazenado o TField em uma variável antes de entrar no loop.


por: Visitante : Out 06, 2006 - 11:52
Totalmente equivocada sua opinião e seus exemplos. o FieldByName só criaria um gargalo de desempenho em um sistema muito mal planejado e mal modelado também. percorrer todos os registros de uma tabela só se o programador não tiver conhecimentos de SQL. Tabelas com mais de 20 campos é uma falha na normalização do banco de dados.

reveja seus conceitos.


por: acidbytes (acidbytes@acidbytes.com) : Out 06, 2006 - 03:11
(Informações sobre o membro | Enviar uma mensagem) http://
Fábio, acho que há um tremendo equívoco, ou falta de informação da pessoa que lhe orientou sobre isso. Rode um profiler e você verá que a diferença de performance é insignificativa.
Para o caso que você citou, de fazer uma soma, acho que nem um iniciante faria assim, uma vez que uma instrução SQL resolve de maneira muito mais eficiente, daquela forma que você colocou, teria que trazer os registros para o cliente e efetuar uma soma no cliente, algo impensável!
Além do mais, qualquer compilador gera um código otimizado nesse caso.
O ganho de performance eventual (se existir algum minimamente significativo), é totalmente invalidado pela perda de tempo e clareza do código.


por: clelson : Out 09, 2006 - 11:02
(Informações sobre o membro | Enviar uma mensagem) http://
Bom,

Se o problema é performance, creio que isso resolve o seu problema:
var
campo: TField;
begin
valor := 0;
campo := dataset.FieldByName(''campo_valorado'');
valor := 0;
while not DataSet.Eof do
begin
Valor := valor + campo.asCurrency;
DataSet.Next;
end;

pronto. Isso evita a procura pelo campo todas aquelas vezes, não complica o código e mantem a clareza.

Agora se vamos entrar em questões de performance mesmo: - Que tal dar uma olhada na implementação de uma chamada a método virtual?
VMT e etc?


por: fabiofs : Out 12, 2006 - 11:20
(Informações sobre o membro | Enviar uma mensagem) http://
Só um mais um comentário sobre os comentários à cima. Sistemas pequenos e mais simples tem tabelas pequenas. Por exemplo, na tabela de produtos na empresa em que trabalho temos cerca de 60 campos e fale com a equipe de DBAS aqui que a tabela esta desnormalizada que os mesmos "dão até tiro". Mas é verdade que a tabela está dentro das 5 regras de normalização que utilizamos aqui e que são as mesmas utilizadas para normalização de BD em qualuer lugar.

Existem casos também em que temos que montar ClientDataSet dinamicos e sendo assim não há SQL que nos auxilie com suas funções como SUM, AVG, etc...

Estou ciente que o meu exemplo foi bem simples. Mas apenas queria mostrar a diferença de linhas de código executadas quando utilizamos FieldByName e Fields.


por: Galdarius : Out 19, 2006 - 06:14
(Informações sobre o membro | Enviar uma mensagem) http://
por ex, depois de se adicionar todos os fields, se referir a ele assim:

Tabela1CAMPO1.asString;

alguma ideia do comportamento?


por: fabiofs : Out 19, 2006 - 12:58
(Informações sobre o membro | Enviar uma mensagem) http://
Utilizando assim, você acessará direto o objeto referente ao TField criado e sendo assim não será efetuada a mesma busca do campo como é utilizado o FieldByName.


por: danilorsa_betta (danilo@jfkasdjfs.com.br) : Out 28, 2006 - 03:36
(Informações sobre o membro | Enviar uma mensagem) http://http://
Achei muito interessante, tanto o artigo quanto as soluções nas respostas dos amigos. Sempre me deparei com essas questões, e assim utilizo o FieldByName em muitos casos, visto, que mudanças futuras em tipos de dados, tamanhos, implicam em remover os campos e adicionar novamente no dataset, quando utilizamos TabelaCAMPO.asAlgumaCoisa. E esse tipo de manutenção, é um Deus nos acuda.
  Edição 112

Revista ActiveDelphi

  50 Programas Fontes


  Produtos

Conheça Nossos Produtos

Copyright© 2001-2016 – Active Delphi – Todos os direitos reservados