Sim…. replicação para firebird… não tem ? tem sim… só trabalhar um pouco – nem tudo é como visão de brigadeiro..

A replicação consistem manter DUAS ou mais tabelas com conteúdo iguais – de tal forma que se obtém o mesmo resultado em qualquer uma das duas bases de dados.

Existe dois lados na replicação – um que tem os dados que precisam ser levados para o outro (publisher-aquele que publica) – o outro que recebe os dados publicados pelo servidor (subscriptor-aquele que se candidata a receber os dados). Por último a combinação de ambos em um só – ele é tanto publisher como subscriptor da mesma tabela;


Fase do desenvolvimento

  1. Preparar o Publisher para publicar dados para replicação (banco de origem);
  2. Preparar o Subscriptor para receber os dados publicados (banco de destino);
  3. Codificar os componente que monitoram as alterações do Publisher;
  4. Codificar a transferências dos dados da origem para o destino;

 

A título ilustrativo, vou adotar a tabela de PEDIDOs como base

Considere a seguinte tabela:

[code lang=”SQL”]

create table PEDIDOS (dcto varchar(10),
data Date,
cliente integer,
total numeric(15,4));

[/code]


Preparando o banco de origem em firebird

Como base para a replicação, vou criar uma coluna nova na tabela de pedidos na origem para indicar a chave de referência para a replicação – para fugir dos tipos auto-incrementos (já escrevi como fazer isto aqui no blog) vou usar uma coluna para identificar a chave de replicação que receberá uma representação texto para um GUID

alter table PEDIDOS add GID varchar(38);

[OFF] antes que alguém questione… não dá para usar auto-incremento em razão de atender a pré-condição de que o dado dever ser o mesmo nos dois banco de dados (traduzindo – não podem ser diferentes)

A coluna GID vai receber um valor por “trigger” toda vez que for incluída na tabela

[code lang=”SQL”]
SET TERM ^ ;
ALTER TRIGGER REPL_PEDIDOS_GID ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS
begin
/* Trigger Utilizado para Replicacao */
if ((new.gid is null)) then
new.gid = UUID_TO_CHAR( gen_uuid() );
end^
SET TERM ; ^
[/code]

[OFF] como se nota inclui na trigger o UPDATE – fiz isto para pegar linhas antigas que ainda não foram replicadas em nenhum processo anterior – caso ocorre uma replicação parcial;

Até aqui já temos a marcação de chave na tabela de origem para localizar as linhas que serão publicadas para replicação. Agora vamos criar uma tabela de controle de publicação das replicações

[code lang=”SQL”]
CREATE TABLE REPL_ITENS
(
TABELA Varchar(128), // recebe o nome da tabela alvo
GID Varchar(38), // recebe o GID da tabela alvo
TIPO Char(1), // I-insert U-update D-delete
DATA Date, // data e hora da alteração
ID integer, // sequencial interna
SESSION_ID integer // sessão da transação que criou – um luxo
);
CREATE INDEX REPL_ITENSDATA ON REPL_ITENS (DATA);
CREATE INDEX REPL_ITENSGID ON REPL_ITENS (GID);
CREATE INDEX REPL_ITENSID ON REPL_ITENS (ID);
CREATE INDEX REPL_ITENSTABELA ON REPL_ITENS (TABELA);
GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON REPL_ITENS TO SYSDBA WITH GRANT OPTION;
[/code]

Vamos precisar de um GENERATOR para popular a coluna ID da tabela e uma trigger para popular com o valor do GENERATOR. ver também

[code lang=”SQL”]
CREATE GENERATOR REPL_ITENS_GEN_ID;
SET TERM ^ ;
CREATE TRIGGER REPL_ITENS_ID FOR REPL_ITENS ACTIVE
BEFORE INSERT POSITION 0
AS
begin /* Replicacao Storeware */
new.id = gen_id(REPL_ITENS_GEN_ID,1);
new.session_id = rdb$get_context(‘SYSTEM’,’SESSION_ID’);
new.data = cast(‘now’ as date);
end^
SET TERM ; ^
[/code]

[OFF] para este caso não usei o GUID – vamos precisar garantir uma sequência na tabela que seja em ordem crescente – isto vai facilitar a codificação a frente.

 

