0% acharam este documento útil (0 voto)
54 visualizações14 páginas

Programando em PSQL

O documento descreve como criar stored procedures e triggers no Firebird usando PSQL, incluindo a estrutura básica com cabeçalho e corpo, e os principais comandos como DECLARE, IF/THEN, EXECUTE PROCEDURE e EXCEPTION.

Enviado por

Aline Paliari
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
54 visualizações14 páginas

Programando em PSQL

O documento descreve como criar stored procedures e triggers no Firebird usando PSQL, incluindo a estrutura básica com cabeçalho e corpo, e os principais comandos como DECLARE, IF/THEN, EXECUTE PROCEDURE e EXCEPTION.

Enviado por

Aline Paliari
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd

1

Programando em PSQL
Criação de stored procedures e triggers
POR CARLOS H. CANTU

Um dos recursos mais importantes de um sistema gerenciador de banco de dados é sem dúvida
nenhuma sua capacidade de executar procedimentos internos e de disparar ações baseadas em eventos
ocorridos em uma tabela – são as stored procedures e os triggers. Como não poderia deixar de ser,
o Firebird (FB) suporta ambos os recursos, fornecendo comandos especiais para serem utilizados
nesses casos, que fazem parte do PSQL (Procedural SQL).
Indo além, o FB permite que você use stored procedures como fonte de dados, ou seja, como se
fossem tabelas do banco. São as select stored procedures (que chamaremos de stored procedures
"selecionáveis"). Além disso, a possibilidade de utilizar UDFs (funções definidas pelo usuário)
aumenta ainda mais o poder e a funcionalidade das stored procedures e triggers.
Estrutura e criação de uma stored procedure

O comando utilizado para a criação de uma stored procedure (SP) é o CREATE PROCEDURE. As
SPs têm duas partes: cabeçalho e corpo. No cabeçalho, temos a definição do nome da SP, parâmetros
de entrada (caso existam) e parâmetros de saída (caso a SP retorne valores). O corpo contém
declarações de variáveis e comandos a serem executados. A estrutura é definida da seguinte maneira:
CREATE PROCEDURE nome_procedure (
PARAMETRO1 TIPO, /* lista de parâmetros de entrada */
PARAMETRO2 TIPO)
RETURNS (
RESULTADO1 TIPO, /* lista de parâmetros de saída */
RESULTADO2 TIPO)
AS
DECLARE VARIABLE VAR1 TIPO;
DECLARE VARIABLE VAR2 TIPO;
<... Outras declarações de variáveis ...>
BEGIN
<... Corpo da stored procedure (comandos) ...>
END
Nota: A variação CREATE OR ALTER pode facilitar a manutenção de um banco de dados pois
permite criar ou alterar uma procedure/trigger quando já existir, sem a necessidade de ter dois
comandos separados (CREATE e ALTER).
Estrutura e criação de um trigger

Semelhante a uma stored procedure, um trigger também possui um cabeçalho e um corpo. No


cabeçalho definimos a tabela à qual o trigger está associado e o tipo de evento que irá dispará-lo (veja
os eventos suportados na Tabela 1). O corpo do trigger contém a declaração de variáveis e comandos
a serem executados. Veja a estrutura:
CREATE TRIGGER nome_trigger FOR nome_tabela
[ACTIVE | INACTIVE]
{BEFORE | AFTER} evento POSITION posição
AS
DECLARE VARIABLE VAR1 TIPO;
DECLARE VARIABLE VAR2 TIPO;
<... Outras declarações de variáveis ...>
BEGIN
<... Corpo do trigger (comandos)...>
END
2

Até a versão 1.0, um trigger só poderia estar associado a um único evento (after/before - insert,
update, delete). A partir do FB 1.5, podemos associar um único trigger a vários eventos! Para
identificar o tipo de ação que disparou o trigger foram criadas 3 variáveis: Inserting, Updating e
Deleting, que podem ser checadas no código do trigger e, através de Ifs, condicionar os comandos a
serem executados, como mostrado no código abaixo:
CREATE TRIGGER TABELA_BIU FOR TABELA
ACTIVE BEFORE INSERT OR UPDATE POSITION 0
AS
begin
if (inserting) then
begin
/* Executa alguma operação quando for inserção */
end
else
if (updating) then
begin
/* Executa alguma operação quando for edição */
end
end

Em triggers (mas não em SPs) é possível utilizar as variáveis de contexto NEW e OLD, que
representam respectivamente os valores atuais e anteriores dos campos da tabela em questão. Na
Tabela 1, temos as situações em que cada variável de contexto pode ser utilizada. O FB permite ter
vários triggers definidos para um mesmo evento. Eles serão executados na sequência definida pelo
valor da cláusula POSITION. Variáveis OLD são sempre ReadOnly.

