Administração de Banco de Dados (PARTE XXVII)
Programação avançada no banco de dados: variáveis, controle de fluxo, cursores, exceções e auditoria profissional
Administração de Banco de Dados (PARTE XXVII)
Programação avançada no banco de dados: variáveis, controle de fluxo, cursores, exceções e auditoria profissional
No artigo anterior demos nossos primeiros passos na programação dentro do banco de dados. Conhecemos os conceitos fundamentais de Stored Procedures, Functions e Triggers, compreendendo o propósito de cada uma delas e como transformam o banco de dados em um motor ativo de regras de negócio, permitindo que parte da lógica de negócio seja executada diretamente pelo Sistema Gerenciador de Banco de Dados (SGBD).
Entretanto, os exemplos apresentados naquele momento eram relativamente simples. Criamos funções capazes de realizar cálculos básicos, procedures responsáveis por executar tarefas específicas e triggers que reagiam automaticamente a determinados eventos. No entanto, na vida real, o desenvolvimento de rotinas de banco de dados (utilizando linguagens como o PL/pgSQL no PostgreSQL) exige a construção de algoritmos complexos. Precisamos declarar variáveis, tomar decisões baseadas em estruturas condicionais, iterar sobre milhares de registros linha por linha, percorrer conjuntos de registros, tratar erros e construir mecanismos avançados de auditoria.
Neste artigo, vamos dominar a sintaxe avançada da programação relacional. Aprenderemos a declarar variáveis, manipular parâmetros, controlar o fluxo de execução com estruturas de repetição e condicional, e culminaremos construindo um sistema de auditoria profissional e blindado. Ao final deste capítulo, teremos uma visão muito mais próxima do que realmente ocorre em ambientes corporativos que utilizam programação avançada diretamente no banco de dados.
Cenário de testes
Para os exemplos práticos deste artigo, continuaremos utilizando as nossas tabelas clientes e pedidos. Caso não as tenha no seu SGBD, aqui está a estrutura básica:
Estrutura da tabela clientes
CREATE TABLE clientes (
id_cliente INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
nome VARCHAR(150) NOT NULL,
cpf CHAR(11) UNIQUE NOT NULL,
email VARCHAR(60) UNIQUE NOT NULL,
salario DECIMAL(10,2) NOT NULL,
data_cadastro DATE NOT NULL DEFAULT CURRENT_DATE,
CHECK(salario > 0)
);
-- Comando para inserir dados nessa tabela clientes
INSERT INTO clientes (nome, cpf, email, salario)
VALUES
('Ana Souza', '99988877766', 'ana@email.com', 4200.00),
('Pedro Lima', '44455566677', 'pedro@email.com', 6100.00),
('Fernanda Costa', '33322211100', 'fernanda@email.com', 5300.00),
('Carlos Mendes', '77788899900', 'carlos@email.com', 3900.00),
('Marta Rodrigues', '12588899600', 'marta@email.com', 3500.00);
Estrutura da tabela pedidos
CREATE TYPE status_pedido AS ENUM (
'Finalizado', 'Em_andamento', 'Cancelado'
);
CREATE TABLE pedidos (
id_pedido INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
data_pedido DATE NOT NULL DEFAULT CURRENT_DATE,
valor_total DECIMAL(10,2) NOT NULL,
status status_pedido NOT NULL, id_cliente INT NOT NULL,
CHECK(valor_total > 0),
CONSTRAINT fk_pedido_id_cliente FOREIGN KEY (id_cliente)
REFERENCES clientes(id_cliente)
);
-- Comando para inserir dados nessa tabela pedidos
INSERT INTO pedidos (data_pedido, valor_total, status, id_cliente)
VALUES
('2026-05-21', 150.00, 'Em_andamento', 1),
('2026-05-22', 320.00, 'Finalizado', 2),
('2026-05-23', 570.00, 'Cancelado', 2),
('2026-05-24', 80.00, 'Finalizado', 3),
('2026-05-25', 235.00, 'Em_andamento', 3);
Utilizaremos essas estruturas para demonstrar os exemplos apresentados ao longo deste artigo.
Variáveis
Assim como ocorre nas linguagens de programação tradicionais, o PL/pgSQL permite criar variáveis para armazenar informações temporariamente durante a execução de uma Procedure, Function ou Trigger.
As variáveis são extremamente úteis para guardar valores intermediários, realizar cálculos, armazenar resultados de consultas e controlar o fluxo de execução de uma rotina. No PostgreSQL, as variáveis são declaradas dentro da seção DECLARE, localizada entre o cabeçalho da rotina e o bloco BEGIN.
A sintaxe básica é a mostrada a seguir:
DECLARE
nome_variavel tipo_dado;
Onde:
- DECLARE: inicia a área de declaração das variáveis;
- nome_variavel: representa o identificador ou nome escolhido para a variável;
- tipo_dado: define qual tipo de informação ela poderá armazenar.
Por exemplo:
DECLARE
nome_cliente VARCHAR(100);
Nesse caso:
- nome_cliente: é o nome da variável;
- VARCHAR(100): indica que ela poderá armazenar textos com até 100 caracteres.
Após a declaração, a variável pode receber valores dentro do bloco BEGIN … END utilizando o operador de atribuição :=.
Para entender melhor a utilidade das variáveis, imagine que desejamos criar uma Function que receba o ID de um cliente e retorne um pequeno resumo contendo o nome do cliente, a quantidade de pedidos realizados e o valor total gasto por esse cliente.
Para isso, podemos armazenar essas informações temporariamente em variáveis, como mostrado a seguir:
CREATE OR REPLACE FUNCTION resumo_cliente(cliente_id INT)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
nome_cliente VARCHAR(150);
quantidade_pedidos INT;
total_gasto DECIMAL(10,2);
BEGIN
SELECT nome
INTO nome_cliente
FROM clientes
WHERE id_cliente = cliente_id;
SELECT COUNT(*),
COALESCE(SUM(valor_total), 0)
INTO quantidade_pedidos, total_gasto
FROM pedidos
WHERE id_cliente = cliente_id;
RETURN 'Cliente: ' || nome_cliente ||
' | Pedidos: ' || quantidade_pedidos ||
' | Total gasto: R$ ' || total_gasto;
END;
$$;
Nesse exemplo, criamos três variáveis dentro da seção DECLARE, sendo elas:
- nome_cliente: responsável por armazenar o nome encontrado na tabela clientes;
- quantidade_pedidos: responsável por armazenar a quantidade de pedidos do cliente;
- total_gasto: responsável por armazenar a soma dos valores dos pedidos.
Dentro do bloco BEGIN … END, usamos o comando SELECT … INTO para guardar o resultado das consultas dentro das variáveis.
Vamos analisar o que acontece durante a execução:
- Buscamos o nome do cliente:
SELECT nome
INTO nome_cliente
FROM clientes
WHERE id_cliente = cliente_id;
- Buscamos a quantidade de pedidos e o valor total gasto:
SELECT COUNT(*),
COALESCE(SUM(valor_total), 0)
INTO quantidade_pedidos, total_gasto
FROM pedidos
WHERE id_cliente = cliente_id;
- Usamos as variáveis para montar a mensagem de retorno da Function:
RETURN 'Cliente: ' || nome_cliente ||
' | Pedidos: ' || quantidade_pedidos ||
' | Total gasto: R$ ' || total_gasto;
Para executar a Function, podemos chamá-la da seguinte forma:
SELECT resumo_cliente(2);
Como o id_cliente = 2 possui dois pedidos cadastrados, o retorno será semelhante a:
resumo_cliente |
--------------------------------------------------------+
Cliente: Ana Souza | Pedidos: 2 | Total gasto: R$ 890.00|
Nesse caso, as variáveis deixam de ser apenas elementos estáticos e passam a cumprir um papel prático: armazenar resultados intermediários para que a Function consiga montar uma resposta final mais completa.
As variáveis podem armazenar diversos tipos de informação, incluindo:
- números inteiros;
- números decimais;
- textos;
- datas;
- horários;
- valores booleanos;
- resultados de consultas SQL;
- registros completos de tabelas.
Por esse motivo, elas constituem um dos recursos fundamentais da programação em PL/pgSQL, permitindo que as rotinas manipulem dados temporariamente antes de executar operações mais complexas.
Parâmetros IN
Até este momento, criamos rotinas capazes de executar operações e processar informações. Entretanto, muitas vezes precisamos que uma Procedure ou Function trabalhe com valores fornecidos pelo próprio usuário ou pela aplicação. Para isso utilizamos os parâmetros IN.
Os parâmetros IN representam os dados de entrada de uma rotina. Em outras palavras, eles permitem que informações sejam enviadas para uma Procedure ou Function no momento de sua execução.
A sintaxe básica é a seguinte:
nome_parametro tipo_dado
ou, de forma explícita:
IN nome_parametro tipo_dado
Onde:
- IN: indica que o parâmetro será utilizado para receber um valor de entrada;
- nome_parametro: representa o nome escolhido para o parâmetro;
- tipo_dado: define qual tipo de informação poderá ser recebido.
Observe a Function mostrada a seguir, utilizada para dar um bônus de 10% do salário informado a um cliente em específico:
CREATE OR REPLACE FUNCTION calcular_bonus(IN salario DECIMAL)
RETURNS DECIMAL
LANGUAGE plpgsql
AS $$
BEGIN
RETURN salario * 0.10;
END;
$$;
Nesse caso:
- salario: é o parâmetro de entrada;
- o tipo DECIMAL: indica que ele receberá valores numéricos;
- o valor recebido será utilizado para calcular um bônus correspondente a 10% do salário informado.
Após criar a Function, podemos executá-la da seguinte forma:
SELECT calcular_bonus(5000);
Durante a execução, o valor 5000 é enviado para a Function e armazenado no parâmetro salario.
Assim, quando a instrução ***RETURN salario * 0.10*** for executada, o PostgreSQL utilizará o valor recebido para realizar o cálculo e retornará:
500.00
Os parâmetros IN são amplamente utilizados para tornar Procedures e Functions mais flexíveis, permitindo que a mesma rotina seja reutilizada com diferentes valores de entrada sem a necessidade de alterar seu código.
Parâmetros OUT
Enquanto os parâmetros IN são utilizados para receber informações, os parâmetros *OUT são utilizados para devolver resultados produzidos por uma Procedure *ou Function.
Em outras palavras, eles funcionam como variáveis de saída, permitindo que a rotina retorne valores para quem a executou.
A sintaxe básica é a seguinte:
OUT nome_parametro tipo_de_dado
Onde:
- OUT: indica que o parâmetro será utilizado para retornar um valor;
- nome_parametro: representa o nome do parâmetro de saída;
- tipo_de_dado: define qual tipo de informação será retornado.
Observe o exemplo a seguir:
CREATE OR REPLACE PROCEDURE obter_dados_cliente(
IN p_id INT,
OUT p_nome VARCHAR
)
LANGUAGE plpgsql
AS $$
BEGIN
SELECT nome
INTO p_nome
FROM clientes
WHERE id_cliente = p_id;
END;
$$;
Vamos analisar o funcionamento dessa Procedure:
- p_id: é um parâmetro de entrada que recebe o código do cliente;
- p_nome: é um parâmetro de saída que armazenará o nome encontrado;
- o comando SELECT … INTO busca o nome do cliente e o atribui ao parâmetro p_nome;
- ao final da execução, o valor armazenado em p_nome é devolvido automaticamente.
Após criar a Function, podemos executá-la da seguinte forma:
SELECT obter_dados_cliente(1);
Assim, ao executar a Procedure informando o valor id_cliente = 1, o PostgreSQL localizará o registro correspondente e armazenará o nome encontrado no parâmetro de saída p_nome.
Assim, o resultado retornado será:
Ana Souza
Os parâmetros OUT são úteis quando desejamos devolver informações produzidas pela rotina sem a necessidade de utilizar uma instrução RETURN. Dessa forma, uma Procedure pode receber dados de entrada, processá-los e disponibilizar os resultados por meio de um ou mais parâmetros de saída.
Cabe mencionar que em PostgreSQL moderno, é muito mais comum utilizar Functions com RETURN para retornar valores. Os parâmetros OUT existem e são úteis para fins didáticos e alguns cenários específicos, mas aparecem com menos frequência no dia a dia do que os parâmetros IN e o uso tradicional de RETURN.
Também é possível usar os parâmetros IN e OUT juntos. Para demostrar seu uso combinado, vamos criar uma função que recebe o ID de um cliente (IN) e devolve simultaneamente a quantidade de pedidos dele e o valor total gasto (dois parâmetros OUT), como mostrado a seguir:
CREATE OR REPLACE FUNCTION obter_estatisticas_cliente(
p_id_cliente IN INT,
p_qtd_pedidos OUT INT,
p_total_gasto OUT DECIMAL
)
LANGUAGE plpgsql AS $$
DECLARE
-- Variável interna apenas para controle
v_status_alvo VARCHAR := 'Finalizado';
BEGIN
-- Contando os pedidos
SELECT COUNT(id_pedido) INTO p_qtd_pedidos
FROM pedidos
WHERE id_cliente = p_id_cliente AND status = v_status_alvo;
-- Somando o valor (usando COALESCE para evitar NULL se não houver pedidos)
SELECT COALESCE(SUM(valor_total), 0) INTO p_total_gasto
FROM pedidos
WHERE id_cliente = p_id_cliente AND status = v_status_alvo;
END;
$$;
Ao executar ***SELECT * FROM obter_estatisticas_cliente(2);***, o banco retornará duas colunas distintas em uma única chamada.
Parâmetros INOUT
Os parâmetros INOUT combinam as características dos parâmetros IN e OUT. Isso significa que eles podem receber um valor de entrada, ser modificados durante a execução da rotina e, ao final, devolver o valor atualizado.
Em outras palavras, o mesmo parâmetro é utilizado tanto para fornecer informações à rotina quanto para retornar o resultado do processamento.
A sintaxe básica é a seguinte:
INOUT nome_parametro tipo_dado
Onde:
- INOUT: indica que o parâmetro funcionará como entrada e saída;
- nome_parametro: representa o nome do parâmetro;
- tipo_dado: define qual tipo de informação será manipulada.
Observe o exemplo de uma Procedure cujo propósito é dar um aumento de 10% no salario dos usuários, conforme mostrado a seguir:
CREATE OR REPLACE PROCEDURE aplicar_aumento(
INOUT salario DECIMAL
)
LANGUAGE plpgsql
AS $$
BEGIN
salario := salario * 1.10;
END;
$$;
Nesse caso:
- o parâmetro salario recebe um valor de entrada;
- a Procedure aplica um aumento de 10%;
- o valor atualizado é devolvido automaticamente ao final da execução.
Suponha que o valor inicial seja 5000.00. Durante a execução da Procedure, a instrução:
salario := salario * 1.10;
calcula um aumento de 10%, produzindo o valor:
5500.00
Assim, o mesmo parâmetro que recebeu o valor de entrada passa a armazenar o resultado final da operação.
Os parâmetros INOUT são úteis em situações nas quais uma informação precisa ser recebida, processada e devolvida pela rotina. Embora não sejam tão utilizados quanto os parâmetros IN, eles podem simplificar determinados cenários ao eliminar a necessidade de criar parâmetros separados para entrada e saída.
Comparando os parâmetros IN, OUT e INOUT
Os parâmetros IN, OUT e INOUT definem a forma como uma Procedure ou Function troca informações com o ambiente externo. Embora a sintaxe seja semelhante, cada tipo possui uma finalidade específica.
Em termos práticos:
- IN: recebe informações para processamento;
- OUT: devolve informações produzidas pela rotina;
- INOUT: recebe informações, modifica seus valores e devolve o resultado.
Essa distinção é importante porque permite construir rotinas mais flexíveis, definindo claramente quais informações entram, quais saem e quais podem ser alteradas durante a execução.
Estruturas condicionais com IF / ELSIF / ELSE
Em muitas situações, uma Procedure, Function ou Trigger precisa tomar decisões com base nos dados que está processando. Afinal, nem todas as regras de negócio produzem o mesmo resultado para todos os cenários.
Para lidar com esse tipo de situação, o PL/pgSQL oferece as estruturas condicionais IF, ELSIF e ELSE. Essas estruturas permitem que diferentes blocos de código sejam executados dependendo do resultado de uma condição.
O comando IF
A estrutura mais simples é o IF, utilizado quando desejamos executar uma ação somente se uma determinada condição for verdadeira.
Sua sintaxe básica é:
IF condicao
THEN instrucoes;
END IF;
Onde:
- IF: inicia a estrutura condicional;
- condicao: representa a expressão que será avaliada;
- THEN: indica o início das instruções que serão executadas caso a condição seja verdadeira;
- END IF: encerra a estrutura.
Observe o exemplo:
IF salario > 5000 THEN
bonus := salario * 0.15;
END IF;
Nesse caso, o bônus será calculado apenas para funcionários cujo salário seja superior a R$ 5.000,00. Podemos representar essa lógica da seguinte forma:
salario > 5000 ?
↓
Sim
↓
Calcula bônus de 15%
Se a condição for falsa, nenhuma ação será executada.
Utilizando a instrução ELSE
Em alguns cenários, precisamos definir uma ação alternativa para quando a condição não for satisfeita. Para isso utilizamos o bloco ELSE, como mostrado a seguir:
IF salario > 5000 THEN
bonus := salario * 0.15;
ELSE
bonus := salario * 0.05;
END IF;
Agora existem dois caminhos possíveis:
salario > 5000 ?
↓
┌───┴───┐
Sim Não
↓ ↓
15% 5%
Isto é: se o salário for superior a R$ 5.000,00, o bônus será de 15%. Caso contrário, será aplicado um bônus de 5%.
Utilizando ELSIF
Quando existem várias condições possíveis, podemos utilizar o comando ELSIF, pois ele permite criar múltiplas decisões dentro da mesma estrutura condicional da forma mostrada a seguir.
IF salario > 10000 THEN
bonus := 2000;
ELSIF salario > 5000 THEN
bonus := 1000;
ELSE
bonus := 500;
END IF;
Nesse exemplo:
- salários acima de R$ 10.000,00 recebem R$ 2.000,00 de bônus;
- salários acima de R$ 5.000,00 recebem R$ 1.000,00;
- os demais recebem R$ 500,00.
Podemos visualizar o fluxo da seguinte forma:
salario > 10000 ?
↓
┌──────┴─────┐
Sim Não
↓ ↓
2000 salario > 5000 ?
↓
┌────┴────┐
Sim Não
↓ ↓
1000 500
Em resumo:
- IF: Executa ações quando uma condição é verdadeira;
- ELSE: Define um caminho alternativo quando a condição é falsa;
- ELSIF: Permite testar condições adicionais.
Estruturas condicionais com a instrução CASE
Embora as estruturas IF, ELSIF e ELSE sejam extremamente úteis, existem situações em que precisamos comparar uma mesma variável com diversos valores possíveis. Nesses casos, o comando CASE costuma oferecer uma solução mais organizada e fácil de manter.
A instrução CASE permite executar diferentes blocos de código dependendo do valor analisado, funcionando como uma alternativa mais elegante a longas sequências de condições.
Sua sintaxe básica é a seguinte:
CASE expressao
WHEN valor_1 THEN
instrucoes;
WHEN valor_2 THEN
instrucoes;
ELSE
instrucoes;
END CASE;
Onde:
- CASE: inicia a estrutura condicional;
- expressao: representa o valor que será analisado;
- WHEN: define cada condição possível;
- THEN: indica as instruções associadas à condição;
- ELSE: define o comportamento padrão quando nenhuma condição for satisfeita;
- END CASE: encerra a estrutura.
Observe o exemplo a seguir:
CASE status
WHEN 'Finalizado' THEN mensagem := 'Pedido concluído';
WHEN 'Cancelado' THEN mensagem := 'Pedido cancelado';
ELSE mensagem := 'Pedido em andamento';
END CASE;
Nesse exemplo, o valor armazenado na variável status será analisado e dependendo do resultado, uma mensagem diferente será atribuída à variável mensagem.
Podemos representar esse fluxo da seguinte forma:
status
↓
┌──────────────┼──────────────┐
↓ ↓ ↓
Finalizado Cancelado Outros
↓ ↓ ↓
Pedido Pedido Pedido
em concluído cancelado andamento
Suponha que a variável status possua o valor ‘Cancelado’. Nesse caso, o PostgreSQL executará o bloco:
WHEN 'Cancelado'
THEN mensagem := 'Pedido cancelado';
E a variável mensagem passará a conter ‘Pedido cancelado’.
Qual estrutura devo usar: IF ou CASE?
Em muitos cenários, tanto a estrutura CASE quanto o IF podem produzir o mesmo resultado. A diferença está principalmente na organização do código.
Quando estamos avaliando diferentes condições independentes ou expressões complexas, o IF costuma ser a melhor escolha. Por outro lado, quando precisamos comparar uma mesma variável com vários valores possíveis, o CASE geralmente torna o código mais limpo e fácil de compreender.
Observe a seguinte comparação:
Estrutura IF.
IF status = 'Finalizado' THEN mensagem := 'Pedido concluído';
ELSIF status = 'Cancelado' THEN mensagem := 'Pedido cancelado';
ELSE mensagem := 'Pedido em andamento';
END IF;
Estrutura CASE.
CASE status
WHEN 'Finalizado' THEN mensagem := 'Pedido concluído';
WHEN 'Cancelado' THEN mensagem := 'Pedido cancelado';
ELSE mensagem := 'Pedido em andamento';
END CASE;
Ambas as estruturas produzem o mesmo resultado, mas o CASE costuma ser mais legível quando existem muitas alternativas possíveis. Por esse motivo, ele é amplamente utilizado para classificar valores, categorizar informações e implementar regras de negócio que dependem de múltiplos cenários previamente conhecidos.
Estruturas de repetição
Até este momento, vimos como tomar decisões utilizando estruturas condicionais como IF e CASE. Entretanto, existem situações em que precisamos executar uma mesma tarefa várias vezes durante a execução de uma Procedure, Function ou Trigger.
Para resolver esse tipo de problema, o PL/pgSQL oferece as chamadas estruturas de repetição, também conhecidas como laços de repetição ou loops. Essas estruturas permitem que um bloco de instruções seja executado repetidamente até que determinada condição seja satisfeita.
De forma simplificada, podemos representar o funcionamento de um laço da seguinte maneira:
Início
|
▼
Executa instruções
|
▼
Sim ◄────── Condição satisfeita? ◄──────┐
| | |
▼ ▼ |
Fim Não ────────► Repete novamente
As estruturas de repetição são úteis em diversas situações, como:
- percorrer registros retornados por consultas;
- processar grandes volumes de dados;
- executar cálculos repetitivos;
- gerar relatórios;
- realizar validações em múltiplos registros;
- automatizar tarefas administrativas.
Imagine, por exemplo, que precisamos analisar todos os pedidos cadastrados no banco de dados para calcular estatísticas ou gerar notificações. Em vez de escrever o mesmo código várias vezes, podemos utilizar uma estrutura de repetição para processar cada registro automaticamente.
Neste contexto, o PL/pgSQL oferece diferentes mecanismos para esse tipo de processamento, entre eles:
- LOOP;
- WHILE;
- FOR.
Cada um desses recursos foi projetado para cenários específicos e oferece diferentes formas de controlar a repetição das instruções. A seguir estudaremos cada uma dessas estruturas e veremos quando utilizar cada uma delas.
Instrução LOOP
O LOOP representa a forma mais simples de estrutura de repetição disponível no PL/pgSQL.
Sua principal característica é que ele executa um bloco de instruções continuamente até que uma condição de parada seja encontrada. Diferentemente de outras estruturas, o LOOP não possui uma condição de término definida em sua declaração. Por esse motivo, cabe ao programador indicar explicitamente quando a repetição deverá ser encerrada.
A sintaxe básica é a seguinte:
LOOP
instrucoes;
EXIT WHEN condicao;
END LOOP;
Onde:
- LOOP: inicia o laço de repetição;
- instrucoes: representa o conjunto de comandos que será executado repetidamente;
- EXIT WHEN: define a condição de parada;
- END LOOP: encerra a estrutura.
Observe o exemplo a seguir:
LOOP
contador := contador + 1;
EXIT WHEN contador > 10;
END LOOP;
Nesse caso, a variável contador é incrementada a cada execução do laço. Após cada incremento, o PostgreSQL verifica a condição:
contador > 10
Enquanto essa condição for falsa, o laço continuará executando. Quando ela se tornar verdadeira, a instrução EXIT WHEN interromperá imediatamente a repetição.
Podemos representar esse processo da seguinte forma:
contador := contador + 1
|
▼
┌────────────────► contador > 10 ?
| |
Repete ◄────── Não ◄───────|
novamente |
▼
Sim
↓
Encerra
Suponha que a variável contador tenha sido inicializada com o valor contador := 0;.
As primeiras execuções ocorrerão da seguinte maneira:
| Iteração | Valor do contador |
| -------- | ----------------- |
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| ... | ... |
| 10 | 1 |
| 11 | 11 |
Quando o contador atingir o valor 11, a condição contador > 10 será verdadeira e o laço será encerrado.
A importância do EXIT
Ao utilizar um LOOP, é fundamental garantir que exista algum mecanismo de saída.
Por exemplo, o código abaixo nunca será interrompido:
LOOP
contador := contador + 1;
END LOOP;
Como não existe uma condição de parada, o PostgreSQL continuará executando o laço indefinidamente.
Por esse motivo, o comando EXIT é frequentemente utilizado em conjunto com o LOOP, garantindo que a repetição seja encerrada no momento apropriado.
Em termos práticos, o LOOP é indicado quando precisamos de total controle sobre o momento em que a repetição deve terminar, especialmente em situações nas quais a condição de parada não é conhecida antecipadamente.
Instrução WHILE
Enquanto o LOOP executa indefinidamente até encontrar uma instrução de saída, o WHILE já possui uma condição de repetição definida desde o início. Essa estrutura executa um bloco de instruções enquanto uma determinada condição permanecer verdadeira. Quando a condição se tornar falsa, a repetição será encerrada automaticamente.
A sintaxe básica é a seguinte:
WHILE condicao
LOOP instrucoes;
END LOOP;
Onde:
- WHILE: inicia a estrutura de repetição;
- condicao: representa a expressão que será avaliada antes de cada execução;
- LOOP: marca o início do bloco de instruções;
- instrucoes: corresponde aos comandos que serão repetidos;
- END LOOP: encerra a estrutura.
Observe o exemplo a seguir:
WHILE v_estoque > 0 LOOP
-- processa baixa de estoque
v_estoque := v_estoque - 1;
END LOOP;
Nesse caso, o PostgreSQL verificará a condição:
v_estoque > 0
Se a condição for verdadeira, o bloco será executado. Após a execução, a condição será avaliada novamente. Esse processo continuará até que a condição se torne falsa.
Podemos representar esse funcionamento da seguinte forma:
┌────────────────► v_estoque > 0 ?
| |
| ▼
| ┌─────┴─────┐
| | |
| Sim Não
| | |
| ▼ ▼
Volta para a ◄───────── Executa Encerra
condição bloco
Suponha que a variável tenha sido inicializada da seguinte forma:
v_estoque := 5;
As execuções ocorrerão da seguinte maneira:
| Iteração | Valor de v_estoque|
| -------- | ----------------- |
| inicial | 5 |
| 1 | 4 |
| 2 | 3 |
| 3 | 2 |
| 4 | 1 |
| 5 | 0 |
Quando o valor de v_estoque atingir 0, a condição v_estoque > 0 será falsa e o laço será encerrado automaticamente.
Por esse motivo, o WHILE costuma ser mais indicado quando a condição de repetição já é conhecida desde o início, tornando o código mais simples e fácil de compreender.
Instrução FOR
O FOR é uma das estruturas de repetição mais utilizadas no PL/pgSQL. Diferentemente do LOOP e do WHILE, ele é especialmente indicado quando já sabemos antecipadamente quantas vezes o bloco de código deverá ser executado. Nesse tipo de situação, o próprio PostgreSQL controla automaticamente a variável de repetição, tornando o código mais simples e legível.
A sintaxe básica é a seguinte:
FOR variavel IN valor_inicial..valor_final LOOP
instrucoes;
END LOOP;
Onde:
- FOR: inicia a estrutura de repetição;
- variavel: representa o contador utilizado pelo laço;
- IN: indica o intervalo de valores que será percorrido;
- valor_inicial..valor_final: define a faixa de repetição;
- LOOP: inicia o bloco de instruções;
- END LOOP: encerra a estrutura.
Imagine que desejamos processar todos os pedidos que ainda estão em andamento. Para isso, podemos utilizar uma variável do tipo RECORD, capaz de armazenar temporariamente cada linha retornada pela consulta.
DECLARE
r_pedido RECORD;
BEGIN
FOR r_pedido IN (
SELECT id_pedido, valor_total
FROM pedidos
WHERE status = 'Em_andamento'
) LOOP
RAISE NOTICE
'Processando pedido % no valor de %',
r_pedido.id_pedido,
r_pedido.valor_total;
END LOOP;
END;
Vamos analisar cada parte da estrutura:
- r_pedido RECORD: declara uma variável capaz de armazenar uma linha completa retornada pela consulta;
- FOR r_pedido IN (…): percorre os registros retornados pelo SELECT;
- a cada repetição, uma nova linha é armazenada em r_pedido;
- r_pedido.id_pedido: acessa a coluna id_pedido do registro atual;
- r_pedido.valor_total: acessa a coluna valor_total do registro atual;
- RAISE NOTICE: exibe uma mensagem informativa durante a execução.
Podemos representar esse fluxo da seguinte forma:
i = 1
↓
Executa bloco
↓
i = 2
↓
Executa bloco
↓
...
↓
i = 10
↓
Executa bloco
↓
Fim
Durante a execução, o PostgreSQL considerará os dados cadastrados anteriormente e exibirá o seguinte no console:
| id_pedido| valor_total| status |
| -------- | -----------| ------------ |
| 1 | 150.00 | Em_andamento |
| 2 | 320.00 | Finalizado |
| 3 | 570.00 | Cancelado |
| 4 | 80.00 | Finalizad |
| 5 | 235.00 | Em_andamento |
A consulta retornará apenas os pedidos em andamento:
| id_pedido| valor_total|
| -------- | -----------|
| 1 | 150.00 |
| 5 | 235.00 |
Consequentemente, o laço será executado duas vezes, produzindo mensagens semelhantes a:
Processando pedido 1 no valor de 150.00
Processando pedido 5 no valor de 235.00
Podemos visualizar esse processo da seguinte forma:
SELECT retorna pedidos
|
▼
Pedido 1 ────► Processa
|
▼
Pedido 5 ────► Processa
|
▼
Fim dos registros
|
▼
Encerrar LOOP
Esse tipo de estrutura é amplamente utilizado para processar registros individualmente, gerar relatórios, executar validações, realizar auditorias e automatizar tarefas administrativas dentro do banco de dados.
Comparação entre as instruções LOOP, WHILE e FOR
As três estruturas permitem criar repetições, mas cada uma é mais adequada para um tipo específico de situação.
| Estrutura| Quando utilizar |
| -------- | ---------------------------------------------------------------- |
| LOOP | Quando a condição de parada será definida dentro do laço |
| WHILE | Quando existe uma condição de repetição conhecida desde o início |
| FOR | Quando sabemos exatamente quantas repetições serão necessárias |
Observe a diferença conceitual:
- LOOP: Repete até encontrar uma saída.
- WHILE: Repete enquanto uma condição for verdadeira.
- FOR: Repete uma quantidade conhecida de vezes.
Por sua simplicidade, o FOR é amplamente utilizado para percorrer intervalos numéricos, processar conjuntos de registros e executar tarefas repetitivas cuja quantidade de execuções já é conhecida previamente.
Cursores: percorrendo resultados linha por linha
Em muitas situações, uma consulta pode retornar centenas, milhares ou até milhões de registros. Embora o PostgreSQL seja capaz de processar conjuntos inteiros de dados, existem cenários nos quais precisamos analisar cada linha individualmente. Para esse tipo de situação utilizamos os cursores (cursors).
De forma simplificada, um cursor pode ser entendido como um ponteiro que percorre os resultados de uma consulta, permitindo que os registros sejam processados um de cada vez, mantendo uma referência para a linha que está sendo processada naquele momento.
Podemos representar esse funcionamento da seguinte forma:
Consulta SQL
↓
┌─────────────┐
│ Registro 1 │ ◄── Cursor
├─────────────┤
│ Registro 2 │
├─────────────┤
│ Registro 3 │
└─────────────┘
À medida que a execução avança, o cursor se desloca para o próximo registro disponível até que não existam mais linhas para processar.
No PostgreSQL, um cursor explícito pode ser utilizado da seguinte forma:
DECLARE
cur_pedidos CURSOR FOR
SELECT id_pedido
FROM pedidos;
v_id INT;
BEGIN
OPEN cur_pedidos;
LOOP
FETCH cur_pedidos INTO v_id;
EXIT WHEN NOT FOUND;
RAISE NOTICE 'Processando pedido %', v_id;
END LOOP;
CLOSE cur_pedidos;
END;
Vamos analisar cada etapa:
- DECLARE cur_pedidos CURSOR FOR … cria o cursor associado à consulta;
- OPEN cur_pedidos executa a consulta e prepara o cursor para navegação;
- FETCH cur_pedidos INTO v_id obtém o próximo registro disponível;
- EXIT WHEN NOT FOUND encerra o laço quando não existirem mais registros;
- CLOSE cur_pedidos libera os recursos utilizados pelo cursor.
O fluxo de execução pode ser representado da seguinte forma:
OPEN
↓
┌────────────────► FETCH
| ↓
| Registro encontrado?
| ↓
| ┌───────┴───────┐
| │ │
| Sim Não
| │ │
| ▼ ▼
└◄────── Processa CLOSE
Esse tipo de processamento é extremamente útil quando precisamos executar operações específicas para cada registro retornado por uma consulta.
Os cursores são amplamente empregados em rotinas administrativas, integrações entre sistemas, migrações de dados. processos que exigem tratamento individual dos registros retornados por uma consulta, auditorias, importação e exportação de dados, processos de migração e outras tarefas que exigem o tratamento individual dos registros.
Embora os cursores sejam extremamente úteis em determinados cenários, eles não devem ser utilizados indiscriminadamente. Sempre que possível, operações baseadas em conjuntos de dados (set-based operations) tendem a ser mais simples e eficientes. Os cursores tornam-se especialmente valiosos quando existe a necessidade de processar cada registro individualmente.
Tratamento de exceções
Em sistemas reais, erros acontecem. Uma consulta pode não encontrar os dados esperados, uma divisão pode resultar em erro, uma constraint pode ser violada ou uma operação pode falhar por diversos outros motivos.
Quando uma exceção ocorre dentro de uma Procedure, Function ou Trigger, o PostgreSQL interrompe a execução da rotina e sinaliza o erro. Em muitos casos, isso é desejável. Entretanto, existem situações em que precisamos tratar essas falhas de forma controlada, executando ações alternativas em vez de simplesmente encerrar o processamento. É para esse tipo de situações que o PL/pgSQL disponibiliza o bloco EXCEPTION.
Seu funcionamento funciona da seguinte forma:
Executa instruções
↓
Ocorreu erro?
↓
┌─────┴─────┐
│ │
Não Sim
│ │
▼ ▼
Continua EXCEPTION
execução ↓
Trata o erro
A sintaxe básica é a seguinte:
BEGIN
instrucoes;
EXCEPTION
WHEN tipo_erro THEN tratamento;
END;
Onde:
- BEGIN: inicia o bloco principal de execução;
- instrucoes: representa os comandos que serão executados;
- EXCEPTION: inicia a área de tratamento de erros;
- WHEN: especifica qual exceção será capturada;
- THEN: define a ação que será executada quando o erro ocorrer;
- END: encerra o bloco.
Observe o exemplo a seguir:
BEGIN
INSERT INTO clientes (nome, cpf, email, salario)
VALUES ('João Gomes', '12588899634', 'joao@email.com', 6500.00);
EXCEPTION WHEN unique_violation THEN RAISE NOTICE 'Atenção: Este e-mail já está cadastrado no sistema.';
WHEN OTHERS THEN RAISE NOTICE 'Ocorreu um erro inesperado.';
END;
Nesse caso, o PostgreSQL tentará executar o comando INSERT. Se a operação for concluída com sucesso, o bloco EXCEPTION será ignorado.
Entretanto, caso o e-mail informado já exista na tabela e a constraint UNIQUE seja violada, a exceção unique_violation será capturada e a mensagem definida será exibida.
Podemos visualizar esse processo da seguinte forma:
INSERT cliente
↓
Houve Erro?
↓
┌─────────┴─────────┐
│ │
Não Sim
│ │
▼ ▼
INSERT realizado unique_violation
com Sucesso ↓
Exibe mensagem
Observe também a presença do bloco:
WHEN OTHERS THEN
A palavra-chave OTHERS funciona como uma condição genérica, capturando qualquer exceção que não tenha sido tratada explicitamente pelos blocos anteriores.
Exceções comuns no PostgreSQL
O PL/pgSQL disponibiliza diversas exceções predefinidas para facilitar o tratamento de erros. Entre as mais utilizadas estão:
| Exceção | Descrição / Significado |
| --------------------- | ----------------------------------|
| division_by_zero | Tentativa de divisão por zero |
| no_data_found | Nenhum registro encontrado |
| unique_violation | Violação de uma constraint UNIQUE |
| foreign_key_violation | Violação de uma chave estrangeira |
Essas exceções permitem criar rotinas mais robustas e preparadas para lidar com situações inesperadas sem comprometer completamente a execução do sistema.
Por esse motivo, o tratamento de exceções é considerado uma das práticas mais importantes na programação de Procedures, Functions e Triggers, contribuindo para a confiabilidade, a segurança e a estabilidade das aplicações que dependem do banco de dados.
Exceções devem ser utilizadas com moderação
Embora o tratamento de exceções seja um recurso extremamente importante, ele não deve ser utilizado como mecanismo principal de controle de fluxo da aplicação, já que quando uma exceção é disparada, o PostgreSQL precisa interromper a execução normal da rotina, desfazer as operações realizadas dentro do bloco que gerou o erro e transferir o controle para a área de tratamento definida em EXCEPTION.
Em termos internos, esse processo envolve mecanismos adicionais de gerenciamento de transações, tornando o tratamento de exceções mais custoso do que uma simples avaliação condicional utilizando estruturas como IF ou CASE.
Por esse motivo, exceções devem ser tratadas como situações realmente excepcionais, ou seja, eventos inesperados que não fazem parte do fluxo normal de execução.
Como regra geral, sempre que for possível prever uma situação através de validações ou estruturas condicionais, essa abordagem costuma ser preferível ao uso de exceções. O bloco EXCEPTION deve ficar reservado para falhas que realmente não podem ser evitadas ou previstas antecipadamente.
Triggers Avançadas
Nas seções anteriores, aprendemos que uma Trigger é executada automaticamente quando determinados eventos ocorrem em uma tabela. Entretanto, nem todas as Triggers são acionadas no mesmo momento. Assim, a classificação das Triggers está relacionada ao instante em que ela é executada e à operação que a disparou. Sendo assim, a seguir serão apresentadas as classificações das Triggers.
1. Trigger BEFORE
As Triggers BEFORE são aquelas executadas antes que a operação seja efetivamente realizada no banco de dados. Isso significa que elas possuem a capacidade de validar informações, modificar valores ou até mesmo impedir que a operação aconteça.
Por esse motivo, esse tipo de Trigger é amplamente utilizado para implementar regras de validação e garantir a integridade dos dados antes que eles sejam armazenados.
Observe o exemplo abaixo:
CREATE FUNCTION validar_salario()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.salario <= 0 THEN
RAISE EXCEPTION 'Salário inválido';
END IF;
RETURN NEW;
END;
$$;
Nesse caso:
- NEW.salario: representa o valor que está sendo inserido ou atualizado;
- a condição verifica se o salário é menor ou igual a zero;
- caso a validação falhe, uma exceção é lançada através de RAISE EXCEPTION;
- a operação é imediatamente interrompida;
- RETURN NEW: permite que o registro continue seu processamento quando a validação for bem-sucedida.
Após criar a Function, associamos a Trigger à tabela clientes:
CREATE TRIGGER trg_validar_salario
BEFORE INSERT OR UPDATE
ON clientes
FOR EACH ROW
EXECUTE FUNCTION validar_salario();
Vamos analisar cada parte dessa definição:
- BEFORE: indica que a Trigger será executada antes da operação;
- INSERT OR UPDATE: define os eventos monitorados;
- ON clientes: associa a Trigger à tabela clientes;
- FOR EACH ROW: determina que a Trigger será executada para cada registro afetado;
- EXECUTE FUNCTION: especifica a Function que será chamada.
Suponha que um usuário tente executar a seguinte operação:
INSERT INTO clientes ( nome, cpf, email, salario )
VALUES ( 'Carlos Silva', '11122233344', 'carlos@email.com', -500 );
O fluxo de execução será o seguinte:
INSERT
↓
Trigger BEFORE
↓
salario <= 0 ?
↓
┌───┴───┐
│ │
Sim Não
│ │
▼ ▼
Erro Salva
Como o salário informado é inválido, a Trigger lançará a exceção:
Salário inválido
e o registro não será inserido na tabela.
Essa é justamente uma das principais vantagens das Triggers BEFORE: elas permitem impedir operações inválidas antes que qualquer alteração seja efetivamente gravada no banco de dados, funcionando como uma camada adicional de proteção para as informações armazenadas.
💡 Observação importante: Neste exemplo específico, a regra poderia ser implementada através de uma CHECK CONSTRAINT, que normalmente seria a solução mais simples. Ainda assim, o uso da Trigger é válido para fins didáticos porque demonstra claramente a capacidade das Triggers BEFORE de interceptar e bloquear operações antes da gravação dos dados.
2. Trigger AFTER
Enquanto as Triggers BEFORE são executadas antes da realização da operação, as Triggers AFTER são acionadas somente após a conclusão da ação que as disparou.
Isso significa que, quando uma Trigger AFTER é executada, a operação principal já foi realizada com sucesso pelo banco de dados. Por esse motivo, esse tipo de Trigger é amplamente utilizado em tarefas complementares, como:
- auditoria de alterações;
- geração de logs;
- envio de notificações;
- integração com outros sistemas;
- atualização de tabelas auxiliares;
- registro de históricos.
Podemos representar seu funcionamento da seguinte forma:
UPDATE
↓
Registro atualizado
↓
Trigger AFTER
↓
Executa ações complementares
Diferentemente das Triggers BEFORE, as Triggers AFTER normalmente não são utilizadas para impedir operações, mas sim para executar processos adicionais após a alteração dos dados.
Observe o exemplo a seguir:
CREATE TRIGGER trg_auditoria
AFTER UPDATE
ON clientes
FOR EACH ROW
EXECUTE FUNCTION registrar_auditoria();
Vamos analisar cada parte dessa definição:
- AFTER: indica que a Trigger será executada após a operação;
- UPDATE: define que o gatilho será acionado sempre que ocorrer uma atualização;
- ON clientes: associa a Trigger à tabela clientes;
- FOR EACH ROW: determina que a Trigger será executada para cada registro atualizado;
- EXECUTE FUNCTION registrar_auditoria(): define a rotina responsável pelo processamento da auditoria.
Considere a seguinte instrução:
UPDATE clientes
SET salario = 6500
WHERE id_cliente = 2;
O fluxo de execução será semelhante ao seguinte:
UPDATE executado
↓
Registro atualizado
↓
Trigger AFTER
↓
registrar_auditoria()
↓
Registro de auditoria criado
Nesse cenário, a alteração do salário ocorre primeiro. Somente após a atualização ser concluída é que a Function registrar_auditoria() será executada.
Essa característica torna as Triggers AFTER especialmente úteis para auditoria, pois elas trabalham com dados que já foram efetivamente gravados no banco de dados.
Além disso, como a operação principal já foi concluída, a Trigger pode utilizar informações do registro atualizado para gerar históricos, registrar logs ou alimentar outros processos sem interferir diretamente na alteração realizada pelo usuário.
Comparando Triggers BEFORE e AFTER
Uma dúvida bastante comum é saber quando utilizar uma Trigger BEFORE e quando utilizar uma Trigger AFTER.
A principal diferença está no momento em que cada uma é executada:
| Tipo | Momento da execução | Uso mais comum |
| -------- | ------------------- | ----------------------------------------------|
| BEFORE | Antes da operação | Validação e bloqueio de alterações inválidas |
| AFTER | Após a operação | Auditoria, logs e integrações |
De maneira geral, quando o objetivo é impedir ou corrigir uma alteração antes que ela seja gravada, a melhor escolha costuma ser uma Trigger BEFORE. Por outro lado, quando desejamos registrar informações ou executar ações complementares após a conclusão da operação, uma Trigger AFTER normalmente é a solução mais adequada.
Construindo um sistema completo de auditoria
Uma das aplicações mais importantes das Triggers é a implementação de mecanismos de auditoria.
Em ambientes corporativos, frequentemente existe a necessidade de registrar quem alterou um dado, quando a alteração ocorreu e quais informações foram modificadas. Esses registros são fundamentais para rastreabilidade, conformidade regulatória, segurança e investigação de incidentes.
Uma forma bastante comum de implementar auditoria consiste em utilizar uma Trigger para registrar automaticamente as alterações realizadas em uma tabela. O primeiro passo é criar uma tabela destinada ao armazenamento dos eventos de auditoria.
CREATE TABLE auditoria_clientes (
id_auditoria INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
data_evento TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
operacao VARCHAR(20),
id_cliente INT,
valor_anterior TEXT,
valor_novo TEXT
);
Essa tabela será responsável por armazenar informações sobre cada alteração realizada na tabela clientes.
Vamos analisar seus campos:
- id_auditoria: identifica cada evento registrado;
- data_evento: armazena o momento em que a alteração ocorreu;
- operacao: registra o tipo de operação executada;
- id_cliente: identifica o cliente afetado;
- valor_anterior: armazena os dados antes da alteração;
- valor_novo: armazena os dados após a alteração.
Após criar a tabela de auditoria, precisamos desenvolver a Function responsável por registrar os eventos.
CREATE OR REPLACE FUNCTION auditoria_clientes_func()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO auditoria_clientes ( operacao, id_cliente, valor_anterior, valor_novo )
VALUES ( TG_OP, NEW.id_cliente, ROW(OLD.*)::TEXT, ROW(NEW.*)::TEXT );
RETURN NEW;
END;
$$;
Essa Function será executada automaticamente pela Trigger sempre que ocorrer uma alteração na tabela monitorada.
Vamos analisar os elementos mais importantes:
- TG_OP: é uma variável especial do PostgreSQL que informa qual operação disparou a Trigger (INSERT, UPDATE ou DELETE);
- NEW: representa os novos dados do registro;
- OLD: representa os dados anteriores do registro;
- *ROW(OLD.)::TEXT: converte o registro antigo para texto;
- *ROW(NEW.)::TEXT: converte o registro atualizado para texto.
Essas variáveis especiais permitem registrar exatamente o que foi alterado em cada operação. O próximo passo consiste em associar essa Function à tabela através da Trigger a seguir:
CREATE TRIGGER trg_auditoria_clientes
AFTER INSERT OR UPDATE
ON clientes
FOR EACH ROW
EXECUTE FUNCTION auditoria_clientes_func();
Nesse caso:
- AFTER: indica que a Trigger será executada após a conclusão da operação;
- INSERT OR UPDATE: define quais eventos serão monitorados;
- ON clientes: associa o gatilho à tabela clientes;
- FOR EACH ROW: determina que cada registro afetado será auditado individualmente.
Podemos representar o fluxo de execução da seguinte forma:
UPDATE ou INSERT
↓
Tabela clientes
↓
Trigger AFTER
↓
auditoria_clientes_func()
↓
Registro criado em auditoria_clientes
Suponha que o seguinte comando seja executado:
UPDATE clientes
SET salario = 7000
WHERE id_cliente = 2;
O PostgreSQL realizará a atualização normalmente e em seguida, a Trigger será acionada e registrará automaticamente informações semelhantes às seguintes:
| operação | id_cliente | valor_anterior | valor_novo |
| -------- | ---------- | -------------- |--------------------|
| UPDATE | 2 | (dados antigos)| (dados atualizados)|
Dessa forma, cada alteração realizada na tabela ficará registrada para futuras consultas e auditorias.
Esse tipo de mecanismo é amplamente utilizado em sistemas corporativos que exigem rastreabilidade das informações, incluindo sistemas de gestão empresarial (ERP), instituições financeiras, hospitais, seguradoras, plataformas de comércio eletrônico e órgãos governamentais.
Por esse motivo, a auditoria baseada em Triggers é considerada uma das aplicações mais importantes e práticas da programação dentro do banco de dados.
Evoluindo a auditoria
O exemplo apresentado anteriormente foi desenvolvido com fins didáticos e demonstra os conceitos fundamentais envolvidos na construção de um mecanismo de auditoria utilizando Triggers.
Entretanto, em ambientes corporativos é comum que as rotinas de auditoria precisem tratar separadamente cada tipo de operação monitorada. Isso ocorre porque as variáveis especiais OLD e NEW nem sempre estão disponíveis ao mesmo tempo.
A seguir é apresentado um resumo do uso das variáveis especiais OLD e NEW.
| operação | OLD | NEW |
| -------- | ---------- | ---------- |
| INSERT | Não existe | Disponível |
| UPDATE | Disponível | Disponível |
| DELETE | Disponível | Não existe |
Durante uma operação de INSERT, por exemplo, não existem valores anteriores para serem registrados, pois o registro está sendo criado naquele momento. Por esse motivo, apenas a variável NEW estará disponível.
Já em uma operação de DELETE, ocorre exatamente o oposto: os dados antigos continuam acessíveis através da variável OLD, mas não existe um novo registro após a exclusão.
Nas operações de UPDATE, ambas as variáveis estão disponíveis simultaneamente, permitindo comparar os valores antes e depois da alteração.
Em sistemas de auditoria mais sofisticados, é comum utilizar a variável especial TG_OP para identificar qual operação disparou a Trigger e, a partir disso, registrar corretamente os dados disponíveis em cada situação.
Dessa forma, torna-se possível criar mecanismos de auditoria capazes de monitorar inserções, atualizações e exclusões de forma completa, preservando um histórico detalhado de todas as alterações realizadas no banco de dados.
Boas práticas na programação de banco de dados
Ao longo deste capítulo, estudamos diversos recursos que permitem implementar lógica diretamente no banco de dados, incluindo Procedures, Functions, Triggers, cursores, estruturas condicionais e mecanismos de tratamento de exceções.
Embora essas ferramentas sejam extremamente poderosas, seu uso exige planejamento e disciplina. Assim como acontece em qualquer linguagem de programação, a utilização inadequada desses recursos pode tornar o sistema mais difícil de compreender, manter e evoluir.
Por esse motivo, algumas boas práticas merecem atenção especial.
1. Evite a “Síndrome do Martelo”
Existe um ditado bastante conhecido na área de tecnologia:
Quando tudo o que você possui é um martelo, todos os problemas parecem pregos.
O fato de conhecer recursos como cursores, loops e triggers não significa que eles sejam a melhor solução para todos os cenários.
Em bancos de dados relacionais, operações baseadas em conjuntos (set-based operations) geralmente são mais eficientes do que o processamento linha por linha.
Por exemplo, em vez de percorrer milhares de registros utilizando um cursor para realizar uma atualização, muitas vezes uma simples instrução SQL resolve o problema de forma mais rápida e elegante. Sempre que possível, procure aproveitar a capacidade do SGBD de trabalhar com conjuntos de dados.
2. Tome cuidado com Triggers em cascata
As Triggers podem automatizar processos importantes, mas também podem criar dependências difíceis de identificar.
Dependendo da implementação, isso pode gerar ciclos de execução, aumento de consumo de recursos e comportamentos inesperados. Por esse motivo, recomenda-se manter as Triggers o mais simples possível, delegando lógicas mais complexas para Procedures ou para a própria aplicação quando apropriado.
3. Documente suas rotinas
Procedures, Functions e Triggers costumam permanecer em produção durante anos.
Sem documentação adequada, novos desenvolvedores podem ter dificuldades para compreender por que determinada rotina existe, quais regras implementa ou quais tabelas afeta.
Uma boa prática consiste em documentar:
- objetivo da rotina;
- parâmetros utilizados;
- valores retornados;
- tabelas envolvidas;
- regras de negócio implementadas;
- data de criação e alterações relevantes.
Investir alguns minutos em documentação pode economizar horas de manutenção no futuro.
4. Mantenha as rotinas pequenas e especializadas
Procedures e Functions muito extensas tendem a se tornar difíceis de compreender e testar.
Sempre que possível:
- divida responsabilidades;
- crie rotinas com objetivos específicos;
- evite concentrar diversas regras diferentes em uma única Procedure.
Rotinas menores costumam ser mais reutilizáveis, mais fáceis de depurar e mais simples de manter.
5. Trate exceções adequadamente
O bloco EXCEPTION é uma ferramenta importante para lidar com situações inesperadas. Entretanto, exceções não devem substituir validações normais da aplicação ou do banco de dados.
Sempre que possível:
- valide informações previamente;
- utilize IF e CASE para cenários previsíveis;
- reserve exceções para situações realmente excepcionais.
Isso contribui para um código mais eficiente e mais fácil de compreender.
6. Utilize padrões de nomenclatura
A adoção de convenções de nomenclatura facilita significativamente a manutenção dos projetos.
Por exemplo:
fn_calcular_bonus
proc_cancelar_pedido
trg_auditoria_clientes
Independentemente do padrão escolhido, o mais importante é manter consistência em todo o ambiente.
7. Evite duplicar regras de negócio
Uma mesma regra implementada em diversos locais tende a gerar inconsistências.
Se uma validação existe simultaneamente:
- na aplicação;
- em uma Procedure;
- em uma Trigger;
qualquer alteração futura exigirá manutenção em múltiplos pontos do sistema.
Sempre que possível, procure centralizar as regras mais importantes para evitar duplicação desnecessária.
8. Teste em ambiente de homologação
Procedures e Triggers possuem acesso direto aos dados corporativos. Por esse motivo, qualquer alteração deve ser validada previamente em um ambiente de testes ou homologação.
Essa prática reduz significativamente o risco de falhas em produção e aumenta a confiabilidade das alterações implantadas.
9. Monitore o impacto na performance
Toda lógica executada dentro do banco de dados consome recursos computacionais. Procedures complexas, cursores mal utilizados e Triggers excessivamente pesadas podem afetar o desempenho geral do sistema.
Por esse motivo, é importante acompanhar métricas de execução, identificar gargalos e revisar periodicamente as rotinas implementadas.
10. Encontre o equilíbrio
A programação dentro do banco de dados é uma ferramenta extremamente valiosa, mas deve ser utilizada com equilíbrio. Procedures, Functions e Triggers podem simplificar processos, centralizar regras e aumentar a segurança dos dados. Entretanto, elas não devem assumir responsabilidades que pertencem naturalmente à camada de aplicação.
Em arquiteturas bem projetadas, banco de dados e aplicação trabalham em conjunto, cada um desempenhando o papel para o qual foi concebido. O objetivo não é transferir toda a lógica para o SGBD nem concentrar tudo na aplicação, mas encontrar um equilíbrio que produza soluções seguras, performáticas e fáceis de manter ao longo do tempo.
Considerações finais
Neste artigo avançamos significativamente em nossa jornada pela programação dentro do banco de dados. Aprendemos a utilizar variáveis, parâmetros de entrada e saída, estruturas condicionais, laços de repetição, cursores, tratamento de exceções, Triggers BEFORE e AFTER, além de construir um mecanismo completo de auditoria.
Com esses recursos passamos a trabalhar não apenas com armazenamento de dados, mas também com automação, validação e processamento diretamente no SGBD.
Ao dominar essas ferramentas, o desenvolvedor adquire a capacidade de construir soluções mais robustas, seguras e alinhadas às necessidades de ambientes corporativos de grande porte.
Com isso encerramos nossa jornada na programação em bancos de dados relacionais. A partir do próximo artigo poderemos avançar para tópicos relacionados à administração avançada, tais como CREATE USER, GRANT, REVOKE, roles e privilégios em ambientes profissionais.
Gostou desse resumo prático? Não esqueça de deixar suas palmas 👏, considere compartilhá-lo com outras pessoas que também estejam aprendendo banco de dados, deixe seus comentários se tiver alguma dúvida sobre os conceitos apresentados e continue acompanhando a série.
Desde o mais profundo do meu coração, espero que este conteúdo tenha contribuído para aprofundar seus conhecimentos sobre SQL e bancos de dados relacionais.
Nos vemos na próxima parte!
Para continuar lendo a matéria, clique neste link.
Referências bibliográficas consultadas
ALVES, William Pereira. Banco de Dados — Teoria e Desenvolvimento. São Paulo: Editora Érica, 2009. DATE, C. J. Introdução aos Sistemas de Banco de Dados. 8. ed. Rio de Janeiro: Campus, 2004. ELMASRI, R.; NAVATHE, S. Sistemas de Banco de Dados. São Paulo: Pearson/Addison Wesley, 2011. SILBERSCHATZ, Abraham; KORTH, Henry F.; SUDARSHAN, S. Sistema de Banco de Dados. São Paulo: Elsevier, 2012.
메타데이터
- post_id
- 20123d022bbf
- slug
- administração-de-banco-de-dados-parte-xxvii-20123d022bbf
- url
- https://medium.com/@kamuz01/administra%C3%A7%C3%A3o-de-banco-de-dados-parte-xxvii-20123d022bbf
- canonical_url
- https://medium.com/@kamuz01/administra%C3%A7%C3%A3o-de-banco-de-dados-parte-xxvii-20123d022bbf
- author_url
- https://medium.com/@kamuz01
- status
- ok
- fetched_at
- 2026-06-14 11:28:49