Pular para conteúdo

Triggers e Stored Procedures

Introdução

De acordo com Silberschatz et al., os triggers são uma ferramenta importante em bancos de dados, permitindo a execução automática de ações em resposta a eventos, como inserções, atualizações ou exclusões. Esses gatilhos são úteis para garantir integridade referencial, além de automatizar tarefas como o controle de estoque ou o monitoramento de alterações em dados críticos. Funções, por outro lado, oferecem uma maneira de encapsular lógicas complexas em operações reutilizáveis, frequentemente integradas diretamente na execução das consultas SQL, aumentando a flexibilidade e a eficiência dos SGBDs modernos .

Objetivo

Este trabalho tem como objetivo explorar as funcionalidades dos triggers e functions em sistemas de banco de dados relacionais, focando em como essas ferramentas podem ser utilizadas para automatizar processos e garantir a consistência dos dados. Além disso, será abordada a criação de funções externas em linguagens de programação como C e Java, destacando os benefícios e riscos associados à sua integração nos bancos de dados .

Exemplos de Triggers e Functions

Nesta seção, são apresentados exemplos de triggers e functions implementados em SQL, com descrições detalhadas de suas funcionalidades. Por exemplo, um trigger pode ser utilizado para garantir que, ao atualizar uma tabela de inventário, seja verificada a quantidade mínima de um item, disparando automaticamente uma ordem de reposição quando o nível estiver abaixo do mínimo. Já uma função pode ser definida para calcular o número de alunos matriculados em um curso específico, utilizando a linguagem C para otimizar o processamento.

Trigger: verificar_exclusividade_missao
-- Função que será chamada pelo trigger
CREATE OR REPLACE FUNCTION verificar_exclusividade_missao()
RETURNS TRIGGER AS $$
BEGIN
    -- Verifica se a missão já está em outra tabela de especialização
    IF EXISTS (SELECT 1 FROM CACA WHERE id_missao = NEW.id_missao)
        OR EXISTS (SELECT 1 FROM ENTREGA WHERE id_missao = NEW.id_missao) THEN
        RAISE EXCEPTION 'A missão já está associada a uma especialização diferente.';
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger para a tabela CACA
CREATE TRIGGER trigger_verificar_exclusividade_caca
BEFORE INSERT OR UPDATE ON CACA
FOR EACH ROW EXECUTE FUNCTION verificar_exclusividade_missao();

-- Trigger para a tabela ENTREGA
CREATE TRIGGER trigger_verificar_exclusividade_entrega
BEFORE INSERT OR UPDATE ON ENTREGA
FOR EACH ROW EXECUTE FUNCTION verificar_exclusividade_missao();
Trigger: ajustar_quantidade_monstros
-- Trigger Function
CREATE OR REPLACE FUNCTION ajustar_quantidade_monstros()
RETURNS TRIGGER AS $$
BEGIN
    -- Verifica se a missão é do tipo ENTREGA
    IF (SELECT tipo_missao FROM MISSAO WHERE id_missao = NEW.id_missao) = 'ENTREGA' THEN
        -- Se for do tipo ENTREGA, seta quantidade_monstros como NULL
        NEW.quantidade_monstros := NULL;
    ELSE
        -- Para outros tipos de missão, garante que quantidade_monstros não seja NULL
        IF NEW.quantidade_monstros IS NULL THEN
            RAISE EXCEPTION 'Quantidade de monstros não pode ser NULL para missões que não sejam do tipo ENTREGA.';
        END IF;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger
CREATE TRIGGER trigger_ajustar_quantidade_monstros
BEFORE INSERT OR UPDATE ON REGISTRO_DA_MISSAO
FOR EACH ROW EXECUTE FUNCTION ajustar_quantidade_monstros();
Trigger: atualizar_vida
CREATE OR REPLACE FUNCTION atualizar_vida() RETURNS TRIGGER AS $$
BEGIN
    IF NEW.saude > NEW.nivel * 100 THEN
        NEW.saude := NEW.nivel * 100;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_atualizar_vida
