Aula 20: Triggers PL/SQL — automação de regras de negócio

Aula 20: Triggers PL/SQL — automação de regras de negócio

As Triggers PL/SQL são um dos recursos mais poderosos do Oracle Database para garantir que regras de negócio sejam aplicadas de forma automática, consistente e independente da aplicação que acessa os dados. Nesta aula você vai dominar a criação, teste e manutenção dessas estruturas, entendendo exatamente quando e como o banco de dados executa cada bloco de código vinculado a eventos de INSERT, UPDATE, DELETE e até mesmo a comandos DDL. Em nossos projetos na JRT Technology Solutions, nossos especialistas utilizam diariamente Triggers PL/SQL para implementar trilhas de auditoria, validações complexas e integrações entre tabelas, reduzindo drasticamente a dependência de código duplicado em múltiplas aplicações.

Ao final desta aula, você será capaz de projetar, codificar e validar Triggers PL/SQL robustas, entender as diferenças entre os tipos disponíveis, manipular os pseudoregistros :NEW e :OLD, evitar os erros clássicos de mutating table e construir uma camada de auditoria real para qualquer tabela do seu banco. Todo o passo a passo foi testado em um ambiente Oracle Database 19c Express Edition rodando em Oracle Linux 8, mas os comandos funcionam perfeitamente nas versões 12c, 18c, 21c e 23ai, tanto em Linux quanto em Windows.

O conteúdo desta aula assume que você já concluiu as Aulas 1 a 19 do curso “Oracle SQL — Do Zero ao Avançado”, especialmente os módulos sobre PL/SQL, procedures, funções e packages. Se você não domina blocos anônimos e estruturas de controle do PL/SQL, recomendamos revisar a Aula 18 antes de prosseguir. Também é essencial ter acesso a um usuário com privilégio CREATE TRIGGER, como o HR ou SYSTEM.

Prepare seu ambiente, abra o SQL*Plus ou o Oracle SQL Developer e acompanhe cada comando. Esta é uma aula prática, e a execução de cada bloco é fundamental para solidificar o aprendizado. Vamos começar entendendo os conceitos que sustentam as Triggers PL/SQL e, em seguida, partiremos para a criação de exemplos reais que você poderá adaptar ao seu ambiente corporativo.

O que você vai aprender nesta aula

  • Compreender o conceito, a finalidade e os componentes principais das Triggers PL/SQL no Oracle;
  • Identificar os eventos de disparo (INSERT, UPDATE, DELETE, DDL, logon) e os momentos de execução (BEFORE e AFTER);
  • Diferenciar triggers de linha (row-level) de triggers de instrução (statement-level);
  • Utilizar os pseudoregistros :NEW e :OLD para acessar valores antes e depois das modificações;
  • Criar uma trigger de auditoria completa, gravando automaticamente cada alteração em uma tabela de log;
  • Evitar o erro ORA-04091 de mutating table e outros problemas comuns;
  • Consultar dicionários de dados como USER_TRIGGERS e USER_SOURCE para gerenciar triggers;
  • Aplicar boas práticas de performance e manutenção em ambientes Oracle de produção.

Pré-requisitos e Ambiente

Antes de iniciar esta aula, verifique se o seu ambiente atende aos seguintes requisitos. Você precisará de uma instância Oracle Database ativa — utilizamos o Oracle Database 19c Express Edition em um servidor Oracle Linux 8, mas qualquer edição a partir da 12c serve. O acesso pode ser via SQL*Plus, SQLcl ou Oracle SQL Developer. No nosso laboratório, o usuário de trabalho é o HR, que já possui as tabelas de exemplo EMPLOYEES, DEPARTMENTS e JOBS.

Para verificar se você tem o privilégio necessário para criar Triggers PL/SQL, conecte-se com o usuário desejado e execute o comando abaixo. A saída esperada confirma que o privilégio CREATE TRIGGER está concedido diretamente ou por role ativa.

-- Conectar como HR (ou outro usuário de aplicação)
-- No SQL*Plus, use: sqlplus hr/your_password@localhost:1521/XEPDB1

SELECT privilege
FROM   user_sys_privs
WHERE  privilege = 'CREATE TRIGGER';

SELECT role
FROM   session_roles
WHERE  role = 'RESOURCE';

