Por padrão o FireDAC mantem ativo (true) a propriedade SilentMode. Nesta condição o FireDAC mantem o ciclo de mensagem com o windows para apresentar a ampulheta de processando. Evidentemente, informar o usuário sobre o andamento do processamento pode ser um ponto importante para não ficar com a impressão que tudo travou, no entanto, isto tem um custo no tempo de resposta da operação.

A questão pode se mostrar bastante crítica quando aninha chamada ao banco durante a navegação do usuário em um GRID com os dados… Imagina fazendo um select a cada vez que muda um linha no GRID. Desligar o SilentMode pode ter impacto positivo considerável no resultado… não ficará com aquela ampulheta gastando tempo durante a navegação… tenta ainda teclar varias vez a tecla para baixo em um GRID onde a cada pressionar de tecla chama um SELECT no banco de dados… notará o quanto é significativo o recurso.

No FireDAC há varias formas de acessar esta propriedade, desde acesso mais global como o que ocorre com o componente  TFDManager até acesso mais localizado como em um TFDQuery…

Toda rotina que não precisa informar o usuário, deveria ser desligado para melhorar a resposta e liberar o firedac de ficar despachando mensagem para o Windows.

[code]query1.ResourceOptions.SilentMode := True;[/code]

No Firebird 3 passou a ser possível criar exception com parâmetros que adicionam texto à mensagem retornada para o usuário.

create exception e_invalid_val ‘Valor invalido @1  para a coluna @2’;


if (val < 1000) then
thing = val;
else

exception e_invalid_val using (val, ‘thing’);
end

 

Este recurso na prática não adiciona nenhum ganho, já que no 2.5 era possível adicionar um texto à exceção..

Exception erro ‘Valor invalido ‘||val||’ para a coluna xxxx’;

 

 

Outro dia precisava montar um RANK de produtos, ou seja, precisava saber qual a classificação no RANK de cada produto com possibilidade de apresentar somente os primeiros 100 produtos.
Fazer RANK de dados não é um recurso disponível até o firebird 2.5 – o que irá ocorrer somente no firebird 3.0 – como ainda não é uma versão de produção, então o caminho ainda é resolver com o 2.5;Captura de tela 2016-03-02 21.13.57

Depois de ler muitas sugestões de como fazer, conclui que tinha que montar uma própria – eram todas confusas e levavam a resultados com muito tempo de respostas.

Estratégia:
Montar um select que ordene em ordem crescente os valores de venda em ordem decrescente de cada um dos produtos, depois criar um loop que marque com uma coluna na resposta com a posição do produto (o RANK);

Código Delphi:

      
       query1 := TFDQuery.create(nil);
       try
           with query1 do
           begin
              connection := xxx;
              sql.text := '....'
              open;  
           end;
           ....
       finally
          query1.free;
       end;

Select no Firebird:

execute block 
returns (codigo varchar(18),
         valor double precision,
         conta integer, 
         rank integer, 
         acumulado double precision)
as
declare variable n integer;
begin
   n = 0;
   acumulado = 0;

   for select codigo,sum(valor) valor, count(*) ct from sigcaut2
       where valor&gt;0 and 
             data between '01/01/2015' and '12/31/2015'
       group by codigo
       order by valor desc
       rows 100
       into :codigo,:valor,:conta
   do
     begin
       n = n +1;
       rank = n;
       acumulado = acumulado + valor;
       suspend;
   end
end

 

Em outro “post” escrevi sobre obter o código do cliente gerado automático pelo banco e como retornar o seu valor usando “returning”.

Há situações que é necessário confirmar se uma operação foi aceita pelo banco (sem erro), e que de fator a linha foi inserida, alterada ou excluída do banco de dados.

Depois de submeter um comando de Insert, Update ou Delete para o banco pode-se adotar algumas estratégias para saber se houve sucesso:

  1. usar um Try/Exception para capturar uma exceção. Se o comando retornou uma exceção significa que o banco de dados criticou o comando. De outro lado há comandos que mesmo não retornando nenhum exceção NÃO garante que  conseguiu fazer… ex: envia um update e não encontra o registro – não gera exceção, mas também não fez;
  2. usar a propriedade  RowsAffected  para ler quantas linhas foram afetadas pelo último comando executado.

RowsAffected = 0    -&gt;  não efetuou nenhuma alteração;

RowsAffected = -1   -&gt; quando a operação não suporta ou o banco de dados não suporta obter retorno;

RowsAffected &gt; 0     -&gt; número de linhas que foram alteradas;

 