Para fechar, vamos ensinar a tabela PEDIDOS como publicar as suas alterações:

[code lang=”SQL”]
SET TERM ^ ;
CREATE TRIGGER REPL_PEDIDOS_REG FOR PEDIDOS ACTIVE
AFTER INSERT OR UPDATE OR DELETE POSITION 0
AS
begin
/* Replicacao Storeware */
in autonomous transaction do
begin
if (inserting) then
insert into repl_itens ( tabela,gid,tipo)
values(‘PEDIDOS’,new.gid,’I’);
if (updating) then
insert into repl_itens ( tabela,gid,tipo)
values(‘PEDIDOS’,new.gid,’U’);
if (deleting) then
insert into repl_itens ( tabela,gid,tipo)
values(‘PEDIDOS’,old.gid,’D’);
end
end
^
SET TERM ;
[/code]


 

Preparando o banco de dados de destino

No banco de dados destino temos a mesma tabela de pedidos (o ideal que possua a mesma estrutura).

Considerando que durante a replicação dos dados ocorrerão momentos em que haverá UPDATEs e DELETEs para fazer – será necessário ter uma chave correspondente para a alteração do registro. Esta chave não pode ser controlado localmente pelo banco, ela precisa ser uma chave que tanto o banco de origem como o banco de destino sejam cooperativos. A este requisito a chave GID que recebe um GUID atente perfeitamente.

alter table PEDIDOS add GID varchar(38);

A “trigger” para gerenciar a coluna GID segue o mesmo comportamento do banco de origem.

[code lang=”SQL”]
SET TERM ^ ;
ALTER TRIGGER REPL_PEDIDOS_GID ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS
begin
/* Trigger Utilizado para Replicacao */
if ((new.gid is null)) then
new.gid = UUID_TO_CHAR( gen_uuid() );
end^
SET TERM ; ^
[/code]

[OFF] Note que a trigger checa se o GID é null – isto é importante pois quem vai gerar a chave para o GID é o banco que criou o registro (onde ele nasceu) e durante toda a sua vida precisa receber o mesmo valor nos dois banco de dados.

Agora já temos tudo que precisamos no banco de dados para controlar o que será replicado. Passemos a construir os códigos que vão fazer a replicação em si.


Codificando o select para monitorar as alterações na Origem

O select na origem, ou no publisher, será feito considerando que queremos somente as linhas alteradas para serem transferidas para o banco de destino.

Para isto precisamos fazer controle de quais linhas foram e quais linhas ainda não foram para o destino. Para isto foi que usei um GENERATOR para a tabela de controle   REPL_ITEMS->id

Uma vez executado a replicação precisamos guardar qual foi o ultimo ID utilizado para ser utilizado na próxima chamada:

[code lang=”SQL”]
select a.*, b.tipo repl_tipo from PEDIDOS a, REPL_ITEMS b
where a.gid = b.gid and b.id>:id
order by b.id
[/code]

Para tabelas com muitas linhas, pode ser interessante usar um derivação incluindo o número de linhas a sincronizar a cada chamada:

[code lang=”SQL”]
select first 1000 a.*, b.tipo repl_tipo from PEDIDOS a, REPL_ITEMS b
where a.gid = b.gid and b.id>:id
order by b.id
[/code]


Codificando a transferência dos dados para o destino

Chegou o momento de enviar o dados da Origem para o Destino….
Dependendo da coluna REPL_TIPO que vem do Select na Origem:
I – Faz um INSERT na tabela de destino;
U – Faz um UPDATE;
D – Faz um DELETE;

Esteja preparado para tratar algumas exceções:

  • quando for fazer um UPDATE e o registro ainda não se encontra no destino, precisando fazer o INSERT para iniciar o registro;
  • quando for fazer DELETE de uma registro que não existe;
  • fazer INSERT de registro que já tem chave primária idêntica no destino;
  • se esta inciando a sincronização de uma tabela que já existe, sinalizar a tabela de eventos com os dados já existentes;

 


 

 

Projeto no Git

 

 

 