BEFORE UPDATE ON personagem
FOR EACH ROW
EXECUTE FUNCTION atualizar_vida();
Trigger: atualizar_vida_alien
CREATE OR REPLACE FUNCTION atualizar_vida_alien() RETURNS TRIGGER AS $$
DECLARE
    vida_maxima INTEGER;
    nivel INTEGER;
BEGIN
    -- Buscar a vida máxima do alien com base no personagem
    SELECT a.saude INTO vida_maxima
    FROM STATUS_DO_ALIEN sda
    JOIN ALIEN a ON a.nome = sda.nome_alien
    WHERE a.nome = NEW.nome_alien AND sda.id_personagem = NEW.id_personagem;

    -- Buscar o nível do personagem
    SELECT p.nivel INTO nivel
    FROM PERSONAGEM p
    WHERE p.id_personagem = NEW.id_personagem;

    -- Se a nova saúde for maior que a vida máxima permitida pelo nível, ajusta
    IF NEW.saude > vida_maxima * nivel THEN
        NEW.saude := vida_maxima * nivel;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_atualizar_vida_alien
BEFORE INSERT OR UPDATE ON STATUS_DO_ALIEN
FOR EACH ROW
EXECUTE FUNCTION atualizar_vida_alien();
Trigger: atualizar_vida_monstro
CREATE OR REPLACE FUNCTION atualizar_vida_monstro() RETURNS TRIGGER AS $$
DECLARE
    vida_maxima INTEGER;
BEGIN
    SELECT m.saude INTO vida_maxima
    FROM MONSTRO m
    WHERE m.nome = NEW.nome_especie;

    IF NEW.saude_atual > vida_maxima THEN
        NEW.saude_atual := vida_maxima;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_atualizar_vida_monstro
BEFORE INSERT OR UPDATE ON INSTANCIA_MONSTRO
FOR EACH ROW
EXECUTE FUNCTION atualizar_vida_monstro();
Trigger: verifica_arma_inventario
CREATE OR REPLACE FUNCTION verifica_arma_inventario() RETURNS TRIGGER AS $$
BEGIN
    PERFORM 1 FROM INVENTARIO WHERE nome_item = OLD.nome_item AND id_personagem = OLD.id_personagem;

    IF NOT FOUND THEN
        UPDATE PERSONAGEM
        SET arma = NULL
        WHERE id_personagem = OLD.id_personagem AND arma = OLD.nome_item;
    END IF;

    RETURN OLD;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_verifica_arma_inventario
AFTER DELETE ON INVENTARIO
FOR EACH ROW
EXECUTE FUNCTION verifica_arma_inventario();
Trigger: destransformar_personagem
CREATE OR REPLACE FUNCTION destransformar_personagem() RETURNS TRIGGER AS $$
BEGIN
    IF NEW.saude <= 0 THEN
        UPDATE PERSONAGEM
        SET nome_alien = NULL
        WHERE id_personagem = NEW.id_personagem;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_destransformar_personagem
AFTER UPDATE ON STATUS_DO_ALIEN
FOR EACH ROW
WHEN (NEW.saude <= 0)
EXECUTE FUNCTION destransformar_personagem();
Função: curar_alien_gradativamente
CREATE OR REPLACE FUNCTION curar_alien_gradativamente(personagem_id INTEGER) RETURNS VOID AS $$
DECLARE
    cursor_aliens CURSOR FOR 
        SELECT sda.*, a.saude AS saude_maxima 
        FROM STATUS_DO_ALIEN sda 
        JOIN ALIEN a ON a.nome = sda.nome_alien 
        WHERE sda.id_personagem = personagem_id;

    alien_row RECORD;
    nivel_personagem INTEGER;