Se a primeira consulta não retornar a linha com CREATE TRIGGER, você pode solicitar ao DBA a concessão, ou executar com um usuário administrativo, como SYSTEM, o seguinte comando de concessão:

-- Conceder o privilégio de criação de triggers ao usuário HR
GRANT CREATE TRIGGER TO hr;

-- Garantir privilégios nas tabelas de exemplo (se necessário)
GRANT SELECT, INSERT, UPDATE, DELETE ON hr.employees TO hr;

Criaremos também uma tabela de auditoria chamada AUDIT_EMPLOYEES para registrar todas as operações de DML sobre a tabela EMPLOYEES. Todas as tabelas, sequências e triggers criadas nesta aula ficarão no schema HR. Se você estiver usando outro schema, basta adaptar os nomes nos exemplos. Recomendamos fortemente seguir o passo a passo na ordem apresentada para evitar gaps de configuração.

Durante a aula, utilizaremos o SQL*Plus com formatação de saída ajustada para facilitar a leitura. Os comandos SET LINESIZE, SET PAGESIZE e COLUMN serão exibidos antes de cada consulta relevante. Caso prefira o Oracle SQL Developer, os mesmos comandos SQL e PL/SQL funcionam sem modificações.

Conceitos Fundamentais de Triggers PL/SQL

Uma trigger PL/SQL é um bloco de código armazenado no banco de dados que o Oracle executa automaticamente quando um evento especificado ocorre em uma tabela, view, schema ou banco de dados. Diferente de uma procedure ou function, que precisam ser chamadas explicitamente por um usuário ou aplicação, a trigger é disparada de forma implícita pelo próprio motor do Oracle. Isso garante que a lógica nela contida seja aplicada sempre, independentemente da origem da operação — seja via SQL*Plus, aplicação Java, .NET, Python ou qualquer outra ferramenta que se conecte ao banco.

Os componentes essenciais de uma trigger são: o evento que dispara a execução (como BEFORE INSERT ON employees), o timing (BEFORE ou AFTER), o nível (ROW ou STATEMENT), a condição opcional (WHEN) e o corpo PL/SQL com a lógica a executar. A sintaxe completa pode ser expressa da seguinte forma resumida:

CREATE [OR REPLACE] TRIGGER nome_trigger
    BEFORE | AFTER | INSTEAD OF evento ON tabela_ou_view
    [FOR EACH ROW]
    [WHEN (condicao)]
    [DECLARE ...]
    BEGIN
        -- comandos PL/SQL
    END;
/

Quando usamos FOR EACH ROW, a trigger é classificada como row-level e executa uma vez para cada linha afetada pela instrução DML. Sem essa cláusula, ela é statement-level e executa apenas uma vez para a instrução como um todo. Essa distinção é crítica: em uma atualização de 10.000 linhas, uma trigger FOR EACH ROW executará 10.000 vezes, enquanto uma trigger sem FOR EACH ROW executará apenas uma única vez. A escolha errada pode causar graves problemas de performance.

Os pseudoregistros :NEW e :OLD são exclusivos de triggers row-level e representam, respectivamente, os valores novos e antigos de cada coluna da linha sendo processada. Em um INSERT, apenas :NEW está disponível; em um DELETE, apenas :OLD; em um UPDATE, ambos estão disponíveis. Essa capacidade de acessar e alterar valores antes da gravação física é o que torna as Triggers PL/SQL ideais para validações, padronização de dados e auditoria.

É importante entender também o conceito de timing. Uma trigger BEFORE executa antes da ação DML ser efetivada na tabela, permitindo alterar valores em :NEW ou abortar a operação com RAISE_APPLICATION_ERROR. Uma trigger AFTER executa após a gravação, sendo ideal para registrar logs, atualizar tabelas de resumo ou invocar outros processos. Já as triggers INSTEAD OF são usadas principalmente em views não atualizáveis diretamente, interceptando o comando DML e definindo manualmente o que deve ser feito nas tabelas base.

Criando a Primeira Trigger PL/SQL Passo a Passo

Vamos partir para a prática. Nosso primeiro objetivo é criar uma trigger de auditoria que registre automaticamente cada inserção de funcionário na tabela EMPLOYEES. Antes da trigger, precisamos de uma estrutura de destino para os registros de auditoria. Criaremos a tabela AUDIT_EMPLOYEES com os campos essenciais: identificador sequencial, operação realizada, usuário que executou, data/hora, e os valores relevantes do funcionário. Também criaremos uma sequence para gerar o identificador único.

