PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Esegui il trigger differito solo una volta per riga in PostgreSQL

Questo è un problema complicato. Ma può essere fatto con i trigger per colonna e l'esecuzione di trigger condizionali introdotti in PostgreSQL 9.0 .

Hai bisogno di un flag "aggiornato" per riga per questa soluzione. Usa un boolean colonna nella stessa tabella per semplicità. Ma potrebbe trovarsi in un'altra tabella o anche in una tabella temporanea per transazione.

Il costoso carico utile viene eseguito una volta per riga dove il contatore viene aggiornato (una o più volte).

Anche questo dovrebbe funzionare beh, perché...

  • ... evita più chiamate di trigger alla radice (scala bene)
  • ... non modifica le righe aggiuntive (riduce al minimo il volume della tabella)
  • ... non necessita di una costosa gestione delle eccezioni.

Considera quanto segue

Dimostrazione

Testato in PostgreSQL 9.1 con uno schema separato x come ambiente di prova.

Tabelle e righe fittizie

-- DROP SCHEMA x;
CREATE SCHEMA x;

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

Inserisci due righe per dimostrare che funziona con più righe:

INSERT INTO x.tbl VALUES
 (1, 0, 0, NULL)
,(2, 0, 0, NULL);

Funzioni Trigger e Trigger

1.) Esegui un carico utile costoso

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.) Contrassegna la riga come aggiornata.

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.) Reimposta il flag "aggiornato".

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;

I nomi dei trigger sono rilevanti! Chiamati per lo stesso evento vengono eseguiti in ordine alfabetico.

1.) Payload, solo se non ancora "aggiornato":

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.) Contrassegna la riga come aggiornata, solo se non ancora "aggiornata":

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.) Reimposta bandiera. Nessun ciclo infinito a causa della condizione di attivazione.

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();

Test

Esegui UPDATE &SELECT separatamente per vedere l'effetto differito. Se eseguiti insieme (in un'unica transazione), SELECT mostrerà il nuovo tbl.counter ma il vecchio tbl2.trig_exec_count .

UPDATE x.tbl SET counter = counter + 1;

SELECT * FROM x.tbl;

Ora, aggiorna il contatore più volte (in una transazione). Il payload verrà eseguito solo una volta. Via!

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;