BEGIN
    SELECT nivel INTO nivel_personagem
    FROM PERSONAGEM
    WHERE id_personagem = personagem_id;

    FOR alien_row IN cursor_aliens LOOP
        UPDATE STATUS_DO_ALIEN
        SET saude = LEAST(saude + GREATEST(FLOOR(alien_row.saude_maxima * nivel_personagem * 0.02), 1), alien_row.saude_maxima * nivel_personagem)
        WHERE nome_alien = alien_row.nome_alien AND id_personagem = alien_row.id_personagem;
    END LOOP;
END;
$$ LANGUAGE plpgsql;
Trigger: insere_item
-- Trigger dos itens
CREATE OR REPLACE FUNCTION insere_item() RETURNS TRIGGER AS $check_item$
BEGIN
    IF new.tipo_item IS NULL THEN
        RETURN NULL;
    END IF;
    IF NEW.tipo_item = 'Consumível' THEN
        PERFORM * FROM item WHERE nome_item = NEW.nome_item AND tipo_item = 'Arma';
        IF FOUND THEN 
            RAISE EXCEPTION 'O item não pode ter dois tipos';
        END IF;
    END IF;
    IF NEW.tipo_item = 'Arma' THEN
        PERFORM * FROM item WHERE nome_item = NEW.nome_item AND tipo_item = 'Consumível';
        IF FOUND THEN 
            RAISE EXCEPTION 'O item não pode ter dois tipos';
        END IF;
    END IF;
    RETURN NEW;
END;
$check_item$ LANGUAGE plpgsql;

CREATE TRIGGER check_insere_item 
BEFORE INSERT OR UPDATE ON item
FOR EACH ROW EXECUTE PROCEDURE insere_item();
Trigger: insere_consumivel
-- Trigger dos consumíveis
CREATE OR REPLACE FUNCTION insere_consumivel() RETURNS TRIGGER AS $check_consumivel$
BEGIN
    PERFORM * FROM item WHERE nome_item = NEW.nome_item AND tipo_item = 'Arma';
    IF FOUND THEN 
        RAISE EXCEPTION 'O item é uma arma e não um consumível';
    END IF;
    RETURN NEW;
END;
$check_consumivel$ LANGUAGE plpgsql;

CREATE TRIGGER check_insere_consumivel 
BEFORE INSERT OR UPDATE ON CONSUMIVEL
FOR EACH ROW EXECUTE PROCEDURE insere_consumivel();
Trigger: insere_arma
-- Trigger das armas
CREATE OR REPLACE FUNCTION insere_arma() RETURNS TRIGGER AS $check_arma$
BEGIN
    PERFORM * FROM item WHERE nome_item = NEW.nome_item AND tipo_item = 'Consumível';
    IF FOUND THEN 
        RAISE EXCEPTION 'O item é um consumível e não uma arma';
    END IF;
    RETURN NEW;
END;
$check_arma$ LANGUAGE plpgsql;

CREATE TRIGGER check_insere_arma
BEFORE INSERT OR UPDATE ON ARMA
FOR EACH ROW EXECUTE PROCEDURE insere_arma();

Bibliografia

1. ELMASRI, Ramez; NAVATHE, Shamkant B. Sistemas de banco de dados. Tradução: Daniel Vieira. Revisão técnica: Enzo Seraphim; Thatyana de Faria Piola Seraphim. 6. ed. São Paulo: Pearson Addison Wesley, 2011. Capítulo 24.1, Conceitos de banco de dados ativos e gatilhos (Triggers), página 545【8:9†source.

2. SILBERSCHATZ, Abraham; KORTH, Henry F.; SUDARSHAN, S. Database system concepts. 6. ed. New York: McGraw-Hill, 2011. Capítulo 5, Advanced SQL, tópico 5.3 Triggers, páginas 180-184【8:5†source.

Histórico de Versão

Versão Data Descrição Autor(es) Revisor(es)
1.0 09/09 Criando documento e adicionando introdução, referencias bibliográficas e triggers Eric Silveira Arthur Alves