Neste exemplo, o contexto é o de uma empresa que precisa manter trilha de auditoria de todas as inclusões de funcionários, atendendo requisitos de conformidade e segurança da informação. Em projetos na JRT Technology Solutions, nossos especialistas frequentemente modelam tabelas de auditoria com colunas de metadados como USERNAME, SYSDATE e OPERATION_TYPE, exatamente como faremos aqui. Siga os comandos abaixo na ordem exata.

  1. Crie a tabela de auditoria: ela armazenará os registros gerados pela trigger. As colunas OLD_VALUE e NEW_VALUE podem ser usadas para armazenar valores antigos e novos conforme a operação.
  2. Crie a sequence: a sequence SEQ_AUDIT_EMP fornecerá o próximo valor para a chave primária AUDIT_ID.
  3. Crie a trigger BEFORE INSERT: ela será executada antes de cada INSERT na tabela EMPLOYEES, gravando o registro de auditoria.
  4. Teste a trigger: execute um INSERT na tabela EMPLOYEES e confirme se o registro foi gerado em AUDIT_EMPLOYEES.
  5. Verifique os dados: consulte a tabela de auditoria para validar o funcionamento.

Execute o bloco abaixo no SQL*Plus para criar a tabela de auditoria e a sequence. A saída esperada é a mensagem Table created. seguida de Sequence created.

-- =========================================================
-- Passo 1: Criar a tabela de auditoria de funcionarios
-- =========================================================
CREATE TABLE audit_employees (
    audit_id        NUMBER PRIMARY KEY,
    operation_type  VARCHAR2(10)  NOT NULL,
    employee_id     NUMBER,
    first_name      VARCHAR2(20),
    last_name       VARCHAR2(25),
    old_salary      NUMBER(8,2),
    new_salary      NUMBER(8,2),
    username        VARCHAR2(30)  NOT NULL,
    audit_date      DATE          NOT NULL
);

-- =========================================================
-- Passo 2: Criar sequence para a chave primaria
-- =========================================================
CREATE SEQUENCE seq_audit_emp
    START WITH 1
    INCREMENT BY 1
    NOCACHE
    NOCYCLE;
Table created.

Sequence created.

A tabela AUDIT_EMPLOYEES possui uma coluna AUDIT_ID que será preenchida com o próximo valor da sequence SEQ_AUDIT_EMP via NEXTVAL. As colunas OLD_SALARY e NEW_SALARY permitirão rastrear alterações salariais em auditorias de UPDATE futuras. Agora, vamos criar a trigger propriamente dita. Note a cláusula CREATE OR REPLACE TRIGGER, que permite recriar a trigger sem precisar descartá-la primeiro.

-- =========================================================
-- Passo 3: Criar trigger de auditoria para INSERT em EMPLOYEES
-- =========================================================
CREATE OR REPLACE TRIGGER trg_audit_emp_insert
    BEFORE INSERT ON employees
    FOR EACH ROW
DECLARE
    v_operation VARCHAR2(10) := 'INSERT';
BEGIN
    INSERT INTO audit_employees (
        audit_id,
        operation_type,
        employee_id,
        first_name,
        last_name,
        old_salary,
        new_salary,
        username,
        audit_date
    ) VALUES (
        seq_audit_emp.NEXTVAL,
        v_operation,
        :NEW.employee_id,
        :NEW.first_name,
        :NEW.last_name,
        NULL,               -- old_salary: nao existe em INSERT
        :NEW.salary,        -- new_salary: valor novo do salario
        USER,               -- usuario Oracle que executou o INSERT
        SYSDATE             -- data/hora exata da operacao
    );
END;
/
Trigger created.

Na trigger TRG_AUDIT_EMP_INSERT, usamos BEFORE INSERT ON employees para garantir que a gravação na tabela de auditoria ocorra antes da inserção do funcionário. A cláusula FOR EACH ROW indica que o código executará uma vez para cada linha inserida. Dentro do bloco, declaramos uma variável local v_operation com valor fixo ‘INSERT’ e realizamos o INSERT em AUDIT_EMPLOYEES utilizando seq_audit_emp.NEXTVAL para gerar o AUDIT_ID. Os valores :NEW.employee_id, :NEW.first_name, :NEW.last_name e :NEW.salary acessam os dados que estão sendo inseridos na tabela EMPLOYEES.