Tabela 1. Eventos e variáveis de contexto


Evento Momento em que é disparado Variáveis de contexto
Before Insert Antes de uma inserção New
After Insert Após uma inserção New*
Before Update Antes de uma atualização New e Old
After Update Após uma atualização New* e Old
Before Delete Antes de uma exclusão Old
After Delete Após uma exclusão Old
* Alterar os valores das variáveis NEW nesses eventos não causará qualquer efeito prático nas
versões antigas do FB, mas resultará em erro nas versões mais novas.
A linguagem de SPs e triggers

O Firebird suporta uma linguagem própria para a construção de triggers e stored procedures,
chamada PSQL. A seguir, mostro um resumo da sintaxe e da forma de uso dos principais comandos
da linguagem de SPs e triggers do FB disponíveis no Firebird 1.5. Cada nova versão do Firebird
introduz novos comandos, portanto, sugiro uma leitura do Guia de Referência de Linguagem, em
[Link]
[Link]#fblangref40-psql para conhecer todos os comando disponíveis atualmente.

Variáveis

As variáveis têm papel fundamental nas stored procedures e triggers. Pode-se definir e utilizar
variáveis de qualquer tipo de dado suportado no FB, exceto os tipos ARRAY (e em versões antigas,
também os BLOBs).
Variáveis no FB são sempre locais, ou seja, são visíveis somente dentro da SP ou trigger em que
foram criadas.
Valores são atribuídos a variáveis usando o operador de atribuição "=":
Variável = Valor;
3

Aqui, Valor pode conter um valor imediato (1, 2, 'A' etc.), o resultado de uma expressão (como
x/y+2), ou até mesmo chamadas a UDFs. Note o “;” obrigatório no final da sentença, semelhante ao
Pascal.

A partir da versão 2.1 do Firebird, é permitido declarar o tipo de uma variável baseado no tipo de
um domínio ou coluna de uma tabela através do type of ou do type of column, por exemplo:
declare variable a type of MEUDOMINIO;
declare variable a type of column [Link];

Importante: referências a variáveis e parâmetros devem ser precedidas por um “:” (dois-pontos)
sempre que puderem ser confundidas com campos de uma tabela.

Comentários

Comentários em SPs e triggers são delimitados por /* e */. Por exemplo:


IF ([Link] = ‘A’) THEN /* Verificar se foi aprovado */
Desde o Firebird 1.5 é possível definir comentários de uma linha através do uso de “--", ex:
x = 10; -- inicializa a variável x com valor 10

Blocos

Um bloco é uma sequência de comandos e instruções delimitados por BEGIN e END. Como no
Pascal, o bloco pode conter outros blocos.
No Firebird, o bloco principal de uma stored procedure ou trigger deve terminar com um END
seguido do terminador. O terminador é definido com o comando SET TERM. No exemplo da
Listagem 1, o terminador foi definido como sendo o acento circunflexo (^).

EXCEPTION

O comando EXCEPTION gera uma exceção (erro) previamente definida pelo usuário através do
comando CREATE EXCEPTION.
A sintaxe básica é a seguinte:
EXCEPTION nome ‘mensagem de erro’;

A exceção a ser gerada é definida pelo nome. Quando o comando EXCEPTION é executado, ele
termina a execução da SP ou do trigger e desfaz todas as operações executadas anteriormente por
eles.
O tratamento de exceções pelo usuário pode ser feito usando comandos WHEN...DO (mais adiante),
por exemplo:
CREATE EXCEPTION estoque_negativo
‘O estoque deve ser maior que zero’;
...
IF ([Link] < 0) THEN
EXCEPTION estoque_negativo;
...
IF ([Link] < 0) THEN –- No Firebird 1.5
EXCEPTION estoque_negativo ’O estoque do produto ‘ || [Link] || ‘
não pode ser negativo!’;
...
4
WHEN ANY DO
BEGIN
INSERT INTO ERROR_LOG (...) VALUES (SQLCODE, ...);
EXCEPTION; -- Re-raise de exceção no Firebird 1.5
END

Nota: A partir do Firebird 1.5 é possível gerar exceções dinâmicas, definindo a mensagem exibida
para o usuário no momento em que a exceção é gerada. É permitido também que a exceção seja re-
disparada (re-raise) dentro de um bloco de tratamento de exceções.

EXECUTE PROCEDURE

O comando EXECUTE PROCEDURE permite chamar stored procedures de dentro de triggers ou


de SPs. As chamadas podem ser recursivas (o limite é de mil iterações). Caso a procedure sendo
chamada tenha parâmetros de entrada ou de saída, estes devem ser informados obrigatoriamente.
A sintaxe para esse comando é a seguinte (as partes entre colchetes são opcionais):
EXECUTE PROCEDURE nome [parametros_entrada] [retuning_values
parametros_saida];