Quando estamos rodando um código em um processo paralelo e internamente a Thread encontra pela frente uma EXCEPTION nada é apresentado para o usuário. Isto ocorre porque a Thread não tem como notificar a Thread Principal (do app) para mostrar a exceção ao usuário. Com isto não há um expediente para mostrar a exceção na thread principal.  escreve sobre o tema em seu blog Rob’s Technology Corner.

Robert propõe a rotina que gera erro para contextuar o problema:

[code lang=”pascal”]

procedure TForm5.Button1Click(Sender: TObject);
begin
Button1.Enabled := False;
SlowProc;
end;

procedure TForm5.FormDestroy(Sender: TObject);
begin
Task.Cancel;
end;

procedure TForm5.SlowProc;
begin
Task := TTask.Create( procedure
var
I : Integer;
begin
for I := 0 to 9 do
begin
if TTask.CurrentTask.Status = TTaskStatus.Canceled then
exit;
Sleep(1000);
if I = 2 then
raise EProgrammerNotFound.Create(‘Something bad just happened’);
end;
if TTask.CurrentTask.Status <> TTaskStatus.Canceled then
begin
TThread.Queue(TThread.CurrentThread,
procedure
begin
if Assigned(ListBox1) then
begin
Listbox1.Items.Add(’10 Seconds’);
Button1.Enabled := True;
end;
end);
end;
end);
Task.Start;
end;
[/code]

Executando o código é possível constatar que o procedimento levanta uma exceção e o usuário não recebe a informação de erro.

Stefan Glienke observando o que escreve Robert, propõe uma alteração em TTask para permitir tratar as exceções transparentes para o usuário e mais simples na implementação.
Glienke empresta de .NET uma implementação de Task.ContinueWith que permite continuar a execução após a ocorrência da exceção, veja como ficou.

[code lang=”pascal”]
unit ThreadingEx;

interface

uses
SysUtils,
Threading;

type
TAction<T> = reference to procedure(const arg: T);

TTaskContinuationOptions = (
NotOnCompleted,
NotOnFaulted,
NotOnCanceled,
OnlyOnCompleted,
OnlyOnFaulted,
OnlyOnCanceled
);

ITaskEx = interface(ITask)
[‘{3AE1A614-27AA-4B5A-BC50-42483650E20D}’]
function GetExceptObj: Exception;
function GetStatus: TTaskStatus;
function ContinueWith(const continuationAction: TAction<ITaskEx>;
continuationOptions: TTaskContinuationOptions): ITaskEx;

property ExceptObj: Exception read GetExceptObj;
property Status: TTaskStatus read GetStatus;
end;

TTaskEx = class(TTask, ITaskEx)
private
fExceptObj: Exception;
function GetExceptObj: Exception;
protected
function ContinueWith(const continuationAction: TAction<ITaskEx>;
continuationOptions: TTaskContinuationOptions): ITaskEx;
public
destructor Destroy; override;

class function Run(const action: TProc): ITaskEx; static;
end;

implementation

uses
Classes;

{ TTaskEx }

function TTaskEx.ContinueWith(const continuationAction: TAction<ITaskEx>;
continuationOptions: TTaskContinuationOptions): ITaskEx;
begin
Result := TTaskEx.Run(
procedure
var
task: ITaskEx;
doContinue: Boolean;
begin
task := Self;
if not IsComplete then
DoneEvent.WaitFor;
fExceptObj := GetExceptionObject;
case continuationOptions of
NotOnCompleted: doContinue := GetStatus <> TTaskStatus.Completed;
NotOnFaulted: doContinue := GetStatus <> TTaskStatus.Exception;
NotOnCanceled: doContinue := GetStatus <> TTaskStatus.Canceled;
OnlyOnCompleted: doContinue := GetStatus = TTaskStatus.Completed;
OnlyOnFaulted: doContinue := GetStatus = TTaskStatus.Exception;
OnlyOnCanceled: doContinue := GetStatus = TTaskStatus.Canceled;
else
doContinue := False;
end;
if doContinue then
continuationAction(task);
end);
end;

destructor TTaskEx.Destroy;
begin
fExceptObj.Free;
inherited;
end;

function TTaskEx.GetExceptObj: Exception;
begin
Result := fExceptObj;
end;

