Erros no PostgreSQL podem rolar por vários motivos: violação de restrições (NOT NULL, UNIQUE, CHECK), erro de sintaxe, valores duplicados e por aí vai. Se tu não capturar e tratar esses erros, toda a transação externa pode ser revertida. Pra garantir operações de negócio estáveis, é importante tratar isso direito.
O PL/pgSQL tem um esquema massa de blocos BEGIN ... EXCEPTION ... END pra capturar e tratar erros em funções e procedures. Eles são parecidos com o try-catch do Python ou Java, mas tem suas particularidades no contexto das transações do PostgreSQL.
Detalhe importante:
Cada bloco BEGIN ... EXCEPTION ... END funciona tipo um “savepoint virtual”. Se rolar uma exceção — tudo que mudou dentro desse bloco é revertido automaticamente. Esse é o único jeito certo de fazer rollback parcial em funções e procedures no PL/pgSQL
Sintaxe do tratamento de erros com EXCEPTION
BEGIN
-- código principal
EXCEPTION
WHEN TIPO_DE_ERRO THEN
-- tratamento de erro específico
WHEN OUTRO_TIPO_DE_ERRO THEN
-- outro tratamento
WHEN OTHERS THEN
-- tratamento de todos os outros erros
END;
Exemplo em funções/procedures
DO $$
BEGIN
RAISE NOTICE 'Vai dar erro agora...';
PERFORM 1 / 0;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Divisão por zero capturada!';
END;
$$;
Exemplo: update com tratamento e rollback de erros
Imagina que tem uma tabela de pedidos:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
amount NUMERIC NOT NULL,
status TEXT NOT NULL
);
Vamos criar uma função que atualiza o status do pedido e, se der erro, não muda nada:
CREATE OR REPLACE FUNCTION update_order_status(order_id INT, new_status TEXT)
RETURNS VOID AS $$
BEGIN
BEGIN
UPDATE orders
SET status = new_status
WHERE id = order_id;
-- Simulando erro
IF new_status = 'FAIL' THEN
RAISE EXCEPTION 'Simulando erro!';
END IF;
RAISE NOTICE 'Status do pedido % atualizado', order_id;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Erro ao atualizar pedido %: %', order_id, SQLERRM;
-- Todas as alterações dentro do bloco vão ser revertidas automaticamente!
-- Se quiser, pode relançar o erro pra tratar fora
RAISE;
END;
END;
$$ LANGUAGE plpgsql;
Como isso funciona em procedures com controle explícito de transação
Nas procedures (CREATE PROCEDURE) tu pode usar COMMIT, ROLLBACK, SAVEPOINT, mas não pode fazer ROLLBACK TO SAVEPOINT. Se quiser reverter só uma parte das operações dentro da procedure, usa o mesmo esquema BEGIN ... EXCEPTION ... END:
CREATE OR REPLACE PROCEDURE pay_order(order_id INT, amount NUMERIC)
LANGUAGE plpgsql
AS $$
BEGIN
-- A procedure toda pode usar COMMIT/ROLLBACK, mas pra reverter só uma etapa:
BEGIN
UPDATE accounts
SET balance = balance - amount
WHERE id = (SELECT account_id FROM orders WHERE id = order_id);
-- erro
IF amount < 0 THEN
RAISE EXCEPTION 'Valor não pode ser negativo!';
END IF;
UPDATE orders SET status = 'PAID' WHERE id = order_id;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Erro ao processar pagamento do pedido %: %', order_id, SQLERRM;
-- alterações desse bloco vão ser revertidas automaticamente
END;
COMMIT; -- Só pode finalizar a transação explicitamente em procedures!
END;
$$;
Log de erros
Não basta só capturar os erros, é importante salvar eles pra analisar depois.
CREATE TABLE error_log (
id SERIAL PRIMARY KEY,
order_id INT,
error_message TEXT,
error_time TIMESTAMP DEFAULT now()
);
Na função ou procedure:
EXCEPTION
WHEN OTHERS THEN
INSERT INTO error_log (order_id, error_message)
VALUES (order_id, SQLERRM);
RAISE NOTICE 'Erro registrado no log: %', SQLERRM;
RAISE;
Restrições e detalhes importantes no PostgreSQL 17
Dentro de funções (CREATE FUNCTION ... ) não pode usar comandos de controle de transação (BEGIN, COMMIT, ROLLBACK, SAVEPOINT, ROLLBACK TO SAVEPOINT, RELEASE SAVEPOINT)! Todas as funções rodam inteiras dentro da transação externa.
Dentro de procedures (CREATE PROCEDURE ... ) tu pode usar SAVEPOINT, RELEASE SAVEPOINT, COMMIT, ROLLBACK. MAS: ROLLBACK TO SAVEPOINT — PROIBIDO! (Vai dar erro de sintaxe se tentar usar ROLLBACK TO SAVEPOINT numa procedure PL/pgSQL).
Rollback de "parte do código" dentro de funções e procedures é feito com blocos BEGIN ... EXCEPTION ... END. Se rolar erro, tudo dentro do bloco é revertido automaticamente e o código pode continuar.
Procedures (CREATE PROCEDURE) não podem ser chamadas dentro de função nem via SELECT — só com o comando CALL ... separado.
Como realmente fazer "rollback parcial" em PL/pgSQL?
O único jeito que funciona — usar tratamento de erro com blocos BEGIN ... EXCEPTION ... END. Esse bloco cria um savepoint automático, e se der erro, reverte só o que rolou dentro dele, sem mexer no resto da procedure/função.
Exemplo com EXCEPTION (jeito recomendado):
CREATE OR REPLACE PROCEDURE demo_savepoint()
LANGUAGE plpgsql
AS $$
BEGIN
-- Algum código
BEGIN
-- Se der erro aqui, não reverte a procedure toda,
-- só esse bloco!
INSERT INTO demo VALUES ('dados ruins'); -- pode dar erro
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Erro tratado, alterações dentro do bloco foram desfeitas';
END;
-- Aqui o código continua!
END;
$$;
Exemplo: carregando lote de dados sem perder tudo se der erro
CREATE OR REPLACE PROCEDURE load_big_batch()
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM import_table LOOP
BEGIN
INSERT INTO target_table (col1, col2)
VALUES (rec.col1, rec.col2);
EXCEPTION WHEN OTHERS THEN
INSERT INTO import_errors (err_msg)
VALUES ('Erro no registro: ' || rec.col1 || ': ' || SQLERRM);
-- alterações desse bloco foram revertidas!
END;
END LOOP;
COMMIT; -- só pode se a procedure rodar fora de uma transação externa explícita!
END;
$$;
-- Chamando a procedure
CALL load_big_batch();
Repara: se tu chamar essa procedure de um cliente que já começou uma transação (tipo Python com autocommit=False), rodar COMMIT ou SAVEPOINT dentro da procedure vai dar erro.
Dicas pra trabalhar com savepoints aninhados e EXCEPTION
- Não usa ROLLBACK TO SAVEPOINT em PL/pgSQL! Vai dar erro de sintaxe.
- Pra rollback parcial, sempre usa blocos aninhados BEGIN ... EXCEPTION ... END.
- Lembra que COMMIT e ROLLBACK dentro de procedures reiniciam a transação — só usa se a procedure rodar em modo autocommit!
- Registra os erros numa tabela separada pra não perder info das linhas problemáticas.
- Se a operação de negócio tem que ser 100% atômica (tudo-ou-nada) — faz como função, sem COMMIT/ROLLBACK dentro; se precisa tratar por etapas — faz como procedure.
Exemplo: importando por lotes com tratamento parcial
CREATE OR REPLACE PROCEDURE import_batch()
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM staging_table LOOP
BEGIN
INSERT INTO data_table (data)
VALUES (rec.data);
EXCEPTION
WHEN unique_violation THEN
INSERT INTO import_log (msg)
VALUES ('Duplicado: ' || rec.data);
WHEN OTHERS THEN
INSERT INTO import_log (msg)
VALUES ('Erro: ' || rec.data || ' — ' || SQLERRM);
END;
END LOOP;
END;
$$;
O principal pra lembrar no PostgreSQL 17:
Em procedures PL/pgSQL são permitidos SAVEPOINT, RELEASE SAVEPOINT, COMMIT, ROLLBACK, mas ROLLBACK TO SAVEPOINT — não pode.
"Rollback parcial" dentro de funções e procedures só rola com blocos aninhados BEGIN ... EXCEPTION ... END.
O melhor é controlar as transações por fora (com parâmetros de conexão e autocommit), e dentro das procedures — usar esses esquemas de tratamento de erro.
GO TO FULL VERSION