Veja um exemplo de chamada:


DECLARE VARIABLE Produto CHAR(4);
DECLARE VARIABLE CustoMedio NUMERIC(9,2);
...
produto = ‘0001’;
EXECUTE PROCEDURE CalculaCustoMedio:Produto
RETURNING_Values:CustoMedio;

IF...THEN...ELSE

A condição em um IF deve sempre estar entre parênteses. Como de praxe, o ELSE é executado
quando a condição avaliada for falsa. É permitido utilizar blocos de código dentro de IFs (veja o
exemplo na Listagem 1) e IFs aninhados. Exemplo:
if (:status = 1) then
:desc_status = 'OK';
else
:desc_status = 'FALHOU';

Observe que diferentemente do Pascal, onde o “;” pode determinar o final de um if, no FB isso não
acontece.
POST_EVENT

POST_EVENT faz com que seja enviado um evento do servidor para todas as aplicações que estejam
registradas para receber esse evento. A sintaxe é a seguinte:
POST_EVENT ‘nome_do_evento’ | campo_de_uma_tabela;

Após a aplicação cliente receber a notificação de que o evento foi disparado, ela pode disparar ações
como realizar uma atualização nos dados, replicar alguma informação em outro banco, etc.
SELECT...INTO

Você também pode executar SELECTs que retornem no máximo uma linha a partir de SPs ou
triggers, e armazenar o resultado em variáveis/parâmetros. Na Listagem 1, um SELECT desse tipo é
5

usado para recuperar os valores dos débitos e créditos dos lançamentos anteriores para posteriormente
calcular o saldo inicial.
Nota: no Firebird, podemos garantir que um select não retorne mais que uma linha usando a cláusula
“FIRST 1” do SELECT.

EXIT e SUSPEND

Esses dois comandos funcionam de maneiras diferentes de acordo com o tipo de stored procedure.
Veja as diferenças na Tabela 2, lembrando que o comando EXIT pode ser utilizado em triggers
somente a partir do Firebird 1.5, e que o SUSPEND só pode ser utilizado em Stored Procedures –
tornando-a uma stored procedure selecionável.

Tabela 2. Funcionamento de EXIT e SUSPEND


Tipo de SP SUSPEND EXIT END final
SP selecionável Suspende a execução da procedure Vai para o END final Retorna o controle para a aplicação
até que o próximo FETCH seja e altera SQLCODE para 100,
recebido e retorna os valores de significando que não há mais
saída. registros a serem retornados.
SP executável Vai para o END final (não é Vai para o END final Retorna os valores e devolve o
recomendado seu uso nesse tipo de controle para a aplicação
SP)

WHEN...DO

WHEN..DO é responsável pelo tratamento de erros em SPs e triggers. Quando um erro é gerado, as
ações executadas dentro do bloco BEGIN-END atual são desfeitas e um WHEN é procurado. Caso
não seja encontrado um WHEN dentro do bloco atual, o servidor volta um nível na cadeia de blocos
BEGIN-END à procura desse comando e assim por diante. Se um WHEN for encontrado, a ação (ou
bloco de ações) definida por ele é executada e o fluxo retorna para o bloco BEGIN-END onde ao qual
o WHEN pertence. Caso nenhum WHEN seja encontrado, todas as ações executadas pela SP são
desfeitas.
Os erros que podem ser capturados pelo WHEN são EXCEPTIONs, SQLERRORs (erros de SQL),
GDSERRORs (erros do FB) e ANY (qualquer um dos anteriores). Veja um exemplo do uso do
WHEN na Listagem 2.

SQLCODE e GDSCODE
A partir do Firebird 1.5 as variáveis contextuais SQLCODE e GDSCODE estão disponíveis no PSQL e
fornecem um código do tipo inteiro representando o erro relacionado à exceção ativa dentro de um bloco de
tratamento de exceções. Fora do bloco, ambas retornam zero. Com o uso de WHEN ANY ou WHEN
SQLCODE, a variável GDSCODE sempre retornará zero. Com WHEN GDSCODE a variável SQLCODE será
sempre zero. Se a exceção gerada é uma exceção definida pelo usuário, ambas as variáveis serão zero.

WHILE…DO

O WHILE é o único comando de loop genérico disponível para SPs e triggers, mas é suficiente para
executar praticamente todo tipo de loop necessário. A Listagem 2 contém um exemplo de uso do
WHILE.

Listagem 2. Stored procedure para elevar um número a uma potência


