[Delphi] Tabela temporária com ClientDataSet – Final

[Delphi] Tabela temporária com ClientDataSet - FinalBom, hora de fechar o tema sobre tabelas temporárias no Delphi! Espero que nos dois primeiros artigos você tenha compreendido, de forma satisfatória, como trabalhar com tabelas temporárias utilizando ClientDataSet. Este último artigo apenas apresenta algumas observações relacionadas a tabelas temporárias que podem ser úteis durante o desenvolvimento.

 

Tabelas temporárias também podem ser criadas em tempo de execução, bem como a definição dos seus campos. Observe o exemplo abaixo, onde instancio um TClientDataSet e adiciono três campos de diferentes tipos de dados (integer, string e float):

var
  ClientDataSet1: TClientDataSet; // a unit DBClient deve ser declarada na "uses"
begin
  ClientDataSet1 := TClientDataSet.Create(nil);
  ClientDataSet1.FieldDefs.Add('CODIGO', ftInteger);
  ClientDataSet1.FieldDefs.Add('DESCRICAO', ftString, 60);
  ClientDataSet1.FieldDefs.Add('VALOR', ftFloat);
  ClientDataSet1.CreateDataSet;
end;

 

E campos agregados também! Caso você não conheça, campos agregados servem para realizar cálculos em uma determinada coluna do ClientDataSet. No exemplo a seguir, criei um campo agregado para somar automaticamente o valor total de todos os itens da tabela.

with ClientDataSet1.Aggregates.Add do
begin
  AggregateName := 'Valor Total';
  Expression := 'SUM(TOTAL)';
  Active := True;
end;
ClientDataSet1.AggregatesActive := True;

 

Muitas vezes pode ser necessário “esvaziar” a tabela temporária, como por exemplo, no botão “Limpar” ou “Cancelar” de um formulário. Essa instrução pode ser realizada com apenas uma linha de código:

ClientDataSet1.EmptyDataSet;

 

Barbada, não?
Além disso, tabelas temporárias também permitem a navegação entre os registros na memória utilizando os métodos tradicionais já conhecidos:

ClientDataSet1.First; // move para o primeiro registro
ClientDataSet1.Last;  // move para o último registro
ClientDataSet1.Prior; // move para o registro anterior
ClientDataSet1.Next;  // move para o próximo registro

 

Outro recurso bastante interessante do ClientDataSet é o clone do conjunto de dados. Através do comando CloneCursor é possível copiar os dados de um ClientDataSet para outro, e então manipulá-los de maneira independente.

// ClientDataSet2 "clona" os dados de ClientDataSet1
ClientDataSet2.CloneCursor(ClientDataSet1, True);

 

Agora, imagine que estamos utilizando uma tabela temporária para gravar itens de uma venda. Não é interessante que produtos repetidos sejam inseridos na tabela, concorda? Afinal, se o código do produto fizer parte da chave primária, ocorrerá um erro ao gravar os itens da venda.
Para resolver isso, podemos utilizar a função Locate e controlar a inserção de itens repetidos:

if ClientDataSet1.Locate('COD_PRODUTO', edtCodProduto.Text, []) then
  ShowMessage('Este produto já foi adicionado!')
else
  // grava o registro na tabela

 

Para filtrar os registros, não há segredo. No exemplo abaixo, tenho um componente do tipo TEdit chamado edtPesquisa, e permito o filtro da descrição de um registro na tabela temporária conforme o conteúdo digitado no campo:

ClientDataSet1.Filter := 'DESCRICAO like ' + QuotedStr(edtPesquisa.Text + '%');
ClientDataSet1.Filtered := True; // ativa o filtro

 

Só lembrando que o símbolo de porcentagem permite que todos os registros que iniciem com a palavra digitada em edtPesquisa sejam encontrados. Por exemplo, se o usuário digitar a letra “A”, todas as descrições que começam com essa letra serão filtradas.
Mas atenção: não esqueça de desativar o filtro quando for necessário trabalhar com todos os registros da tabela temporária.

 

Bom, pessoal, espero que tenham gostado dessa série de artigos sobre tabelas temporárias, e que de alguma forma venha a ser útil pra vocês! Boa sorte no trabalho e até breve!


Confira os outros artigos:

Tabela temporária com ClientDataSet – Conceito
Tabela temporária com ClientDataSet – Prática
Tabela temporária com ClientDataSet – Final


 

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