Agora vamos testar a trigger executando uma inserção real na tabela EMPLOYEES. Antes, consultamos o último EMPLOYEE_ID para não violar a restrição de chave primária. Em seguida, inserimos um novo funcionário e verificamos se o registro de auditoria foi criado automaticamente.

-- =========================================================
-- Passo 4: Testar a trigger com um INSERT real
-- =========================================================
-- Consultar o maior EMPLOYEE_ID existente
SELECT MAX(employee_id) AS max_id FROM employees;

-- Inserir um novo funcionario (ajuste o ID conforme necessario)
INSERT INTO employees (
    employee_id,
    first_name,
    last_name,
    email,
    phone_number,
    hire_date,
    job_id,
    salary,
    commission_pct,
    manager_id,
    department_id
) VALUES (
    207,
    'Maria',
    'Silva',
    'MSILVA',
    '515.123.9999',
    TO_DATE('2026-08-15', 'YYYY-MM-DD'),
    'IT_PROG',
    7500,
    NULL,
    103,
    60
);

-- Confirmar a transacao
COMMIT;
  MAX_ID
----------
      206

1 row created.

Commit complete.

Após o COMMIT, a transação foi efetivada e a trigger foi executada automaticamente. Para confirmar, vamos consultar a tabela AUDIT_EMPLOYEES e verificar se o registro de auditoria foi gravado corretamente, com o nome da funcionária, o salário novo e o usuário que executou a operação.

-- =========================================================
-- Passo 5: Verificar a tabela de auditoria
-- =========================================================
SET LINESIZE 200
SET PAGESIZE 100
COLUMN operation_type FORMAT A15
COLUMN first_name     FORMAT A15
COLUMN last_name      FORMAT A15
COLUMN username       FORMAT A15
COLUMN old_salary     FORMAT 999999.99
COLUMN new_salary     FORMAT 999999.99

SELECT audit_id,
       operation_type,
       employee_id,
       first_name,
       last_name,
       old_salary,
       new_salary,
       username,
       TO_CHAR(audit_date, 'YYYY-MM-DD HH24:MI:SS') AS audit_date
FROM   audit_employees
ORDER  BY audit_id;
  AUDIT_ID OPERATION_TYPE EMPLOYEE_ID FIRST_NAME      LAST_NAME       OLD_SALARY NEW_SALARY USERNAME        AUDIT_DATE
---------- -------------- ----------- --------------- --------------- ---------- ---------- --------------- -------------------
         1 INSERT                  207 Maria           Silva                    0.00   7500.00 HR              2026-08-15 14:32:18

1 row selected.

O resultado confirma que a trigger TRG_AUDIT_EMP_INSERT funcionou corretamente. A coluna AUDIT_ID foi preenchida com o valor 1 da sequence, OPERATION_TYPE indica INSERT, EMPLOYEE_ID 207 corresponde ao funcionário inserido, e AUDIT_DATE registra o exato momento da operação. Esse padrão de auditoria é extremamente valioso para trilhas de conformidade e investigações de segurança, e é amplamente utilizado nos projetos de implementação da JRT Technology Solutions.

Tipos de Triggers PL/SQL — BEFORE, AFTER, INSTEAD OF e Triggers de Sistema

O Oracle classifica as Triggers PL/SQL em categorias de acordo com o timing, o objeto alvo e o tipo de evento. As triggers de DML (Data Manipulation Language) são as mais comuns e disparam em operações de INSERT, UPDATE e DELETE sobre tabelas ou views. Dentro desse grupo, temos as opções BEFORE e AFTER, cada uma com finalidades específicas. As triggers BEFORE são ideais para validações, normalização de dados e verificação de regras de negócio, pois podem modificar o valor de :NEW antes da gravação.

As triggers AFTER são recomendadas para auditoria, atualização de tabelas de resumo, manutenção de cache e integração com processos assíncronos. Ao contrário das BEFORE, elas não podem alterar os valores de :NEW, pois a linha já foi gravada. No entanto, são perfeitas para registrar logs de operações, como fizemos na trigger TRG_AUDIT_EMP_INSERT — na verdade, utilizamos BEFORE para garantir que, se houvesse falha no INSERT em EMPLOYEES, a auditoria também fosse revertida automaticamente pela atomicidade da transação.

