Apostila Delphi
Apostila Delphi
Apostila Delphi
Delphi
Delphi – Exemplos de Record Helper
#delphi #helper #record #fevereiro/2016
Há uma teoria que prega uma relação crescente de BUGs com o maior número
de linhas de um programa... então vamos diminiur as linhas digitadas
utilizando o poder de Helpers.
// string to float
total := texto.ToFloat;
// string to datetime
datahora := texto.ToDateTime;
// extrair a data
datahora.ToDate;
// extrair a hora
datahora.ToTime;
Obs: tem vários outros metodos nas units indicadas... vale estudá-los;
Fontes:https://github.com/amarildolacerda/helpers
PDF: https://github.com/amarildolacerda/docs/blob/master/DelphiXFirebid.pdf
Delphi – Obter um JSON de um TDataset
#delhi #json #helper #fevereiro/2016
Depois de muitas idas e vindas vou deixar o resultado do trabalho que já fiz
sobre o assunto:
Fonte: https://github.com/amarildolacerda/helpers/blob/master/Data.DB.Helper.pas
https://github.com/amarildolacerda/helpers/blob/master/System.uJSON.pas
Delphi – Criando Arquivo ZIP
#delphi #zip #fevereiro/2016
Hoje, ao chegar na empresa recebi notícia que uma das nossas API de troca de
dados com fornecedores não estava funcionando na versão mais recente do
Delphi (usamos o XE6 na máquina de montagem de discos).
Olhando o assunto, notamos que o componente que foi utilizado não tem
atualização pelo seu criador... o que fazer agora ? Reposta óbvia, reescrever.
A unit System.Zip que vem com a versão do delphi é muito fácil de usar,
salvou o dia – veja os exemplos:
Criando um ZIP:
Descompactando o ZIP:
(fonte: https://github.com/amarildolacerda/helpers/blob/master/Data.DB.Helper.pas)
var total:double;
begin
query1.run(
procedure (ds:Tdataset) // rotina a executar em paralelo.
begin
ds.first;
while ds.eof=false do
begin
total := total + xxxxx;
ds.next;
end;
end);
end;
Limitações:
• o uso de Helpers esta limitado a um por unit para a mesma classe;
• dificuldade em lidar com variáveis novas locais ao helper;
(fonte: https://github.com/amarildolacerda/helpers/blob/master/System.SysUtils.Helper.pas )
Delphi - Computação Paralela, em Threads anônimas
#delphi #parallel #thread #janeiro/2016
Tthread.CreateAnonymousThread(
procedure begin
TThread.Queue(nil,
procedure begin
end);
end
).Start;
#firebird #delphi #sql
Delphi - Processamento paralelo com TTask #task
#parallel #thread
Na versão XE7 foi incorporado ao Delphi o conceito de processamento paralelo. Antes já era
possível fazer algo parecido utilizando bibliotecas de terceiros
[http://www.omnithreadlibrary.com/index.htm http://andy.jgknet.de/blog/bugfix-units/asynccalls-
29-asynchronous-function-calls/].
Para tentar simplificar o conceito, diz paralelo quanto consegue executar dois ou mais processos ao
mesmo tempo, daí o nome "paralelo".
var
tsk: array [0 .. 2] of ITask;
i, n: integer;
begin
tsk[0] := TTask.create(
procedure
begin
TThread.Queue(nil, procedure
begin
caption := 'xxx'; // sincronizar a atualização da janela.
end);
end);
tsk[0].Start; // inicia o processamento antes de criar as TTask seguintes
tsk[2] := TTask.create(
procedure
var
k: integer;
begin
i := 1;
sleep(1000);
for k := 0 to 10000 do
inc(i);
end);
tsk[1] := TTask.create(
procedure
var
k: integer;
begin
n := n;
for k := 0 to 1000 do
inc(n);
add( 'N');
end);
tsk[2].Start; // inicia o processamento
tsk[1].Start;
Enquanto ITask aguarda uma chamada x.start; para iniciar o processamento em uma thread
própria... o IFuture já inicia o processamento de imediato e aceita aguardar uma resposta após o
término de execução.
TPessoa = record
nome:string;
idade:integer;
end;
tsk = TTask.Future<TPessoa>(
function:TPessoa
begin
// código.....
result.nome := 'Nome'; // resposta para o processamento
result.idade := 18;
end);
ver: #ttask
type
TDatasetHelper = class helper for TDataset
public
procedure DoLoopEvent(AEvent: TProc<TDataset>); 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>=: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;
Requer: https://github.com/amarildolacerda/helpers
Uses System.uJson;
type
TMinhaClasse = class
public
Valor: Double;
Codigo: string;
end;
Exemplo Nivel 0:
nbackup -B 0 localhost:c:\dados\meubanco.fdb c:\backup\backup.bak -U sysdba -P masterkey
Exemplo Nivel 1:
nbackup -B 1 localhost:c:\dados\meubanco.fdb c:\backup\backup1.bak -U sysdba -P masterkey
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');
Codigo base:
uses
FireDAC.Phys.IBWrapper,FireDAC.Phys.FB,FireDAC.Phys.FBDef,FireDAC.Comp.UI,FireDAC.P
hys;
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;
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;
Firebird - CTE "Common Table Expression"
firebird#d
# elphi#s ql
ComplexasconsultasemFirebirdpodemserresolvidasutilizandoCTE.
CTEéumaconstruçãoquemontaumatabeladememóriaaserutilizadaemumSELECTeédesmontadoquando
terminaaexecuçãodoSELECT.
CREATETABLEvendas(codigovarchar(20),datadate,qtdenumeric(18,4),valornumeric(18,4));
withVendasDoMesas
(--agrupaosdadosdevendas
selectCodigo,
extract(monthfromdata)Mes,
extract(yearfromdata)Ano,
sum(qtde)Qtde,
sum(valor)Total
fromvendas
groupby1,2,3--agrupaascolunas
)
select*fromVendasDoMes--montaaconsulta
whereano=2015andmes=12
orderbymes,ano
4) Conclusão:
OSELECTiráretornaraquantidadeetotaldevendasdetodososprodutonomêsdeDezembrode2015.
Teste unitário em Firebird !!!
Aslinguagensdeprogramaçãoemgeralevoluíramnosúltimosanosemdireçãoàqualidade.
Eosbancodedados?
Aindaquenãosejapopular,comumpoucodeimaginaçãoépossívelfazertesteunitárioembancode
dadosfirebird.
Passos para fazer em firebird:
1)criarummodelodeexceptionpararetornarinformações;
createoralterexceptiontest_Error'Error:';
2)montarumesqueletodaprocedure,funçãooupackageasertestada;
SETTERM^;
CREATEORALTERPROCEDUREDECODEDATE(datadate)
returns(anointeger,mesinteger,diainteger)
as
begin
DIA=EXTRACT(DAYFROMDATA);
MES=EXTRACT(MONTHFROMDATA);
ANO=EXTRACT(YEARFROMDATA);
suspend;
end^
SETTERM;^
SETTERM^;
executeblockas
declarevariabledtdate;
declarevariableydoubleprecision;
declarevariablemdoubleprecision;
declarevariableddoubleprecision;
begin
--realizaroteste
dt=cast('now'asdate);
selecty,m,dfromDecodeDate(:dt)
into:y,:m,:d;
if(:y<>extract(yearfrom:dt))then
exceptiontest_Error'DecodeDateYear';
if(:m<>extract(monthfrom:dt))then
exceptiontest_Error'DecodeDateMonth';
if(:d<>extract(dayfrom:dt))then
exceptiontest_Error'DecodeDateDay';
end^
SETTERM;^
Verexemplocompleto:https://github.com/…/08.03.002_firebird_packages_dateutils.…
Firebird - Trabalhando com 1.000.000 de itens
cadastrados
#brincandoComFirebird
Na escola, meus amigos gostavam de jogar vídeo games, eu não sei nada - o que me fascinava
mesmo era como eram feitos - até "assembly" foi aprender por conta disto.
Depois fiquei fascinado com um tal de Lotus 123. O Clipper, há ninguém lembra mais, era o bicho.
Já usava scripts (nem existia js)... Mas tudo mudou mesmo com Delphi 1.0... É ai veio
Delphi+Firebird, não quero mais sair.
O lance agora é brincar de firebird com delphi... video game - só usa o que já existe, pronto, sem
criatividade do participante.
Para brincar de Firebird, queria fazer um desafio - quero por 1 milhão de itens no cadastro de
produto e fazer SELECT para localizar um produto usando só uma parte do nome ( com
performance... por favor).
e) o SELECT... deve retornar somente os produtos que contenham o nome "lindinha" + "2016"
Vou subir os primeiros 100.000 produtos no GIT, depois passo o link... com uma lista contendo os
inserts... quem tiver alguns podem mandar... até completar 1.000.000;
A Tabela:
Com esta quantidade de registros em uma tabela que contém somente 4 colunas, o desempenho do
SELECT:
a) 1.327 seg - usando “like” que não usa índice ‘%lindinha%’ (no meio do nome)
b) 1.453 seg - se fizer “like” para buscar um nome que não existe no banco de dados
c) 0,047 seg - quando usa like que busca pelo índice ‘Boneca%’ ( no início do nome)
Acreditando que estes números podem representar maior impacto em produção considerando a
associação Quantidade de Acessso x Numero de Colunas na Tabela, então vamos reestruturar a
estratégia de busca.
Observando o resultado "c" nota-se que o motor utilizou o índice por nome. Então o que precisa ser
feito é conseguir alterar a estratégia de tal forma que retorne as mesmas linhas de "a", mas
utilizando índice para otimizar a busca.
Restruturando:
1) criar ums nova tabela que recebe palavras chaves do nome e associe com o código do produto;
2) criar índice para a nova tabela (um para cada coluna) código, palavra;
3) criar trigger para fazer a atualização da tabela de palavra chave toda vez que o usuário inserir,
apagar ou alterar um nome na tabela de produtos;
Feito a mudança na estratégia de busca na tabela, gostaria de melhorar o resultado na situação “a”
para que retorne em menor tempo - mais próximo ao registrado em "c". (fontes em:
https://github.com/amarildolacerda/brincandoComFirebird )
Reestruturado os dados de forma que fosse possível fazer o SELECT (que retorna as mesmas linhas
do exemplo “a”):
select codigo,nome
from fb_prod a, palavra_chave_fb_prod b
WHERE
a.codigo=b.CODIGO
and
b.palavra like 'lindinha%'
AND
b.palavra like '2016%'
Lógica:
- usuário insere um registro novo;
- a trigger usa a procedure que fazer a atualização da tabela de palavras chave;
- a procedure de atualização usa um SPLITTER para quebrar o nome do produto em palavras
chaves e guarda o resultado no dicionário de palavras chave ( que agora permite índice de
otimização ) Os fontes estão no GIT para baixar.
https://github.com/amarildolacerda/brincandoComFirebird
Faça os testes ai na sua base de dados e note como vai melhora muito o resultado da pesquisa.
onde:
ValordoFinanciamento=30000,00
TaxadeJuros=5%am
Parcelas=36
selectPKG_FINANCE.pmt(30000,5,36)FROMRDB$DATABASE
Resposta:1813,03
Parabaixarocódigo:https://github.com/amarildolacerda/firebird
Como utilizar:
Tomando por base que a maior parte é publicada como funções ....
select pkg_dateutils.dayOfTheWeek('today') from rdb$database
Para ver uma lista completa fazer leitura dos fontes onde (no final) consta a rotina de teste da
“Package”;
Chutar o Banco de Dados e depois reclamar não é
racional
#firebird
Nos últimos meses dedico algum tempo lendo as mensagens postadas nos foros e a tempo estava me
coçando para dar meu palpite, dado aos erros básicos que vejo - que vou tentar lembrar de alguns.
1) fazer um select de todas as linhas da tabela e depois fazer um filtro para pegar um único registro.
Correto: é construir um select que retorna só registros de interesse; Ex: select codigo,nome from
usuario where codigo=’123’
2) fazer pouca gestão sobre índices é um erro; Correto: é você por no banco de dados somente
índices que sejam favoráveis... lembrando, um índice demanda tempo de processamento quando faz
INSERT, UPDATE ou DELETE; O SELECT demanda tempo para o motor do banco de dados
escolher qual índice é melhor (no prepare); Então não crie muitos índices se você executa muitas
atualizações... No Update, não altere colunas que não sofreram alteração, para não demandar tempo
para atualizar o índice ( altere somente o necessário );
3) colunas que são candidatas a índice são aquelas que fazem parte de uma WHERE, ORDEM BY
ou GROUP BY... nem todos devem ser transformados em índices... somente alguns... entenda como
o banco de dados usa as estatística para escolher qual o índice é mais adequado para inclui no plano
de otimização;
4) índice composto ou simples ? depende do banco de dados... no Firebird sempre que testei o
resultado melhor foi obtido em índices simples Ex: create index cadastroNome on
produtos(codigo,nome)..... pode não ser tão eficiente quanto criar 2 índices... analise o resultado
antes de criá-los;
5) os índices são bons se a coluna registrar o maior numero possível de informações diferentes... por
isto o melhor índice é o da chave primária, pois existe somente uma linha possível, nenhuma é igual
a outra. Um índice de FILIAL em uma tabela pode não valer NADA se a empresa tem uma única
loja... o mesmo vale para uma coluna S ou N - resulta em muitas linhas com o mesmo valor;
6) usar funções dentro de WHERE ou GROUP BY deve ser avaliado com cuidado, na dúvida evite-
os.
7) não use NOT IN ou <> em uma WHERE, em geral o gerenciador ignora todos os índices nestas
situações... Ex: Errado: select qtde from vendas where data<>’today’ (isto mata o índice)... prefira
Correto: select qtde from vendas where ‘today’ >data and ‘today’ < data (isto vai permitir usar o
índice de data);
8) não tenha preguiça.... não pode fazer “select * from xxxxx” (digite as colunas que vai precisar),
isto vai economizar tempo do servidor e de banda de transmissão dos dados;
9) se a sua tabela tem menos de 1000 linhas.... é provável que nem precise de índice. Mas se ela tem
1 milhão de linhas, não dá para ignorar o índice;
10) evite até a morte em criar uma tabela sem indicar a sua chave primária; Bancos de dados foram
feitos para ter chave primária na mais profundo da sua concepção. De a ele uma chave primária para
que ele fique feliz;
11) sempre que for fazer um SELECT tente minimizar ao máximo o número de registro que ele irá
buscar no banco de dados... Se vai precisa da QTDE vendida e o NOME do produto, não faça 2
SELECTs.... junte os 2 em um único SELECT;
12) se acabou de buscar os dados de um cadastro.... não vai buscar de novo o que já conhece...
guarda na memória e reutiliza a informação que já foi obtida. Por isto o GOOGLE é tão rápido - ele
memoriza tudo e vai no buscar somente dados que ainda não conhece.
13) No FIREBIRD, deixar conexões sem dar commit ou as vezes em aberto sem encerrar
adequadamente pode deixar lento o COLETOR DE LIXO do firebird... então inicie e termine um
processo... de preferência, encerre a conexão - isto vai trazer um benefício muito grande para o
banco de dados..... AFINAL devemos escrever para o banco de dados, só assim ele responderá com
eficiência.
Para fechar, “backup “ você vai precisar um dia... faça testes para saber se ele é confiável - não
deixe para descobrir quando for precisar. Não deixe-o no mesmo disco (de preferência, leve para
casa);
Firebird 3.0 - Criando funções
#firebird
Estrutura
SET TERM ^ ;
create or alter function StartOfMonth(PDate date) returns date...
as -- retorna a data do primeiro dia do mes
begin
return PDate - extract(day from PDate)+1;
end^
SET TERM ; ^
Utilizando a função
select StartOfMonth( cast('01/10/2016' as date) ) from RDB$Database
São diversas as situações onde é aplicável dimensionamento de dados por tempo - melhor utilizar
exemplos:
Ex: Deseja saber qual dia da semana as vendas são mais concentradas - Seg, Ter, Qua, Qui, Sex,
Sab, Dom....
with CTE as
(
select data,wdia,cdia from DIM_DATE('12/01/2015','12/31/2015')
)
elect d.wdia as dia, d.cdia as cdia, sum(valor) as Valor
from cte d join TAB_VENDAS a on (d.data=a.data)
group by d.wdia,d.cdia
SET TERM ^ ;
CREATE OR ALTER PROCEDURE DIM_DATE (
p_data_de date,
p_data_ate date)
returns (
data date,
dia integer,
wdia integer,
cdia varchar(1),
mes integer,
ano integer,
semestre integer,
trimestre integer,
nsemana integer,
inimes date,
fimmes date)
as
declare variable dt date;
begin
dt = :p_data_de;
while (dt <= :p_data_ate ) do
begin
data = :dt;
dia = extract(day from :dt);
suspend;
dt = dt+1;
end
end^
SET TERM ; ^
GRANT EXECUTE ON PROCEDURE DIM_DATE TO SYSDBA;
#firebird #delphi
Firebird - Pulou alguma nota fiscal ?
#firebird #delphi #sql
O departamento fiscal quer saber se tem alguma nota fiscal faltando no banco de dados.
Cla...ro que não vamos ficar lendo uma lista para ver se tem alguma que pulou número.
Vamos perguntar para o banco de dados.
A mecânica não é muito trivial, já que não tem uma instrução que descubra algo desconhecido,
então vamos preparar o banco para que conheça o problema a ser resolvido.
1) criar uma procedure selecionável que monte uma sequência esperada de números possíveis:
2) agora de posse de uma lista com os números esperados, podemos perguntar para o banco
qual nota esta faltando em um intervalo (ex: entre 1000 e 2000):
select a.numero
from dim_integer( 1000 , 2000 ) a
where not exists (select notafiscal from tab_NotaFiscal b where b.numeroNotaFiscal=a.numero)
3)resultado:
Uma relação de números que não existem na tabela de nota fiscal;
Firebird - Reativando índices INACTIVE
#firebird #indices #fevereiro/2016
Restaurou o banco de dados com os índices inativados? Claro que era somente um situação de contingência,
porque você continua precisando deles. Então para REATIVAR os índices novamente, rode este código no
banco de dados:
As tabelas que apresentarem erros, o índice não será ativado, mas você continua precisando dele -
recomendo avaliar e corrigir - depois de corrigir, rode o script novamente.
SET TERM ^;
execute block returns (x varchar(255))
as
declare variable ordem integer;
declare variable stm varchar(1024);
begin
for
select y,stm, x from (
SELECT 1 y,' alter index '||trim(rdb$index_name)||' active ' stm , rdb$index_name x from rdb$indices
where rdb$index_inactive=1
and (not rdb$index_name like 'FK%')
and rdb$unique_flag=0
union
SELECT 2 y, ' alter index '||trim(rdb$index_name)||' active ' stm , rdb$index_name x from rdb$indices
where rdb$index_inactive=1
and (not rdb$index_name like 'FK%')
and rdb$unique_flag=1
union
SELECT 3 y, ' alter index '||trim(rdb$index_name)||' active ' stm , rdb$index_name x from rdb$indices
where rdb$index_inactive=1
and (rdb$index_name like 'FK%')
) k order by y
into :ordem, :stm, :x
do
begin
in autonomous transaction do
execute statement stm;
--execute statement 'commit work';
suspend;
when any do
begin
--exception;
end
end
end^
SET TERM;^
Firebird - SWEEP administrado para melhorar
velocidade do banco de dados
*SWEEP - Definição
SWEEP é um recurso que limpa registros lixo do banco de dados. Quando um registro é excluído (DELETE) ou
feito (UPDATE) o firebird mantem uma cópia antiga do registro no arquivo do banco de dados
(MEUBANCO.FDB), ainda que esta cópia não seja visualizada ao fazer o SELECT no banco de dados.
Como a remoção de dados do arquivo consome tempo de processamento considerável, o Firebird não o faz
constantemente, mas sim, de períodos em períodos. Se a instalação padrão do MEUBANCO.FDB for de
20.000 registro, ou seja, quando atinge este número de registro o banco executa automaticamente um
SWEEP do banco de dados (limpa o lixo).
Como ele não escolhe a hora mais adequada para executar o SWEEP, muitas vezes o banco o executa
exatamente quando o usuário mais esta precisando acessar o banco. Quando isto ocorre, o banco aumenta o
tempo de resposta do perceptível ao usuário.
Em instalações de uso intenso, um possibilidade é desabilitar este processo automático e passar a faze-lo em
momento programados. Ex: quando liga a máquina, ou ainda, programada para executar fora do expediente.
TODO: (em estudo) tentar reduzir o numero de registros (algo como 1000) será que muda o
comportamento?
Ao desabilitar o SWEEP o banco deixará de fazer limpeza dos registros inválidos, razão que é preciso montar
um processo de execute o SWEEP manualmente (obrigatório). Se por o SWEEP deixar de ser feito por um
período que acumule muitos registros inválidos, será afetado o desempenho do banco de dados, até que o
SWEEP seja executa; Executar o SWEEP quando reinicia o banco de dados, pode ser uma opção bastante
interessante.
Uma tabela com um único índice já monta uma estatística que indica qual a distribuição
espacial dos dados organizados pelo índice. Se a tabela possui vários índices o firebird utiliza
as estatísticas para escolher os melhores índices para responder a solicitação do usuário.
Sempre que é feito INSERT, UPDATE em uma coluna que pertence a um índice, o índice é
atualizado, mas sua estatítica não segue a mesma mecânica (ocorre uma espécie de
fragmentação do índice).
Se o firebird decide com base na estatítica do índice, não é difícil imaginar que com a
fragmentação o que deveria ser um benefício passa a ser um problema.
Então o firebird recomenda que depois de algum tempo – as estatísticas do banco de dados
sejam refeitas para refletir com melhor precisão a situação do índice.
Para facilitar este trabalho, segue uma procedure que força a atualização dos índice de todas
as suas tabelas.
SETTERM^;
CREATEORALTERPROCEDUREREINDEX
as
declarevariablesqlvarchar(255);
begin
for
selectrdb$index_namefromrdb$indices
into:sql
do
begin
sql='SETSTATISTICSINDEX'||sql;
executestatement:sql;
end
end^
SETTERM;^
GRANTEXECUTEONPROCEDUREREINDEXTOSYSDBA;
Firebird – Calculando Idade
#brincandoComFirebird#firebird
Precisacalcularaidadedeumapessoaoucalcularonúmerodesemanasentreduasdatas,entãoolhas
estasprocedures:
IDADE_SEMANAS(data_inicio,data_fim)->retornaumastring:AnosSemanasDias
IDADE_MESES(data_inicio,data_fim)->retornaumastring:AnosMesesDias
Setemumfuncionárioquenasceuem:01/01/1990equersaberaidadedeleHOJE
selectresultfromidade_meses('01/01/1990','today')
Fonte:https://github.com/amarildolacerda/firebird/blob/master/firebird_procedures_idade.sql