class function TTaskEx.Run(const action: TProc): ITaskEx;
var
task: TTaskEx;
begin
task := TTaskEx.Create(nil, TNotifyEvent(nil), action, TThreadPool.Default, nil);
Result := task.Start as ITaskEx;
end;

end.
[/code]

Como usar a nova implementação de TTask…
[code lang=”pascal”]
TTaskEx.Run(
procedure
begin
Sleep(2000);
raise EProgrammerNotFound.Create(‘whoops’)
end)
.ContinueWith(
procedure(const t: ITaskEx)
begin
TThread.Queue(nil,
procedure
begin
ShowMessage(t.ExceptObj.Message);
end);
end, OnlyOnFaulted);
[/code]
 

Ver PPL – TTask an example in how not to use

A chave primária é responsável em manter a integridade da tabela no banco de dados e o índice de maior eficiência disponível.

Deveríamos ter uma regra obrigatória:
“NAO PODE DEIXAR DE INDICAR A CHAVE PRIMÁRIA”

Para localizar as tabelas que não possuem chave primária executar:

[code lang=”SQL”]
SELECT RDB$RELATION_NAME AS Tabela
FROM RDB$RELATIONS
WHERE RDB$RELATION_TYPE IN (0, 4, 5)
AND (RDB$SYSTEM_FLAG = 0 OR RDB$SYSTEM_FLAG IS NULL)
AND RDB$RELATION_NAME NOT IN
(SELECT RDB$RELATION_NAME FROM RDB$RELATION_CONSTRAINTS
WHERE RDB$CONSTRAINT_TYPE = ‘PRIMARY KEY’)
ORDER BY RDB$RELATION_NAME;
[/code]

fonte: Firebird Conference 2014 – Ivan Prenosil

Este artigo foi revisão – acesse o link aqui

Sim…. replicação para firebird… não tem ? tem sim… só trabalhar um pouco – nem tudo é como visão de brigadeiro..

A replicação consistem manter DUAS ou mais tabelas com conteúdo iguais – de tal forma que se obtém o mesmo resultado em qualquer uma das duas bases de dados.

Existe dois lados na replicação – um que tem os dados que precisam ser levados para o outro (publisher-aquele que publica) – o outro que recebe os dados publicados pelo servidor (subscriptor-aquele que se candidata a receber os dados). Por último a combinação de ambos em um só – ele é tanto publisher como subscriptor da mesma tabela;


Fase do desenvolvimento

  1. Preparar o Publisher para publicar dados para replicação (banco de origem);
  2. Preparar o Subscriptor para receber os dados publicados (banco de destino);
  3. Codificar os componente que monitoram as alterações do Publisher;
  4. Codificar a transferências dos dados da origem para o destino;

 

A título ilustrativo, vou adotar a tabela de PEDIDOs como base

Considere a seguinte tabela:

[code lang=”SQL”]

create table &nbsp;PEDIDOS &nbsp;(dcto varchar(10),
data Date,
cliente integer,
total numeric(15,4));

[/code]


Preparando o banco de origem em firebird

Como base para a replicação, vou criar uma coluna nova na tabela de pedidos na origem para indicar a chave de referência para a replicação – para fugir dos tipos auto-incrementos (já escrevi como fazer isto aqui no blog) vou usar uma coluna para identificar a chave de replicação que receberá uma representação texto para um GUID

alter table PEDIDOS add GID varchar(38);

[OFF] antes que alguém questione… não dá para usar auto-incremento em razão de atender a pré-condição de que o dado dever ser o mesmo nos dois banco de dados (traduzindo – não podem ser diferentes)

A coluna GID  vai receber um valor por “trigger”  toda vez que for incluída na tabela

[code lang=”SQL”]
SET TERM ^ ;
ALTER TRIGGER REPL_PEDIDOS_GID ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS
begin
/* Trigger Utilizado para Replicacao */
if ((new.gid is null)) then
new.gid = UUID_TO_CHAR( gen_uuid() );
end^
SET TERM ; ^
[/code]

[OFF] como se nota inclui na trigger o UPDATE – fiz isto para pegar linhas antigas que ainda não foram replicadas em nenhum processo anterior – caso ocorre uma replicação parcial;