Exceção:   No MS-SQLServer há situações que o retorno é -1  em decorrência deste recurso ser desligado em triggers ou procedure quando omite o comando:  SET NOCOUNT ON

 

Exemplo:

Query1.sql.text := ‘update clientes set fone = ‘xxxx-xxxx’ where codigo=1′;

Query1.execSql;

if Query1.RowsAffected>0 then

showMessage(‘Sucesso’);

 

Até a versão 2.5 do Firebird não existe a figura da coluna Auto-Incremento que existe em outros banco de dados.

No Firebird, você deve criar um GENERATOR que ira cuidar de gerar a sequência e associar uma Trigger para incluir o sequencial na tabela.

Exemplo para criar uma tabela de cliente com código Auto-Incremento:
[code]
// criar o recurso para armazenar o auto-incremento
CREATE GENERATOR CODIGO_CLIENTE; (formato mais antigo)
ou
CREATE SEQUENCE CODIGO_CLIENTE; (formato novo)

// criar a tabela
CREATE TABLE FB_CLIENTE (
CODIGO INTEGER NOT NULL,
NOME VARCHAR(50),
ENDERECO VARCHAR(128),
CIDADE VARCHAR(32),
ESTADO VARCHAR(5)
);

// para marcar o valor inicial (é opcional)
ALTER SEQUENCE CODIGO_CLIENTE RESTART WITH 0;

// Trigger para gerar o próximo código
SET TERM ^ ;
CREATE OR ALTER TRIGGER FB_CLIENTE_GERAR_CODIGO FOR FB_CLIENTE
ACTIVE BEFORE INSERT POSITION 0
AS
begin
  new.codigo =  gen_id(CODIGO_CLIENTE,1);
end
^
SET TERM ; ^
[/code]

Usando insert para retornar o valor o código inserido:
[code]
insert into fb_cliente
(nome,endereco,cidade,estado)
values( :nome,:endereço,:cidade,:estado)
returning codigo into :codigo_cliente
[/code]
 

Com FireDAC, utilizar o método FDQuery1.OPEN e irá retornar o parâmetro INTO como na lista de FIELDS (código cliente);

Sabe aquele cadastro de cliente que você vai inserir um novo cliente e lá o código do cliente é uma coluna gerada com GENERATOR – (auto-incremento), pode ser uma grande dor de cabeça se não estiver seguro sobre o código inserido na tabela.
Muitas vezes precisa deste ID para utiliza-lo em outro lugar. Se errar o ID o registro final irá ficar errado, associando ao cliente errado…
Outra situação é, se demorar algum tempo para descobrir o ID e outro usuário inserir um outro cliente enquanto o app fazia alguma coisa…. vai dar confusão. É preciso garantir com precisão o ID que foi inserido no momento que o banco postou na tabela.

Para resolver estas situações o Firebird permite obter valores de retorno de um INSERT.

Exemplo:

   insert into cliente( nome, endereco, ...) values( :nome,:endereco,...)
   returning id_cliente into :id

Ao executar o comando de INSERT, o banco irá retornar no parâmetro o valor inserido pelo GENERATOR no parâmetro ID.

A API do firedac traz um componente que encapsula o nbackup do firebird o que facilita
personalizar o controle de backups. TFDFBNBackup.

Exemplo Nivel 1:

TNBackup.ExecuteNBackup(‘localhost’,’c:\dados\meubanco.fdb’,’sysdba’,’masterkey’,1,’c:\backup\b
ackup2.nbk’);

  • Segestão de como utilizar NIVEL (level):
    a) fazer backup FULL Nivel 0 para um intervalo de período (semanal);
    b) fazer backup Nivel 1, diário;
    c) fazer backup Nivel 2 para backup a cada hora.

Código base:

uses
FireDAC.Phys.IBWrapper,FireDAC.Phys.FB,FireDAC.Phys.FBDef,FireDAC.Comp.UI,FireDAC.Phys;

type

TNBackup = record
   private
     class function GetNBackup(AHost, ABanco, AUser, APass: string;
        ANivel: integer; ADestino: String): TFDFBNBackup;static;
     class function ExecuteNBackup(AHost, ABanco, AUser, APass: string;
        ANivel: integer; ADestino: String): boolean;static;
end;

class function TNBackup.ExecuteNBackup(AHost, ABanco, AUser, APass: string;
     ANivel: integer; ADestino: String): boolean;
