PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Execute o gatilho adiado apenas uma vez por linha no PostgreSQL


Este é um problema complicado. Mas isso pode ser feito com gatilhos por coluna e execução de gatilho condicional introduzidos no PostgreSQL 9.0 .

Você precisa de um sinalizador "atualizado" por linha para esta solução. Use um boolean coluna na mesma tabela para simplificar. Mas pode ser em outra tabela ou até em uma tabela temporária por transação.

O payload caro é executado uma vez por linha onde o contador é atualizado (uma ou várias vezes).

Isso também deve funcionar bem, porque...
  • ... evita várias chamadas de gatilhos na raiz (escala bem)
  • ... não altera linhas adicionais (minimiza o inchaço da tabela)
  • ... não precisa de tratamento de exceção caro.

Considere o seguinte

Demonstração


Testado no PostgreSQL 9.1 com um esquema separado x como ambiente de teste.

Tabelas e linhas fictícias

-- DROP SCHEMA x;
CREATE SCHEMA x;

CREATE TABLE x.tbl (
 id int
,counter int
,trig_exec_count integer  -- for monitoring payload execution.
,updated bool);

Insira duas linhas para demonstrar que funciona com várias linhas:
INSERT INTO x.tbl VALUES
 (1, 0, 0, NULL)
,(2, 0, 0, NULL);

Funções de gatilho e gatilhos


1.) Executar carga útil cara
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_1()
    RETURNS trigger AS
$BODY$
BEGIN

 -- PERFORM some_expensive_procedure(NEW.id);
 -- Update trig_exec_count to count execution of expensive payload.
 -- Could be in another table, for simplicity, I use the same:

UPDATE x.tbl t
SET    trig_exec_count = trig_exec_count + 1
WHERE  t.id = NEW.id;

RETURN NULL;  -- RETURN value of AFTER trigger is ignored anyway

END;
$BODY$ LANGUAGE plpgsql;

2.) Sinalize a linha como atualizada.
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_2()
    RETURNS trigger AS
$BODY$
BEGIN

UPDATE x.tbl
SET    updated = TRUE
WHERE  id = NEW.id;
RETURN NULL;

END;
$BODY$ LANGUAGE plpgsql;

3.) Redefina o sinalizador "atualizado".
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_3()
    RETURNS trigger AS
$BODY$
BEGIN

UPDATE x.tbl
SET    updated = NULL
WHERE  id = NEW.id;
RETURN NULL;

END;
$BODY$ LANGUAGE plpgsql;

Os nomes dos gatilhos são relevantes! Chamados para o mesmo evento são executados em ordem alfabética.

1.) Payload, somente se ainda não estiver "atualizado":
CREATE CONSTRAINT TRIGGER upaft_counter_change_1
    AFTER UPDATE OF counter ON x.tbl
    DEFERRABLE INITIALLY DEFERRED
    FOR EACH ROW
    WHEN (NEW.updated IS NULL)
    EXECUTE PROCEDURE x.trg_upaft_counter_change_1();

2.) Sinalizar a linha como atualizada, somente se ainda não estiver "atualizada":
CREATE TRIGGER upaft_counter_change_2   -- not deferred!
    AFTER UPDATE OF counter ON x.tbl
    FOR EACH ROW
    WHEN (NEW.updated IS NULL)
    EXECUTE PROCEDURE x.trg_upaft_counter_change_2();

3.) Redefinir Sinalizador. Sem loop infinito por causa da condição de disparo.
CREATE CONSTRAINT TRIGGER upaft_counter_change_3
    AFTER UPDATE OF updated ON x.tbl
    DEFERRABLE INITIALLY DEFERRED
    FOR EACH ROW
    WHEN (NEW.updated)                 --
    EXECUTE PROCEDURE x.trg_upaft_counter_change_3();

Teste


Execute UPDATE &SELECT separadamente para ver o efeito diferido. Se executado em conjunto (em uma transação) o SELECT mostrará o novo tbl.counter mas o antigo tbl2.trig_exec_count .
UPDATE x.tbl SET counter = counter + 1;

SELECT * FROM x.tbl;

Agora, atualize o contador várias vezes (em uma transação). A carga útil será executada apenas uma vez. Voilá!
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;

SELECT * FROM x.tbl;