Até aqui já temos a marcação de chave na tabela de origem para localizar as linhas que serão publicadas para replicação. Agora vamos criar uma tabela de controle de publicação das replicações

[code lang=”SQL”]
CREATE TABLE REPL_ITENS
(
TABELA Varchar(128), // recebe o nome da tabela alvo
GID Varchar(38), // recebe o GID da tabela alvo
TIPO Char(1), // I-insert U-update D-delete
DATA Date, // data e hora da alteração
ID integer, // sequencial interna
SESSION_ID&nbsp;integer // sessão da transação que criou – um luxo
);
CREATE INDEX REPL_ITENSDATA ON REPL_ITENS (DATA);
CREATE INDEX REPL_ITENSGID ON REPL_ITENS (GID);
CREATE INDEX REPL_ITENSID ON REPL_ITENS (ID);
CREATE INDEX REPL_ITENSTABELA ON REPL_ITENS (TABELA);
GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON REPL_ITENS TO SYSDBA WITH GRANT OPTION;
[/code]

Vamos precisar de um GENERATOR para popular a coluna ID da tabela e uma trigger para popular com o valor do GENERATOR. ver também

[code lang=”SQL”]
CREATE GENERATOR REPL_ITENS_GEN_ID;
SET TERM ^ ;
CREATE TRIGGER REPL_ITENS_ID FOR REPL_ITENS ACTIVE
BEFORE INSERT POSITION 0
AS
begin /* Replicacao Storeware */
new.id = gen_id(REPL_ITENS_GEN_ID,1);
new.session_id = rdb$get_context(‘SYSTEM’,’SESSION_ID’);
new.data = cast(‘now’ as date);
end^
SET TERM ; ^
[/code]

[OFF] para este caso não usei o GUID – vamos precisar garantir uma sequência na tabela que seja em ordem crescente – isto vai facilitar a codificação a frente.

 

Para fechar, vamos ensinar a tabela PEDIDOS como publicar as suas alterações:

[code lang=”SQL”]
SET TERM ^ ;
CREATE TRIGGER REPL_PEDIDOS_REG FOR PEDIDOS ACTIVE
AFTER INSERT OR UPDATE OR DELETE POSITION 0
AS
begin
/* Replicacao Storeware */
in autonomous transaction do
begin
if (inserting) then
insert into repl_itens ( tabela,gid,tipo)
values(‘PEDIDOS’,new.gid,’I’);
if (updating) then
insert into repl_itens ( tabela,gid,tipo)
values(‘PEDIDOS’,new.gid,’U’);
if (deleting) then
insert into repl_itens ( tabela,gid,tipo)
values(‘PEDIDOS’,old.gid,’D’);
end
end
^
SET TERM ;
[/code]

Parte 2


Algumas vezes já me declarei com pouca disposição em escrever uma infinidade de linhas de código para fazer coisas simples.
Depois de ver outros artigos tratando sobre a geração de chave de relacionamento em “Master x Detail” e ver códigos de colegas montando engenhocas para obter a chave gerada pelo banco de dados (auto incremento), vou arriscar por pimenta nesta conversa.

Contexto: É fato que é muito comum gerar colunas de auto incremento no banco de dados para criar o relacionamento entre tabelas. Dependendo do banco de dados isto é efetuado por tipagem da coluna direto na tabela como auto incremento (ex: mysql) ou em outros casos se usa triggers para gerar a sequência (ex: Firebird); Num ou outra situação ocorre a mesma coisa, o identificador é gerado pelo servidor sem conhecimento do aplicativo.
Neste contexto é possível obter a chave gerada diretamente na mesma instrução de INSERT como ocorre no Firebird ou ainda, através de recurso da API de acesso ao banco como existe no FIREDAC.

Como fazer com comando direto no Firebird

No FireDAC há a possibilidade de escrever diretamente por comandos para a API do FireDAC e instruí-lo a retornar através de “preprocessing”:

[code]
insert into fb_pedido
(codigo,qtde)
value( :codigo, :qtde)
returning id_pedido {into :id_pedido}
[/code]

Documentação Embarcadero