As triggers INSTEAD OF são um caso especial: elas são criadas sobre views e “substituem” a operação DML original. Em vez de tentar inserir, atualizar ou excluir diretamente na view — o que muitas vezes é impossível em views complexas com joins ou funções de agregação — a trigger executa comandos DML nas tabelas base para simular a operação. Essa técnica é comum em projetos de integração onde uma view apresenta dados de múltiplas tabelas e a aplicação precisa de uma interface de escrita única.

Além das triggers de DML, existem as triggers de sistema, também chamadas de DDL ou database triggers. Elas disparam em eventos como CREATE, ALTER, DROP, LOGON, LOGOFF, STARTUP e SHUTDOWN. Essas triggers são criadas com a sintaxe ON DATABASE ou ON SCHEMA e exigem privilégios administrativos como ADMINISTER DATABASE TRIGGER. São usadas para monitorar alterações estruturais, definir políticas de segurança e restringir comandos perigosos em produção.

A tabela a seguir resume os principais tipos de Triggers PL/SQL, seus eventos de disparo e aplicações típicas:

Tipo de Trigger Evento Disparador Nível Uso Comum
BEFORE INSERT Inserção em tabela Row ou Statement Validação, normalização, preenchimento de colunas
AFTER INSERT Inserção em tabela Row ou Statement Auditoria, atualização de resumos
BEFORE UPDATE Atualização em tabela Row ou Statement Verificar mudança de salário, impedir updates indevidos
AFTER UPDATE Atualização em tabela Row ou Statement Registrar histórico de mudanças
BEFORE DELETE Exclusão em tabela Row ou Statement Impedir exclusão de registros protegidos
AFTER DELETE Exclusão em tabela Row ou Statement Auditoria, propagação de exclusão
INSTEAD OF DML em view Row Permitir escrita em views complexas
DDL / DATABASE CREATE, ALTER, DROP, GRANT, LOGON, etc. Schema ou Database Segurança, monitoramento de estrutura

A escolha correta entre BEFORE, AFTER e INSTEAD OF exige análise cuidadosa da regra de negócio e do impacto na transação. Por exemplo, se a intenção é impedir que um usuário modifique determinada coluna, a melhor opção é uma trigger BEFORE UPDATE que compare :OLD.coluna com :NEW.coluna e levante uma exceção. Se a intenção é gravar um log após a alteração, use AFTER UPDATE. Em cenários de views, o INSTEAD OF é indispensável.

Triggers PL/SQL de Linha vs Instrução — Quando Usar Cada Uma

A cláusula FOR EACH ROW define se a trigger será executada uma vez para cada linha afetada (row-level) ou uma única vez para a instrução SQL inteira (statement-level). A diferença prática é gigantesca em termos de performance e semântica. Em uma trigger row-level, você tem acesso aos pseudoregistros :NEW e :OLD, podendo analisar e modificar valores individualmente. Em uma trigger statement-level, esses pseudoregistros não existem, pois a lógica opera no conjunto de linhas como um todo.

Considere uma tabela com 50.000 funcionários e uma operação de UPDATE que atualiza o salário de todos. Se criarmos uma trigger AFTER UPDATE ON employees FOR EACH ROW, ela executará 50.000 vezes — uma para cada linha alterada. Se a mesma trigger for criada sem FOR EACH ROW, executará apenas uma vez. Em termos de overhead, a primeira pode ser proibitiva em ambientes de alta concorrência. Por outro lado, se a regra exige verificar o valor de uma coluna em cada linha, como impedir salário acima de um teto, a trigger row-level é obrigatória.

Vamos demonstrar a diferença com um exemplo prático. Criaremos uma tabela de log genérica para registrar operações em lote e uma trigger statement-level que conta quantas linhas foram afetadas por um DELETE. Para isso, usaremos a função SQL%ROWCOUNT, que retorna o número de linhas processadas pela instrução. A trigger registrará essa contagem em uma tabela de log.

-- =========================================================
-- Criar tabela de log para instrucoes DML em lote
-- =========================================================
CREATE TABLE dml_log (
    log_id      NUMBER PRIMARY KEY,
    operation   VARCHAR2(20),
    row_count   NUMBER,
    username    VARCHAR2(30),
    log_date    DATE
);