48 comentários

  1. Eu já programo em Delphi há algum tempo e sempre tive o interesse em trabalhar com banco de dados, porém sempre achei um pouco complicado. Este artigo foi muito interessante e didático que percebi que é simples e de certa forma fácil trabalhar com banco de dados no Delphi. Vou estudar este artigo e implementar nos meus aplicativos.

    Obrigado.

    Júlio.

    1. Prezado André.

      Estou carregando tabelas na estrutura de Client Data Set, porém no meio do processo de importação dos dados é interrompido com uma mensagem de “memory insufficient for this operation”. Meu computador é uma máquina de 64bits e 8GB de memória RAM e as tabelas totalizam 534 MB. Você poderia me orientar em como solucionar este problema?

      Obrigado.

      Júlio.

  2. ola tem como usar um if dentro do locate? exemplo quero varre os registro de encontro o campo diferente de nulo.. mas nao tenho valor certo..

  3. André, tenho utilizado com sucesso tabelas temporárias com ClientDataset.
    Uma dúvida: Como acrescento um novo campo após a tabela ter sido utilizada ?
    Sempre que necessito um erro é gerado.

    1. Olá, Álvaro! Após a criação do ClientDataSet não é possível adicionar um novo campo, já que essa operação altera a estrutura interna dos Fields. Sendo assim, para criar um novo campo, é necessário remover todos os campos existentes e adicioná-los novamente:
      ClientDataSet2.Close;
      ClientDataSet2.FieldDefs.Clear;
      // adicione todos os campos anteriores e o campo novo
      ClientDataSet2.CreateDataSet;

      Espero que lhe ajude! Abraço!

  4. Obrigado André. Consegui fazer em tempo de desenho mas é muito mais difícil, o ideal é excluir o ClientDataSet e iniciar o processo mesmo.

    Seu exemplo é uma das vantagens de criação de componentes ( ou algumas propriedades ) por código.

    Muito obrigado e um abraço.

  5. Boa tarde.

    Como faço após clonar todos os registros grava-los fisicamente na tabela de dados?
    Desde já, agradeço a atenção.

    1. Olá, Valtencir. Para gravar os registros que estão na tabela temporária, basta iniciar um loop e percorrê-los um a um, gravando-os no banco de dados. Vou enviar um exemplo de código no seu e-mail, ok? Abraço!

  6. Boa noite André.
    Agradeço muito sua atenção, vou analisar o e-mail e implementar o que você me enviou.

    Grande abraço.

  7. Boa Tarde André. Explicação muito boa sobre tabela temporária. Entretanto tenho uma dúvida. Veja: Criei um campo aggregates para poder somar alguns valores de acordo com as vendas. Esta somando normal, porém a mascara que inseri no campo aggregates não funciona ou seja o valor da moeda do corrente ‘R$’ não é mostrado. Pesquisei na maioria dos fóruns Delphi e não há uma explicação. Vi alguns tutos ensinado por linha de código em evento, mas penso que deve ter algum modo pela propriedades Objecto Inspector.

    Obrigado
    Abraço.

    1. Olá, Sócrates!
      As versões do Delphi anteriores que a versão 2010 contém um bug na formação de campos Aggregates. Mesmo colocando máscaras ou marcando a propriedade “currency” como True, o número não é formatado. Neste caso, uma alternativa é formatar o número no próprio código fonte:
      Edit1.Text := FormatFloat('R$ ###,###,##0.00', ClientDataSet1.FieldByName('CAMPO_AGGREGATE').Value);

      Segundo alguns sites, também há uma opção de corrigir esse bug do Delphi. Na unit “DB.pas”, no método “TAggregateField.GetText”, basta substituir a linha:
      if FResultType in [ftFloat, ftCurrency] then
      por:
      if FResultType in [ftFloat, ftCurrency, ftFMTBcd] then
      Lembrando que, se essa alteração for feita, a unit “BD.pas” deve ser recompilada para que as alterações entrem em vigor.

      Abraço!

  8. Bom dia André,
    Muito obrigado pela atenção. Optei pela primeira opção e deu certo. Substitui o Edit por um Label e alcancei o resultado esperado. Agradeço a explicação e solução postada.

    Abraço!

    1. Hello, Alfredo!

      Unfortunately, I don’t speak Spanish. If you’re able to speak English, maybe I could help you through email.
      Let me know if it’s possible, ok?
      Regards!

  9. Bom dia André L. Celestino

    Eu admiro as pessoas que tem o dom de ensinar sem cobrar, simplesmente pelo prazer de ensinar parabéns!. André, eu acabei de me formar em Tecnologia em Análise e Desenvolvimento de Sistemas acho que até um pouco tarde pela minha idade. Mas tenho um sonho e quero me tornar programador em Delphi linguagem que escolhi para desenvolver mas não tenho muita prática, poderia me dar uma dica, para seguir.

    1. Olá, José Roberto!

      Em primeiro lugar, não se preocupe com a sua idade! Nunca é tarde para adquirir novos conhecimentos!
      José, eu não sei exatamente qual é o seu nível de habilidade em lógica de programação, mas eu posso lhe encaminhar algumas apostilas de Delphi para iniciantes. Vou entrar em contato para mais detalhes.

      Abraço!

  10. me foi muito util, igual a varias outras coisas q vc posta! XD
    eu acho q seria legal republicar essas materias do clientdataset mas com firedac, basicamente so precisa mudar uma propriedade como true do query, um usuario no forum quebrou a cabeca com isso, e eu acabei descobrindo, a sugestao é basicamente para q com isso aumente a documentacao do firedac, e uma funcionalidade muito importante para quem nao teve contato com firedac é a questao de trabalhar com dados em memoria, enfim, fica aqui a sugestao!

    1. Olá, Conde!
      Tem razão, ainda preciso elaborar alguns artigos abordando o FireDAC. Atualmente há poucos materiais na internet sobre essa tecnologia.
      Assim que possível, trabalharei nesse tema.
      Abraço!

  11. André, bom dia.
    Parabens pelo conteúdo do artigo, de todos q procurei o seu realmente foi mto esclarecedor.
    Pergunto: É possível trabalhar com colunas definidas como PK( primary key )?
    Como seria a definição?
    Obg. e sucesso.

    1. Olá, Marco!
      Ótima pergunta!
      É possível trabalhar com o comportamento parcial de uma Primary Key, ou seja, podemos definir um campo (Field) como obrigatório alterando a propriedade Required do campo para True. Entretanto, não é possível definir uma coluna como Unique (que não pode se repetir). Neste caso, você terá que controlar esse comportamento manualmente, checando se o valor já existe na tabela antes de gravar.

      Abraço!

  12. Uma pequena correção no comentário acima. É possível usar coluna com Unique em um TClienteDataSet. Basta ir na propriedade IndexDef do ClienteDataSet criar um novo index, na propriedade Fields do index coloque o campo que você deseja que não se repita e marque as opções ixPrimary e ixUnique. Escolha um nome para o index e atribua esse mesmo nome na propriedade IndexName no ClienteDataSet.

    1. Olá, Felipe, tudo bem?
      Muito obrigado pela contribuição! Eu sinceramente desconhecia essa forma de atribuir o comportamento de valor único para uma coluna de um TClientDataSet! Bem interessante!
      @Marco, confira essa dica do Felipe.

      Abraços!

  13. Bom dia, André !
    Primeiramente, parabéns pelo blog e estou sempre aqui dando uma olhada.
    “Segundamente”, poderiam tentar me ajudar com o seguinte erro que ocorre quando executo o comando ClientDataSet1.ApplyUpdates(0)
    —————————
    Project Project1.exe raised exception class TDBXError with message ‘Table ‘gaucho.lista’ doesn’t exist’.
    —————————

    Acontece que a tabela, no banco, está com letras maiúsculas, gaucho.LISTA seria o certo.

    Se executo uma query/comando manual por um TSQLDataSet ou TSQLQuery o banco atualiza normalmente.
    Se mudo o nome da tabela para letras minúsculas também funciona.

    Utilizo o Quarteto: TSQLConnection + TSQLDataSet + TDataSetProvider + TClientDataSet

    1. Boa noite, André !
      Obrigado pela atenção.

      Com relação a dica que me deste não há este item no parms do meu TSQLConnection…

    2. bom dia, André !

      Desculpe, esqueci de dizer que estou tentando conexão com MySql…

  14. Bom dia André.
    Excelente, aprendi mais duas usar like e % em um filtro, nunca imaginei, da pra usar containing também?
    Valeu mesmo.

    1. Olá, Gerson!
      Opa, que bom que aprendeu algo com o artigo!
      O Containing não está disponível na propriedade Filter justamente porque o like e o símbolo “%” já dão conta do recado!

      Abraço!

  15. Carissimo Andre, adorei tudo que escreveu, mas tenho problema já bem antigo. Sonho diariamente em criar uma tela de qualquer coisa que me permita criar desde o ClientDataset ligado a uma dsp ligado a uma query, tudo em tempo de execução, com campos calculado e campos aggregates. No entanto, até o momento só cheguei a obter resultado até o campos calculado, só me falta o aggregate, o maldito se nega a funcionar.

    Fiz assim

    Q:=TZQuery.Create(Self)
    D:=TDatasetProvider.Create(Self);
    C:=TClientDataset.Create(Self);
    ...
    ...
    Var
      campo:TField;
    Begin
      Campo:=TWideStringField.Create(Self);
      With Campo do begin
        FieldName   :='BancoNome';
        FieldKind   :=fkData;
        Size        :=30;
        Dataset     :=C;
      end;
      Campo:=TIntegerField.Create(Self);
      With Campo do begin
        FieldName   :='Doc';
        FieldKind   :=fkData;
        Dataset     :=cdFinanceiro;
      end;
      Campo:=TFloatField.Create(Self);
      With Campo do begin
        FieldName   :='SomaAberto';
        FieldKind   :=fkInternalCalc;
        Dataset     :=C;
      end;

    Até aqui está tudo funcionando, só tem um detalhe, estou usando CommandText.
    Daqui pra frente, só falta dizer que quero adicionar o AggregateField, já usei diversas forma, já perdi meses tentando descobrir a funcionalidade disso, mas até agora nada.
    A idéia geral é não usar nenhum componente em tempo de projeto.

    Se puder me dar uma pequena dica, agradeço
    Marcelo

  16. Andre, mesmo não tendo respondido, agradeço assim mesmo e informo que depois meses tentando fazer a operação acima, acabei descobrindo sozinho.

    Se alguem tiver a mesma dúvida, hoje posso instruir
    abraço a todos

    1. Olá, boa tarde, Marcelo!
      Peço desculpas pela demora para responder o seu comentário, mas, ao mesmo tempo, fico feliz que já tenha encontrado a solução.
      Neste mesmo artigo, apresento um exemplo de como criar um campo Aggregate em tempo de execução. Foi dessa forma que você criou?
      Se puder colaborar com a sua solução, a comunidade Delphi ficará agradecida! 🙂

      Apenas para aproveitar o ensejo, você pode criar a sua própria rotina de criação de Fields para reduzir a repetição de código. Por exemplo:

      procedure CriarField(DataSet: TClientDataSet;
        const Nome: string; const Tamanho: integer;
        const Tipo: TFieldKind);

      E então, você chamaria dessa forma:

      CriarField(C, 'BancoNome', 30, fkData);

      Abraço!

  17. Prezado André, boa tarde. Mais uma vez, outro excelente artigo. Veja se pode me ajudar, por favor. Num form de pedido, busco os dados do cliente normalmente… aí vem os itens do pedido… achei super prático essa tabela temporária e vem muito a ajudar… Minha dúvida é a seguinte: como eu poderia inserir os itens do pedido em um DBGrid e depois salvar os itens do DBGrid no banco de dados? Tipo, digito o código do produto em um edit e me retorna a descrição e o valor.. aí digito a quantidade e calcula o total daquele item… aí clico em um botão “Inserir Item”. Quando clicar nesse botão, queria que os dados fossem para um DBGrid para poder inserir outros itens… E só quando clicar no botão “Salvar Pedido” que tudo fosse salvo no banco de dados, entendeu?

    1. Olá, Jonismar.
      Entendi perfeitamente a sua questão. Tabelas temporárias são úteis para esse tipo de cenário que você mencionou. Você pode inserir vários registros em uma tabela temporária (“Inserir item”) e solicitar que todos eles sejam persistidos de uma vez só no banco de dados (“Salvar Pedido”).
      Eu faço essa demonstração na segunda parte do artigo. Clique aqui para acessá-la.
      Qualquer dúvida, envie um e-mail para “contato@andrecelestino.com” para que eu possa ajudá-lo melhor.
      Abraço!

  18. Olá André! Primeiramente parabéns pelo artigo; bem didático e de simples interpretação.
    Estou tendo uma dificuldade com um filtro, o que não tem nada a ver com tabelas temporárias, mas se possível, gostaria de uma dica sua, caso já tenha se deparado com este problema.

    Meu ambiente de trabalho:
    Delphi 2006 – MySQL 5.7 – DBExpress

    SITUAÇÃO:
    Tenho um dbGrid que traz o conteúdo de um ClientDataSet com um tabela do banco. Alguns campos string, inteiros e memo.

    Muitos programadores têm dificuldade com utilizar o filter, porém após tanto estudar e testar, o utilizo da seguinte forma:

    Ex. campo inteiro .: ” > 2″

    Ex. campo string ..: ” lower() like lower(‘%’++’%’)”
    ‘%’ usado antes e depois do texto para que possa encontrar a palavra em qualquer posição da string.

    Ex. campo memo ….: ” like ‘%’++’%’ ”
    ‘%’ mesma finalidade do campo string
    FilterOptions = foCaseInsensitive

    Quando o contém acentos, ele passa por um procedimento que troca cada letra acentuada pelo caratere ‘_’, fazendo com que ele aceite qualquer caractere naquela posição.

    PROBLEMA:
    Em alguns campos memo a pesquisa encontra algumas palavras e outras não, sem motivo aparente!
    Às vezes ocorre em palavras acentuadas, às vezes em palavras não acentuadas…
    Não consegui identificar um motivo para isto ocorrer.

    Após a pesquisa enviada para o filter e o filtered ativado, eu exibo o conteúdo do filter na tela e nada demais tem em sua estrutura.

    Para ficar claro, coloquei 4 imagens com as pesquisas onde o erro ocorrre.

    Imagem 1: pesquisa realizada: “esclarecendo” ( sem acento – traz o campo na pesquisa )
    http://www.hexadesenvolvimento.com.br/downloads/screenshots/20170727_delphi_filter/tela01.png

    Imagem 2: pesquisa realizada: “sobreaviso” ( sem acento – não traz o campo na pesquisa )
    http://www.hexadesenvolvimento.com.br/downloads/screenshots/20170727_delphi_filter/tela02.png

    Imagem 3: pesquisa realizada: “misericórdia” ( com acento – traz o campo na pesquisa )
    http://www.hexadesenvolvimento.com.br/downloads/screenshots/20170727_delphi_filter/tela03.png

    Imagem 4: pesquisa realizada: “médicos” ( com acento – não traz o campo na pesquisa )
    http://www.hexadesenvolvimento.com.br/downloads/screenshots/20170727_delphi_filter/tela04.png

    Todas as palavras pesquisadas podem ser encontradas no mesmo campo memo, porém algumas palavras não retornam o campo.

    Agradeço demais qualquer ajuda que possa dar quanto à este problema.

  19. Bom dia André,
    Como sempre, parabéns, ótimo post sobre ClientDataSet, sempre ajuda.
    Em minhas dúvidas, sempre consulto seu blog e sempre tem algo de bom a se tirar.
    Gostaria se possível que tirasse uma dúvida sobre o ClientDataSet. Creio que essa dúvida não é só minha.
    Trabalho com Delphi sem utilizar componentes dataware. Até então tudo bem. O problema é trabalhar com campos blob no DataSnap. Sempre que vou atualizar o registro (sem modificar o valor do campo), se existir algum valor, ele apaga o conteúdo. Porém, se alterar o valor contido no momento, ele grava sem problemas. Isso é uma falha do componente ClientDataSet ou é apenas um erro de configuração das propriedades do componente?
    O legal seria um post sobre o assunto, iria ajudar muitos desenvolvedores, assim como eu.
    Obrigado.

    1. Olá, boa noite, Francisco.
      Fiquei um pouco perplexo com o seu problema. O campo BLOB não deveria perder o seu conteúdo.
      Vou entrar em contato para solicitar mais detalhes.
      Abraço!

  20. Tanto tempo postado e ainda atual e ajudando tanta gente. Sua didática é excelente, obrigado pelo seu tempo de dedicação.
    Minha dúvida é simples. Criei um cds temporario conforme suas dicas (declarei a unit) mas ao digitar CdsTemp.FieldDefs.Add(‘Codigo’, ftinteger), o Delphi diz “undeclared identifier ‘ftinteger'”. O mesmo para ftstring. Mas não deu erro ao declarar:

    var  cdstemp :TclientDataset;

    Obs: Delphi 2010. Se eu digitar “integer” ao invés de “ftinteger”, ele nao dá o erro acima mas ao compilar acusa: Unit1.pas ‘(‘expectd but ‘)’ found, ou seja, ele queria que eu abrisse outro parentesis…. para que? Estou preso nisso… kkk help!

    1. Olá, Emerson, tudo certo?
      Acho que esse problema é simples de resolver. Experimente adcionar a unit DB na seção uses. Os tipos de dados (ftInteger, ftString, ftFloat, etc…) estão declarados nessa unit.

      Obrigado! Abraço!

  21. Boa tarde, André ótimo post , uma duvida quando acontecer uma queda de energia ou maquina desligue existe uma maneira de recuperar esse dados ?

    Att

    1. Olá, Júnior! Ótima pergunta!
      Caso ocorra uma queda de energia, os dados realmente serão perdidos, já que as tabelas temporárias trabalham com dados em memória.
      No entanto, esse risco pode ser contornado se gravarmos os dados em disco temporariamente. Por exemplo, a cada registro inserido em uma tabela temporária, podemos salvar os dados em um arquivo temporário (utilizando o comando SaveToFile do TClientDataSet). Dessa forma, caso seja necessário recuperá-los, basta carregar o arquivo temporário (com o método LoadFromFile). Uma boa funcionalidade, hein? 🙂

      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.