Proposta para simplificar:
Já há algum tempo que as linguagens incluem um gerador de identificador único para múltiplos propósito – falando de GUID – que gera uma sequência única por máquina a cada chamada (ligado ao relógio).
Então a idéia é criar na tabela pedido uma coluna que recebe a nova chave única que servirá para o relacionamento do pedido (Master & Detail):
Firebird:

[code]
// GID – coluna para receber o GUID (texto)
alter table tb_pedido add GID varchar(38) not null;
[/code]

usando o delphi, gerar localmente o GID e incluí-lo no insert.

[code]
var gid:string;

gid := GuidToString( TGuid.NewGuid ); //(SysUtils) chave de relacionamento do pedido

insert into tb_pedido
( gid, codigo, qtde)
values( :gid, :codigo, :qtde)

[/code]

Uma vez gerado a chave GID (GUID ID), os itens passam a receber a mesma informação para indicar o relacionamento entre as tabelas… ou seja, não precisamos daqueles “auto incrementos” que as vezes traz uma lista de códigos associados.

 

Excluir uma chave primária quando se tem uma ferramenta visual a frente não é uma operação complicada de se fazer no Firebird.
O problema mesmo… é quando precisamos fazer isto por um script…
O Firebird usa um mapeamento de nomes para a chave primária que cria – diferente do nome do índice e passa a fazer parte da lista de “constraints” da tabela.
Para excluir uma “constraint” é necessário descobrir o seu nome e aplicar a “DROP” da constrait. A seguir código para um procedimento que procura o nome da “constraint” e executa o “DROP” da mesma.

Como usar: Execute Procedure RDB$DELETE_PRIMARYKEY(‘produtos’);

[code]

SET TERM ^;

CREATE OR ALTER PROCEDURE RDB$DELETE_PRIMARYKEY (
tabela varchar(128))
as
declare variable nome varchar(128);
declare variable d varchar(128);
declare variable stm varchar(1024);
begin

— localiza o nome da constraint
select rdb$constraint_name from rdb$relation_constraints
where rdb$relation_name= UPPER(:tabela)
AND rdb$constraint_type=’PRIMARY KEY’
into :nome;

— monta o comando
stm = ‘ALTER TABLE ‘||:tabela||’ DROP CONSTRAINT ‘|| coalesce(:nome,’INDEF’);

if (:nome is not null) then
begin
execute statement stm;
end else
exception erro ‘Nao encontrei a chave primaria para apagar’;

suspend;
end^

SET TERM ; ^

[/code]

Sabe todos aquelas linhas de código para incluir um item no menu “by code”…

  • inicializa o item de menu;
  • adiciona os parametros;
  • cria o método para o evento OnClick…
  • atribui o evento;
  • adiciona o item à lista;

Exemplo VCL para incluir um item de menu usando código:

[code]
procedure TForm40.ClickMenuItem(sender:TObject);
begin
showMessage(‘menu sem anonimous’);
end;
procedure TForm40.FormCreate(Sender: TObject);
var it:TMenuItem;
begin
it := TMenuItem.Create(MainMenu1);
it.caption := ‘Teste de um menu sem anonimous’;
it.OnClick := ClickMenuItem;
MainMenu1.Items.Add(it);
end;
[/code]

Métodos “anonimous” é uma poderosa ferramenta presente no Delphi na era XE, que encurta caminhos complexos. Ao reduzir código ao mesmo tempo aumentamos qualidade (com menor ocorrência de bugs) bem como menor tempo de implementação. Se considerar que o maior tempo se gasta em testes e correções de defeitos, então o ganho é exponencial.

Exemplo usando “Anonimous Method”:

[code]
uses VCL.Menus.Helpers;
procedure TForm40.FormCreate(Sender: TObject);
begin
MainMenu1.Items.CreateAnonimous(‘Teste menu com anonimous’,
procedure begin
showmessage(‘ok…’);
end);
end;
[/code]

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

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]

RDB$DB_KEY é uma coluna existente em todas as tabelas do banco de dados, mas que encontra-se escondida visível somente se for indicada literalmente no SELECT.

A coluna recebe um número sequencial de linhas na tabela e pode ser utilizada para fazer UPDATE para as linhas. Como um coluna nativa do FIREBIRD, que representa o endereço da linha, não requer índice individualizado para ela, já otimizada internamente ao banco de dados.