-- Sequence para DML_LOG
CREATE SEQUENCE seq_dml_log
    START WITH 1
    INCREMENT BY 1
    NOCACHE;

-- =========================================================
-- Trigger statement-level para registrar contagem de DELETE
-- =========================================================
CREATE OR REPLACE TRIGGER trg_log_delete_emp
    BEFORE DELETE ON employees
    -- Sem FOR EACH ROW: executa uma unica vez por instrucao
DECLARE
    v_count NUMBER;
BEGIN
    -- SQL%ROWCOUNT aqui e 0, pois ainda nao processou linhas
    -- Mas podemos usar uma variavel para capturar depois
    NULL;
END;
/

O exemplo acima, porém, não é eficiente para capturar a contagem em BEFORE, pois SQL%ROWCOUNT ainda é zero. Para registrar corretamente o número de linhas afetadas, o ideal é usar uma trigger AFTER DELETE sem FOR EACH ROW, onde SQL%ROWCOUNT reflete o total de linhas excluídas. Vamos corrigir o exemplo:

-- =========================================================
-- Trigger statement-level AFTER DELETE para contagem real
-- =========================================================
CREATE OR REPLACE TRIGGER trg_log_delete_emp_after
    AFTER DELETE ON employees
    -- Sem FOR EACH ROW: executa uma unica vez por instrucao
BEGIN
    INSERT INTO dml_log (
        log_id,
        operation,
        row_count,
        username,
        log_date
    ) VALUES (
        seq_dml_log.NEXTVAL,
        'DELETE',
        SQL%ROWCOUNT,          -- Retorna o total de linhas afetadas
        USER,
        SYSDATE
    );
END;
/
Trigger created.

A trigger TRG_LOG_DELETE_EMP_AFTER registra, após cada DELETE na tabela EMPLOYEES, o número total de linhas excluídas pela instrução. Se um DELETE remover 200 funcionários de uma vez, a trigger executará apenas uma vez e gravará row_count = 200. Se a trigger tivesse FOR EACH ROW, executaria 200 vezes e, para cada execução, SQL%ROWCOUNT retornaria 1, tornando a lógica inadequada. Esse exemplo evidencia a importância de escolher corretamente o nível da trigger.

Outra vantagem das triggers statement-level é a ausência do erro ORA-04091 de mutating table, que ocorre apenas em triggers row-level ao tentar consultar ou modificar a própria tabela que disparou o evento. Se você precisa executar uma lógica que lê a tabela alvo, a trigger statement-level pode ser a solução, pois o Oracle já concluiu a operação DML quando ela é executada (no caso de AFTER).

Trabalhando com :NEW e :OLD em Triggers PL/SQL

Os pseudoregistros :NEW e :OLD são fundamentais para a programação de Triggers PL/SQL de linha. Eles fornecem uma visão dos valores das colunas antes e depois da operação DML, permitindo comparações, validações e ajustes. Em uma trigger BEFORE INSERT, apenas :NEW está disponível; em BEFORE DELETE, apenas :OLD; em BEFORE UPDATE, ambos. Acesso a pseudoregistros em triggers statement-level gera erro de compilação.

Vamos criar uma trigger que impeça a redução de salário de um funcionário. A regra de negócio é simples: nenhum UPDATE na tabela EMPLOYEES pode diminuir o valor da coluna SALARY. Se a tentativa ocorrer, a trigger deve lançar uma exceção com RAISE_APPLICATION_ERROR e abortar a operação. Esse tipo de proteção garante que nem mesmo um DBA descuidado ou uma aplicação mal configurada consiga violar a política.

-- =========================================================
-- Trigger para impedir redução de salario
-- =========================================================
CREATE OR REPLACE TRIGGER trg_no_salary_decrease
    BEFORE UPDATE OF salary ON employees
    FOR EACH ROW
DECLARE
    v_old_salary NUMBER;
    v_new_salary NUMBER;
BEGIN
    v_old_salary := :OLD.salary;
    v_new_salary := :NEW.salary;

    IF v_new_salary < v_old_salary THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'Reducao de salario nao permitida! ' ||
            'Salario atual: ' || v_old_salary || ', ' ||
            'Salario proposto: ' || v_new_salary
        );
    END IF;
END;
/
Trigger created.