begin
   result := false;
   with TNBackup.GetNBackup(Ahost,ABanco,AUser,APass,ANivel,ADestino) do
   try
      Backup; // gerar backup.
      result := true;
   finally
      free;
   end;
end;

class function TNBackup.GetNBackup(AHost, ABanco, AUser, APass: string;
    ANivel: integer; ADestino: String): TFDFBNBackup;
var
nBackup:TFDFBNBackup;
FDGUIxWaitCursorX: TFDGUIxWaitCursor;
FDPhysFBDriverLinkX: TFDPhysFBDriverLink;
begin
     result:=TFDFBNBackup.create(nil);
     try
        FDGUIxWaitCursorX:= TFDGUIxWaitCursor.Create(result);
        FDPhysFBDriverLinkX:= TFDPhysFBDriverLink.Create(result);
        with result do
        begin
           Level := ANivel;
           host := AHost;
           username := AUser;
           password := APass;
           protocol := ipTCPIP;
           Database := ABanco;
           backupfile := ADestino;
           DriverLink := FDPhysFBDriverLinkX;
        end;
   finally
      // liberar a instancia no metodo chamador
   end;
end;

Requer: https://github.com/amarildolacerda/helpers

Uses System.uJson;
type
   TMinhaClasse = class
   public
     Valor: Double;
     Codigo: string;
   end;

procedure TForm33.FormCreate(Sender: TObject);
var
mc: TMinhaClasse;
begin
    mc := TMinhaClasse.create;
    try
      mc.Codigo := '123456';
      mc.Valor := 10;
      ShowMessage(mc.asJson);
   finally
      mc.Free;
   end;
end;

Lembra quantas vezes você precisou fazer um Loop em um Dataset para fazer uma soma, uma
contagem ou qualquer outra coisa…
Não gosto de fazer de novo algo que já fiz antes… Pensando nisto passei a usar “anonimous
method” do delphi para executar para mim os trechos repetitivos dos loops…
Veja como ficou.

[code lang=”pascal”]
type
TDatasetHelper = class helper for TDataset
public
procedure DoLoopEvent(AEvent: TProc&lt;TDataset&gt;); overload;
end;

procedure TForm34.execute;
var total:Double;
begin
// abere o Dataset com os dados.
alQuery1.sql.Text := ‘select codigo, total valor from sigcaut1 where data&gt;=:data’;
alQuery1.ParamByName(‘data’).AsDateTime := strTodate(’01/01/2016′);
alQuery1.Open;
// fazer um loop para somar o total, usando metodos anonimos;
total := 0;
alQuery1.DoLoopEvent( procedure( ds:TDataset)
begin
total := total + ds.FieldByName(‘valor’).AsFloat; // executa o loop
end);
showMessage( FloatTOStr(total) ); // mostra o total da soma obtida no loop
end;

procedure TDatasetHelper.DoLoopEvent(AEvent: TProc;TDataset;);
var
book: TBookMark;
begin
book := GetBookmark;
try
DisableControls;
first;
while eof = false do
begin
AEvent(self);
next;
end;
finally
GotoBookmark(book);
FreeBookmark(book);
EnableControls;
end;
end;
[/code]

Código Original

Executar uma query em segundo plano (em paralelo) não …é difícil de fazer, o seu controle é que
pode ser mais complexo.
Para executar em segundo plano basta:

[code lang=”pascal”]
TThread.CreateAnonymousThread(procedure
begin
ALQuery1.sql.Text := ‘….’;
ALQuery1.Open;
end).Start;
[/code]

ou

[code lang=”pascal”]
TThread.CreateAnonymousThread(
procedure
var i:integer;
begin
for I := 0 to 10 do
begin
// faz alguma coisa…
AlQuery1.execSQL;
end;
end).Start;
[/code]

Um pensamento simplista é você criar uma conexão para cada QUERY em separado. Se você tem
um CONNECTION isolado para UMA QUERY, então é possível executá-la em paralelo dentro de
uma nova Thread;

Algumas idéias onde pode utilizar o processo em paralelo:
– quando precisa registrar um log em uma tabela;
– se for possível adiantar um SELECT que será utilizado mais a frente;
– se precisa rodar um loop que não tem dependência com os próximos passos da sequencia do
código;
– quando precisa fazer um somatório de dados na tabela para mostrar o seu valor na janela…. e
liberar o usuário para continuar fazendo outras coisas.
As vezes você pode por um SELECT em paralelo e disparar outra sequencia de código… e mais
adiante aguardar o primeiro SELECT concluir… para depois então continuar…. Este é assunto para
outro POST para tratar de TTASK.