CREATE PROCEDURE POWER (
NUMERO NUMERIC(9,2),
EXPOENTE INTEGER)
RETURNS (
RESULTADO NUMERIC(15,2))
AS
DECLARE VARIABLE CONTADOR INTEGER;
6
BEGIN
resultado = -1; /* Em caso de erro, retorna -1 */
/* A rotina só funciona para expoentes positivos */
IF (:expoente < 0) THEN
EXCEPTION exp_negativo;
contador = 0;
resultado = 1;
WHILE (:contador <:expoente) DO
BEGIN
resultado = resultado * numero;
contador = contador + 1;
/* Caso ocorra algum erro (como um overflow, retorna -1) */
WHEN ANY DO
BEGIN
resultado = -1;
EXIT;
END
END
END
^

FOR SELECT...DO

O FOR SELECT é um dos comandos mais importantes em SPs e triggers, permitindo abrir um cursor
unidirecional e percorrer os dados retornados pelo SELECT. Nesse tipo de SELECT, é exigido o uso
da cláusula INTO para receber os dados dos campos especificados. Antigamente, essa é a única forma
de se trabalhar com cursores de dentro de SPs e triggers. Versões mais recentes do Firebird
introduziram comandos próprios para a criação e uso de cursores explicitamente no PSQL.
A Listagem 1 usa dois loops FOR SELECT para percorrer as tabelas de débito e crédito retornando
os registros desejados e incrementando o valor do saldo final. Esses loops alimentam algumas das
variáveis de saída da procedure (que serão retornadas quando for executada).

Comandos CASE, COALESCE e NULLIF