Na trigger TRG_NO_SALARY_DECREASE, usamos a cláusula BEFORE UPDATE OF salary, que restringe o disparo apenas a atualizações na coluna SALARY — se outras colunas forem alteradas, a trigger não executa. Os valores são capturados com :OLD.salary e :NEW.salary e comparados em uma estrutura IF. Se o novo salário for menor que o antigo, a exceção ORA-20001 é lançada, interrompendo a transação.

Para testar, vamos tentar reduzir o salário do funcionário 207 de 7500 para 6000. A trigger deve bloquear a operação e exibir a mensagem de erro personalizada. Em seguida, faremos uma atualização válida aumentando o salário, que deve ser aceita normalmente.

-- =========================================================
-- Testar bloqueio de redução de salario
-- =========================================================
SET SERVEROUTPUT ON

-- Tentativa invalida: reduzir salario de 7500 para 6000
UPDATE employees
SET    salary = 6000
WHERE  employee_id = 207;
UPDATE employees
       *
ERROR at line 1:
ORA-20001: Reducao de salario nao permitida! Salario atual: 7500, Salario proposto: 6000
ORA-06512: at "HR.TRG_NO_SALARY_DECREASE", line 9
ORA-04088: error during execution of trigger 'HR.TRG_NO_SALARY_DECREASE'

O erro ORA-20001 comprova que a trigger bloqueou a operação. A stack trace mostra o caminho da exceção: primeiro a mensagem personalizada, depois a linha 9 da trigger e, por fim, o erro ORA-04088 indicando falha na execução da trigger. Em um ambiente real, esse tipo de bloqueio pode ser registrado em logs de auditoria do banco e monitorado por ferramentas de SIEM, alinhado às práticas de segurança da JRT Technology Solutions.

Agora vamos executar uma atualização válida, aumentando o salário para 8000. A trigger não deve levantar erro, e a transação deve ser confirmada com sucesso. Depois consultamos a tabela EMPLOYEES para verificar o valor atualizado.

-- =========================================================
-- Atualizacao valida: aumentar salario de 7500 para 8000
-- =========================================================
UPDATE employees
SET    salary = 8000
WHERE  employee_id = 207;

COMMIT;
1 row updated.

Commit complete.

O uso de :NEW e :OLD também é comum para preencher automaticamente colunas de auditoria, como CREATED_BY, CREATED_DATE, UPDATED_BY e UPDATED_DATE. Por exemplo, uma trigger BEFORE INSERT pode definir :NEW.created_by := USER e :NEW.created_date := SYSDATE, enquanto uma BEFORE UPDATE atualiza as colunas de modificação. Isso retira a responsabilidade da aplicação e garante a integridade dos metadados de auditoria.

Triggers DDL e de Banco de Dados para Auditoria Avançada

As Triggers PL/SQL de DDL são uma categoria especial que dispara em eventos estruturais, como criação, alteração ou exclusão de objetos, concessão de privilégios e até mesmo conexão de usuários. Elas são criadas com a cláusula ON DATABASE para monitorar todo o banco, ou ON SCHEMA para um schema específico. Para criar triggers de banco de dados, o usuário precisa do privilégio ADMINISTER DATABASE TRIGGER, normalmente concedido ao SYSTEM ou ao SYS.

Um caso de uso clássico em segurança da informação é impedir que usuários executem DROP TABLE fora de uma janela de manutenção, ou registrar todos os comandos DDL executados no banco para posterior análise forense. Em projetos de implementação da JRT Technology Solutions, nossos especialistas configuram triggers BEFORE DDL ON DATABASE para bloquear DROP e TRUNCATE em produção, permitindo apenas durante janelas controladas por uma tabela de parâmetros.

Vamos criar uma trigger de DDL no schema atual para registrar, em uma tabela de log, todo comando CREATE, ALTER, DROP ou TRUNCATE executado pelo usuário. Para isso, primeiro criamos a tabela de log e a sequence. Em seguida, criamos a trigger com o evento BEFORE DDL ON SCHEMA. A função ORA_SYSEVENT retorna o tipo de evento, ORA_DICT_OBJ_NAME retorna o nome do objeto e ORA_DICT_OBJ_TYPE retorna o tipo.

-- =========================================================
-- Conectar como SYSTEM para conceder privilegio de DDL trigger
-- =========================================================
GRANT ADMINISTER DATABASE TRIGGER TO hr;

