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;