O comando CASE permite retornar um valor baseado nas condições definidas. A introdução desse
comando no Firebird 1.5 permitiu a implementação de duas outras funções: COALESCE e NULLIF.
COALESCE retorna o primeiro valor não nulo de uma sequência de valores passados como
parâmetros. NULLIF recebe dois parâmetros de entrada e retorna NULL caso os valores dos 2
parâmetros sejam iguais; caso os valores sejam diferentes, o valor do primeiro parâmetro é retornado.
Internamente COALESCE e NULLIF são convertidos em um CASE.
A listagem 3 mostra a utilização do CASE em um select para substituir o código utilizado no campo
método_pagto (que representa o tipo de pagamento realizado em uma venda) pela sua respectiva
descrição.
Na listagem 4 usamos a função coalesce para obter, em ordem de preferência, o nome fantasia de
uma empresa, ou a razão social ou, caso nenhum desses esteja disponível, a string ‘Sem Nome’. A
função NULLIF utilizada no update faz com que o campo estoque da tabela produtos fique nulo caso
o valor atual dele seja 0.
SELECT v.Nota_Fiscal, [Link],
CASE v.metodo_pagto
WHEN 'V' THEN 'A vista'
WHEN 'P' THEN 'Parcelado'
WHEN 'C' THEN 'Cartão de crédito'
WHEN 'D' THEN 'Cheque Pré-datado'
ELSE 'Tipo desconhecido ''' || v.metodo_pagto || ''''
END
FROM Vendas v;
Listagem 3. Uso do CASE em select
7
select coalesce (c.nome_fantasia, c.razao_social, 'Sem Nome')
from clientes c;
update produtos set estoque = nullif(estoque,0);
Listagem 4. Exemplos de uso do coalesce e nullif.

[FOR] Execute Statement

Um recurso poderoso que permite montar e executar dinamicamente comandos SQL dentro de
Stored Procedures e Triggers através dos comandos Execute Statement e sua variação For Execute
Statement. O primeiro deve ser utilizado para a execução de comandos que não retornam resultados
(ex: updates, inserts, etc.) ou retornam no máximo 1 linha de dados. O segundo é específico para
comandos que retornam várias linhas de resultado (a maioria dos selects).
Como a engine do banco de dados não pode validar previamente o comando SQL que será montado
em tempo de execução, é necessário tomar cuidado na sua construção pois um comando inválido
provocará uma exceção/erro ao ser executado. Devemos lembrar também que não é possível utilizar
parâmetros nos comandos montados dinamicamente.
O código da listagem 5 mostra um exemplo simples de utilização desse recurso. A procedure SOMA
recebe o nome de um campo e de uma tabela e realiza uma operação de somatória (SUM) nesse
campo, retornando o valor obtido. A procedure selecionável GET_ULT_COMPRA recebe como
parâmetro o nome de uma tabela e uma sequência de códigos de clientes separados por vírgula e
devolve o nome de cada cliente e a data da sua última compra.
CREATE PROCEDURE Soma (Campo VARCHAR(30), Tabela VARCHAR(50))
RETURNS (Resultado NUMERIC (18,2))
AS
BEGIN
EXECUTE STATEMENT ‘SELECT SUM(‘ || Campo || ‘) FROM ’ || Tabela INTO
:Resultado;
END
CREATE PROCEDURE GET_ULT_COMPRA (
VARTABELA VARCHAR(32),
VARCODIGOS VARCHAR(512))
AS
DECLARE VARIABLE VARNOME VARCHAR(50);
DECLARE VARIABLE VARULTCOMPRA DATE;
begin
for execute statement
'select nome, ultcompra from ' || :VarTabela ||
'where codigo in (' || :VarCodigos || ')'
into :VarNome, :VarUltCompra
do
begin
/* Processa as informações */
suspend;
end
end
Listagem 5. Utilização do Execute Statement e For Execute Statement

LEAVE/BREAK/CONTINUE

LEAVE e BREAK permitem interromper o processamento de loops gerados pelos comandos


WHILE, FOR SELECT e FOR EXECUTE, desviando o processamento para o próximo comando
logo após o END final do loop. O padrão SQL-99 aconselha a utilização do LEAVE ao invés do
BREAK, mas ambos têm a mesma finalidade.
Os comandos Leave, Break e Exit podem ser usados tanto em triggers como em stored procedures.
LEAVE pode ser acompanhado de um rótulo, permitindo interromper a execução do loop atual e
também de um loop externo devidamente rotulado.
CONTINUE permite desviar o fluxo de execução para a próxima iteração do loop e, quando
acompanhado de um rótulo, para a próxima iteração de um loop externo.
8

A listagem 6 mostra um exemplo de utilização do leave para interromper um loop baseado em uma
condição.
conta = 1;
while (:conta < 100) do
begin
-- Realiza algum processamento, como cálculos com as variáveis x e y
if (:x = :y) then leave; -- Se o valor da variável x for igual ao de y,
sai do loop.
conta = conta + 1;
end
Listagem 6. Utilização do Leave para interromper o loop.

Stored procedures selecionáveis

O suporte a stored procedures selecionáveis é um dos recursos mais poderosos do FB. Com ele
podemos utilizar uma stored procedure como fonte de dados para SELECTs, como se a SP fosse uma
tabela do banco de dados.
O efeito é o mesmo de ter uma “tabela virtual” montada com informações vindas de uma ou mais
tabelas do banco de dados, sendo que essas informações podem ser transformadas, calculadas etc.
para montar a tabela.
Para demonstrar o uso desse tipo de stored procedure, criei um exemplo simples de um fluxo de
caixa, onde temos uma tabela para “contas a receber” e outra para “contas a pagar”. A intenção é que
possamos obter dados para o fluxo de caixa em determinado período, ordenado por data, listando os
lançamentos e seus tipos (débitos e créditos), incluindo o saldo inicial (antes do período informado)
e o saldo final. O código da SP pode ser visto na Listagem 7. Na seção seguinte, explico cada parte
do código.
CREATE PROCEDURE CAIXA (
INICIO DATE,
FIM DATE)
RETURNS (
DATA DATE,
HISTORICO VARCHAR(30),
VALOR NUMERIC(9,2),
TIPO CHAR(1))
AS
DECLARE VARIABLE SALDO_ANT_CREDITO NUMERIC(9,2);
DECLARE VARIABLE SALDO_ANT_DEBITO NUMERIC(9,2);
DECLARE VARIABLE SALDO_FINAL NUMERIC(9,2);
begin
/* Recupera a soma dos débitos anteriores ao período informado */
SELECT sum([Link])
FROM apagar ap
WHERE ([Link] <:inicio)
INTO:saldo_ant_debito;
/* Recupera a soma dos créditos anteriores ao período informado */
SELECT sum([Link])
FROM areceber ar
WHERE ([Link] <:inicio)
INTO:saldo_ant_credito;
/* Trata nulos */
IF (:saldo_ant_credito IS NULL) THEN
saldo_ant_credito = 0.00;
IF (:saldo_ant_debito IS NULL) THEN
saldo_ant_debito = 0.00;
data =:inicio - 1;
historico = 'SALDO ANTERIOR';
valor = (saldo_ant_credito - saldo_ant_debito);
/* Inicializando a variavel que armazenara o saldo final
com o valor do saldo anterior */
saldo_final = valor;
9
/* Trata valores negativos */
IF (:valor < 0) THEN
BEGIN
tipo = 'D';
valor = valor * -1;
END
ELSE
BEGIN
tipo = 'C';
END
SUSPEND;
/* Retorna a primeira linha com o saldo anterior */
saldo_final = valor;
/* Seleciona os registros dentro do período informado
buscando na tabela de contas a pagar */
tipo = 'D'; /* Estamos recuperando débitos */
FOR SELECT [Link], [Link], [Link]
FROM apagar ap
WHERE ([Link] BETWEEN:inicio and:fim)
INTO:data,:historico,:valor
DO
BEGIN
saldo_final = saldo_final - valor;
/* O suspend a seguir retorna uma linha contendo os campos
data, histórico, tipo e valor */
SUSPEND;
END
tipo = 'C'; /* Estamos recuperando créditos */
/* Seleciona os registros dentro do período informado
buscando na tabela de contas a receber */
FOR SELECT [Link], [Link], [Link]
FROM areceber ar
WHERE ([Link] BETWEEN:inicio and:fim)
INTO:data,:historico,:valor
DO
BEGIN
saldo_final = saldo_final + valor;
/* O suspend a seguir retorna uma linha contendo os campos
data, historico, tipo e valor */
SUSPEND;
END
/* Montamos a última linha, com os valores do saldo final */
data =:fim;
historico = 'SALDO FINAL';
tipo = 'C';
valor = saldo_final;
SUSPEND;
END
^
Listagem 7. Stored procedure para um fluxo de caixa

Análise da stored procedure

A stored procedure CAIXA tem como parâmetros de entrada as datas de início e final do período
que desejamos visualizar e, como parâmetros de saída, os “campos” da nossa “tabela virtual” (data,
histórico, valor e tipo). Como vimos anteriormente, essas informações são definidas no cabeçalho da
SP:
CREATE PROCEDURE CAIXA (
INICIO DATE,
FIM DATE)
RETURNS (
DATA DATE,
HISTORICO VARCHAR(30),
10
VALOR NUMERIC(9,2),
TIPO CHAR(1))

Em seguida, definimos duas variáveis numéricas que armazenarão os totais de débitos e créditos
anteriores ao período informado, para que possamos então calcular o saldo anterior, além de uma
terceira variável (saldo_final), para armazenar o saldo final do período:
DECLARE VARIABLE SALDO_ANT_CREDITO NUMERIC(9,2);
DECLARE VARIABLE SALDO_ANT_DEBITO NUMERIC(9,2);
DECLARE VARIABLE SALDO_FINAL NUMERIC(9,2);

O próximo passo é obter os valores para o cálculo do saldo anterior. Usamos dois SELECTs com a
função SUM para totalizar os lançamentos (débitos e créditos) anteriores à data inicial:
SELECT SUM([Link])
FROM apagar ap
WHERE ([Link] <:inicio) INTO:saldo_ant_debito;
SELECT SUM([Link])
FROM areceber ar
WHERE ([Link] <:inicio) INTO:saldo_ant_credito;

A seguir devemos verificar se as variáveis são nulas (isso ocorrerá se não houver nenhum
lançamento anterior ao dia inicial). Como sabemos, NULL dentro de qualquer cálculo matemático
faz com que o resultado da operação também seja NULL, o que pode fazer com que o saldo anterior
fique incorreto. Sendo assim, checamos se alguma das variáveis possui valor NULL. Caso positivo,
atribuímos a ela o valor zero:
IF (:saldo_ant_credito IS NULL) THEN saldo_ant_credito = 0.00;
IF (:saldo_ant_debito IS NULL) THEN saldo_ant_debito = 0.00;
Nota: A listagem 8 mostra uma versão da procedure adaptada para o Firebird 1.5 (ou superior) onde
substituímos a checagem de nulls pelo comando COALESCE.

O próximo passo é montar a primeira linha do resultado da nossa SP, contendo os dados do saldo
anterior. Definimos o parâmetro data como o dia anterior à data inicial informada, depois
descrevemos o histórico como sendo “SALDO ANTERIOR” e calculamos o saldo com base nas
variáveis anteriormente calculadas.
Para não mostrar valores negativos no resultado, verificamos se o saldo anterior é negativo; se for,
o convertemos para um valor positivo. Nesse caso o lançamento é feito como débito, caso contrário,
lançamos como crédito.
data =:inicio - 1;
historico = 'SALDO ANTERIOR';
valor = (saldo_ant_credito - saldo_ant_debito);
saldo_final = valor;
IF (:valor < 0) THEN
BEGIN
tipo = 'D';
valor = valor * -1;
END
ELSE
BEGIN
tipo = 'C';
END
SUSPEND;

A linha contendo o SUSPEND fará com que a SP devolva o primeiro registro com os valores
atribuídos aos parâmetros de saída. O processamento da stored procedure continua a partir dali, assim
que ela recebe uma requisição de fetch da aplicação cliente.
Seguindo a sequência, devemos inicializar a variável saldo_final com o saldo anterior obtido:
11

saldo_final = valor;

O próximo passo é recuperar os lançamentos no período informado. Primeiramente, vamos percorrer


a tabela de contas a pagar. Ela fornece os lançamentos de débito, portanto devemos alterar o parâmetro
tipo para “D” (débito). Feito isso, usamos um FOR SELECT para varrer todos os registros dentro do
período especificado, armazenando os valores dos campos data, histórico e valor nos respectivos
parâmetros de saída que formarão uma linha/registro da “tabela virtual”. Note que dentro do loop
atualizamos o saldo final com base no campo valor do registro que está sendo percorrido.
Obtidos todos os dados necessários, precisamos pedir à SP que retorne um novo registro para a
aplicação, chamando mais uma vez o SUSPEND. Note que para cada linha percorrida pelo FOR
SELECT, teremos um SUSPEND retornando uma linha do resultado. Depois repetimos o processo
para a tabela de contas a receber, alterando tipo para “C” (crédito) e somando os valores ao saldo
final, em vez de subtraí-los. A sequência pode ser observada no seguinte código:
tipo = 'D';
FOR SELECT [Link], [Link], [Link]
FROM apagar ap
WHERE ([Link] BETWEEN:inicio AND:fim)
INTO:data,:historico,:valor
DO
BEGIN
saldo_final = saldo_final - valor;
SUSPEND;
END
tipo = 'C';
FOR SELECT [Link], [Link], [Link]
FROM areceber ar
WHERE ([Link] BETWEEN:inicio AND:fim)
INTO:data,:historico,:valor
DO
BEGIN
saldo_final = saldo_final + valor;
SUSPEND;
END

Agora só nos resta retornar um registro contendo o saldo final. Montamos o registro com bases nos
dados já coletados, como pode ser visto a seguir:
data =:fim;
historico = 'SALDO FINAL';
tipo = 'C';
valor = saldo_final;
SUSPEND;
Chamando a procedure

Uma stored procedure selecionável deve ser chamada por um SELECT, como se fosse uma tabela
comum do banco de dados:
SELECT * FROM CAIXA ('15.1.2003','1.3.2003')
ORDER BY data

Note que as datas do período estão sendo passadas usando “.” ao invés da tradicional “/”. O ponto
diz ao FB que a data está no formato dia/mês/ano. Se fosse usado “/”, deveria usar o formato
americano mês/dia/ano.
Na Figura 1 podemos ver um exemplo de resultado desse comando.
12

Figura 1. Resultado de SELECT sobre a procedure selecionável CAIXA

Exemplo com triggers

Para exemplificar o uso de um trigger, veremos como simular um campo “autoincremento” no FB.
Para fazer isso, devemos primeiramente criar um generator, que nada mais é do que um contador
interno de 64 bits dentro do banco. Para isso usamos o comando CREATE GENERATOR, como em:
CREATE GENERATOR GEN_APAGAR_ID;

Agora que temos o generator criado, definiremos um trigger no evento Before Insert (ou seja, antes
que uma inserção seja efetivada), que irá incrementar o nosso contador e atribuir o valor dele ao
campo "autoincremento". Verifique o código:
CREATE TRIGGER APAGAR_BI_BI FOR APAGAR
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF ([Link] IS NULL) THEN
[Link] = GEN_ID(GEN_APAGAR_ID,1);
END
^

No exemplo, ID é o campo autoincremento e só recebe um valor caso já não tenha sido inicializado.
Isso permite uma flexibilização que não ocorre no tipo autoincremento real, encontrado em diversos
SGBDs. Dessa maneira, podemos forçar um registro a ter um determinado ID simplesmente
atribuindo um valor a ele antes de ser efetivamente gravado. O trigger irá detectar que o campo não
está nulo e, portanto, não incrementará o generator.
Nota: GEN_ID é uma função interna do Firebird que incrementa o valor de um generator retornando
um novo valor.

CREATE PROCEDURE CAIXA (


INICIO DATE,
FIM DATE)
RETURNS (
DATA DATE,
HISTORICO VARCHAR(30),
VALOR NUMERIC(9,2),
TIPO CHAR(1))
AS
DECLARE VARIABLE SALDO_ANT_CREDITO NUMERIC(9,2);
DECLARE VARIABLE SALDO_ANT_DEBITO NUMERIC(9,2);
DECLARE VARIABLE SALDO_FINAL NUMERIC(9,2);
begin

select coalesce(sum([Link]),0)
from apagar ap
where ([Link] < :inicio)
13

into :saldo_ant_debito;

select coalesce(sum([Link]),0)
from areceber ar
where ([Link] < :inicio)
into :saldo_ant_credito;

data = :inicio - 1;
historico = 'SALDO ANTERIOR';
valor = (saldo_ant_credito - saldo_ant_debito);

saldo_final = valor;

if (:valor < 0) then


begin
tipo = 'D';
valor = valor * -1;
end
else
begin
tipo = 'C';
end

suspend; -- Retornando à primeira linha (registro) com o saldo anterior

tipo = 'D'; -- Estamos recuperando débitos

for select [Link], [Link], [Link]


from apagar ap
where ([Link] between :inicio and :fim)
into :data, :historico, :valor
do
begin
saldo_final = saldo_final - valor; /*Subtraimos o valor do lançamento
do saldo final */
suspend;
end

tipo = 'C'; /* Estamos recuperando créditos */

for select [Link], [Link], [Link]


from areceber ar
where ([Link] between :inicio and :fim)
into :data, :historico, :valor
do
begin
saldo_final = saldo_final + valor;
suspend;
end

data = :fim;
historico = 'SALDO FINAL';
tipo = 'C';
valor = saldo_final;

suspend;
end
Listagem 8. Código da procedure alterado para Firebird 1.5 utilizando o comando COALESCE e comentário de uma linha
14

Esse artigo foi extraído do livro “Firebird Essencial”, de minha autoria, e revisado em 17-julho-2021. Autor: Carlos Henrique Cantu

Common questions

Com tecnologia de IA

A Firebird selectable stored procedure is structured to return a result set similar to an SQL table, defined by its output parameters. However, unlike a static table, a selectable SP dynamically generates and returns data as it processes logic within its defined BEGIN-END blocks, often using loops and conditionals. This allows for complex computations and conditional data fetching that goes beyond static table functionality, offering flexibility in data manipulation and presentation .

In Firebird, an autoincrement-like feature is implemented using a generator and a BEFORE INSERT trigger. The generator, created with CREATE GENERATOR, acts as a counter. The trigger increments this generator using GEN_ID function and assigns the value to a field, simulating an autoincrement. This setup is flexible as it allows for manual setting of ID values when needed. However, compared to native autoincrement features in other DBMSs, it lacks built-in constraints and automatic progression, which can lead to complexities and potential errors if not carefully managed .

The POST_EVENT command in Firebird sends a server-side event notification to all client applications registered to receive such events. When the event is posted, clients can execute actions such as updating data or propagating information to another database. It's an effective way of notifying applications in real-time about certain changes in the database .

In Firebird, the EXCEPTION command is used to generate user-defined exceptions. When executed, it stops the execution of a stored procedure (SP) or trigger and undoes all operations that were performed by them up to that point. Exceptions can be dynamically generated with specific messages for users from Firebird 1.5 onwards, which also allows for exceptions to be re-raised within an exception handling block .

The WHEN...DO clause in Firebird is used for error handling. It captures specific error types like EXCEPTIONs, SQLERRORs, or GDSERRORs within SPs and triggers. When an error is encountered, operations in the current BEGIN-END block are undone, and the system searches for an applicable WHEN clause to execute corresponding error-handling code. This mechanism allows the defining of custom recovery or notification actions when certain errors occur, affecting the flow by redirecting the process according to error-handling logic .

To implement a WHILE loop in a Firebird stored procedure, initialize the loop counter usually with a DECLARE VARIABLE statement. Construct the loop with a WHILE condition, ensuring it will eventually resolve to false to prevent infinite loops. Within the loop, include necessary computations or database operations. Embed WHEN...DO blocks to handle potential runtime errors, ensuring robust error management and maintaining procedure integrity if an exception occurs .

In Firebird 1.5, the COALESCE function returns the first non-null value from its list of arguments, which is useful for handling potential null values in expressions. NULLIF, on the other hand, returns NULL if its two arguments are equal, which can be used for comparisons where a specific output (like NULL) is needed when conditions are met. Internally, both functions are implemented with the CASE command and help manage null values effectively in SQL operations .

In Firebird 1.5, dynamic exception messages allow developers to specify error messages at runtime, providing clearer and more context-specific errors to users. The ability to re-raise exceptions within a WHEN...DO block enables further processing or logging after an initial exception handling, enhancing the flexibility and depth of error management strategies. These features enrich error handling by allowing more informative and controlled responses to exceptional situations .

The SUSPEND command in a selectable stored procedure pauses execution of the procedure and returns the current output values to the calling application. It turns the stored procedure into a source of data similar to a table from which records can be fetched in sequence. Each SUSPEND call provides a single record result based on the defined output parameters until no more data is available .

The EXIT command in Firebird triggers terminates execution and returns control to the application, altering SQLCODE to 100 if no more data is available. In contrast, SUSPEND is used only in selectable stored procedures, returning current record values and pausing the procedure until the next fetch request. EXIT is typically used in non-selectable SPs to signal completion, while SUSPEND facilitates incremental data retrieval in selectable SPs .

Você também pode gostar