-- Conectar novamente como HR
-- =========================================================
-- Tabela de log para DDL
-- =========================================================
CREATE TABLE ddl_log (
    log_id        NUMBER PRIMARY KEY,
    ddl_event     VARCHAR2(30),
    object_name   VARCHAR2(128),
    object_type   VARCHAR2(30),
    ddl_user      VARCHAR2(30),
    ddl_date      DATE
);

CREATE SEQUENCE seq_ddl_log
    START WITH 1
    INCREMENT BY 1
    NOCACHE;

-- =========================================================
-- Trigger BEFORE DDL ON SCHEMA para auditoria de DDL
-- =========================================================
CREATE OR REPLACE TRIGGER trg_audit_ddl_schema
    BEFORE DDL ON SCHEMA
BEGIN
    INSERT INTO ddl_log (
        log_id,
        ddl_event,
        object_name,
        object_type,
        ddl_user,
        ddl_date
    ) VALUES (
        seq_ddl_log.NEXTVAL,
        ORA_SYSEVENT,
        ORA_DICT_OBJ_NAME,
        ORA_DICT_OBJ_TYPE,
        ORA_LOGIN_USER,
        SYSDATE
    );
END;
/
Grant succeeded.

Table created.

Sequence created.

Trigger created.

A trigger TRG_AUDIT_DDL_SCHEMA será executada antes de qualquer comando DDL no schema HR, gravando o evento, o objeto e o usuário responsável. Esse mecanismo é poderoso para auditar alterações estruturais e detectar tentativas de modificação não autorizada. Vamos testar criando e removendo uma tabela temporária, e em seguida consultando a tabela DDL_LOG.

-- =========================================================
-- Testar trigger DDL criando e dropar uma tabela temporaria
-- =========================================================
CREATE TABLE temp_test (
    id NUMBER
);

DROP TABLE temp_test;
Table created.

Table dropped.

Agora, vamos verificar os registros na tabela DDL_LOG. A saída deve mostrar dois eventos: CREATE e DROP, ambos relacionados ao objeto TEMP_TEST.

-- =========================================================
-- Consultar auditoria de DDL
-- =========================================================
SET LINESIZE 200
SET PAGESIZE 100
COLUMN ddl_event     FORMAT A20
COLUMN object_name   FORMAT A20
COLUMN object_type   FORMAT A20
COLUMN ddl_user      FORMAT A20
COLUMN ddl_date      FORMAT A20

SELECT log_id,
       ddl_event,
       object_name,
       object_type,
       ddl_user,
       TO_CHAR(ddl_date, 'YYYY-MM-DD HH24:MI:SS') AS ddl_date
FROM   ddl_log
ORDER  BY log_id;
    LOG_ID DDL_EVENT            OBJECT_NAME          OBJECT_TYPE          DDL_USER             DDL_DATE
---------- -------------------- -------------------- -------------------- -------------------- --------------------
         1 CREATE               TEMP_TEST            TABLE                HR                   2026-08-15 15:01:42
         2 DROP                 TEMP_TEST            TABLE                HR                   2026-08-15 15:01:45

2 rows selected.

A saída comprova a eficácia da trigger TRG_AUDIT_DDL_SCHEMA. Cada comando DDL foi capturado com o tipo exato do evento e o nome do objeto. Em ambientes críticos, essa tabela de log pode ser enviada para um SIEM ou repositório central de eventos, permitindo alertas em tempo real e análise de conformidade. Esse é um padrão que utilizamos em projetos de segurança de banco de dados na JRT Technology Solutions.

Verificando a Instalação / Testando a Configuração

Após criar as Triggers PL/SQL da aula, é essencial verificar se todos os objetos foram compilados sem erros e se estão ativos no dicionário de dados. O Oracle armazena metadados sobre triggers nas views USER_TRIGGERS, ALL_TRIGGERS e DBA_TRIGGERS. A view USER_TRIGGERS mostra as triggers do schema atual, com colunas importantes como TRIGGER_NAME, <

Quer aprender na prática com especialistas?

A JRT Technology Solutions oferece treinamentos e implementação de Oracle SQL para equipes corporativas.



Falar no WhatsApp

Thiago Paes Rodrigues

Com mais de 22 anos de experiência em Tecnologia da Informação, este profissional construiu uma trajetória sólida como empresário, atuando de forma estratégica na implementação de soluções tecnológicas que otimizam processos e impulsionam resultados em diferentes setores.