O RDB$DB_KEY é alterado toda vez que faz  BACKUP/RESTORE, então não é possível utiliza-lo para fazer relacionamento com outras tabelas, já que não é fixo ao longo do tempo.

O uso do RDB$DB_KEY é mais rápido seu uso se comparado a chave primária, já que internamente o índice aponta o endereço do registro para o RDB$DB_KEY.

É particularmente útil quando existem linhas repetidas em uma tabela que não possui chave primária por indicar a única diferença entre as linhas (repetidas).

 

Pesquisar: RDB$DB_KEY

Fonte: Firebird FAQ

How to delete duplicate records (when table doesn’t have a primary key column)?

 

You can use the ‘secret’ RDB$DB_KEY feature of Firebird. RDB$DB_KEY is a ‘hidden’ record that identifies rows in each SQL statement. For example, if you have a table TABLE1 without primary key and have columns COL1 and COL2 which are the same, you can delete the extra rows with the following statement:

DELETE FROM TABLE1 t1
WHERE EXISTS (
SELECT 1 FROM TABLE1 t2
WHERE t1.COL1 = t2.COL1 and t1.COL2 = t2.COL2
AND t1.RDB$DB_KEY < t2.RDB$DB_KEY
);

You can control whether the record is considered duplicate by including the relevant table columns in the WHERE clause (in most cases, you will want to include all columns).

The above code assumes that columns are NOT NULL or you want to treat rows with NULLs as different. If you want to remove duplicate rows that include NULLs in same columns, you can use the following code:

DELETE FROM TABLE1 t1
WHERE EXISTS (
SELECT 1 FROM TABLE1 t2
WHERE t1.COL1 IS NOT DISTINCT FROM t2.COL1
AND t1.COL2 IS NOT DISTINCT FROM t2.COL2
AND t1.RDB$DB_KEY < t2.RDB$DB_KEY
);

The IS NOT DISTINCT FROM operator is available since Firebird 2.0.

Considerando isoladamente a extração de dados OVER é uma o mais interessante recurso incluído ao Firebird.

Há uma possibilidade grande de combinações onde OVER se aplica – não vamos conseguir cobrir tudo somente neste POST, então vamos nos concentrar a um texto já tratando neste blog em relação a versão 2.5, que é apresentação de RANK, ou seja, mostrar a posição que se encontra o dado dentro da lista..

Ex: Qual a posição do vendedor XXX no RANK de vendas;

A implementação segue um padrão de estrutura tratado como “analytical functions”, ou seja, funções analíticas ou de análise de dados.

A ideia é usar um SELECT para separar uma janela de dados em paralelo do mesmo dado e montar um relacionamento dos dados do SELECT com os dados da JANELA para efetuar algum cálculo… no caso, calcular o RANK.
Tomando o exemplo da documentação emprestado

[code language=”sql”]select id, salary,
dense_rank() over (order by salary),
rank() over (order by salary),
row_number() over (order by salary),
sum(1) over (order by salary)
from employee
order by salary;

The result set:
id salary dense_rank rank row_number sum
— —— ———- —- ———- —
3 8.00 1 1 1 1
4 9.00 2 2 2 2
1 10.00 3 3 3 4
5 10.00 3 3 4 4
2 12.00 4 5 5 5

[/code]

Fazendo uma leitura do exemplo, em primeiro passo o engine monta o SELECT – quando encontra o comando OVER, ele separa uma janela em paralelo com os dados do mesmo SELECT (agora tem 1 cópia do mesmo dado do SELECT)… o que vem depois do OVER (….) é a forma em que o dado será organizado – no exemplo ORDER BY Salary; Sobre a janela de dados aplica a função indicada – o RANK();

Pegando carona no exemplo:

Há uma diferença entre RANK() e DENSE_RANK(),  em relação a forma de sequencia montada quando existe repetição do dado… enquanto RANK() continua a contagem quando encontra repetições, no DENSE_RANK() o número é mantido e segue a mesma sequencia do número anterior.

ROW_NUMBER() é uma contagem simples de linhas sem diferenciar dados repetidos como faz o RANK();