Dies ist ein kniffliges Problem. Dies kann jedoch mit spaltenweisen Triggern und der bedingten Triggerausführung erfolgen, die in PostgreSQL 9.0 eingeführt wurden .
Sie benötigen ein "aktualisiert"-Flag pro Zeile für diese Lösung. Verwenden Sie einen boolean
Spalte in derselben Tabelle der Einfachheit halber. Aber es könnte sich in einer anderen Tabelle oder sogar in einer temporären Tabelle pro Transaktion befinden.
Die teure Nutzlast wird einmal pro Zeile ausgeführt wo der Zähler aktualisiert wird (einmal oder mehrfach).
Dies sollte auch funktionieren naja, weil ...
- ... es vermeidet mehrfache Aufrufe von Triggern an der Wurzel (skaliert gut)
- ... ändert keine zusätzlichen Zeilen (minimiert das Aufblähen der Tabelle)
- ... benötigt keine teure Ausnahmebehandlung.
Beachten Sie Folgendes
Demo
Getestet in PostgreSQL 9.1 mit separatem Schema x
als Testumgebung.
Tabellen und Dummy-Zeilen
-- DROP SCHEMA x;
CREATE SCHEMA x;
CREATE TABLE x.tbl (
id int
,counter int
,trig_exec_count integer -- for monitoring payload execution.
,updated bool);
Fügen Sie zwei Zeilen ein, um zu demonstrieren, dass es mit mehreren Zeilen funktioniert:
INSERT INTO x.tbl VALUES
(1, 0, 0, NULL)
,(2, 0, 0, NULL);
Triggerfunktionen und Trigger
1.) Teure Payload ausführen
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.) Zeile als aktualisiert kennzeichnen.
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.) "Aktualisiert"-Flag zurücksetzen.
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;
Triggernamen sind relevant! Für dasselbe Ereignis aufgerufen, werden sie in alphabetischer Reihenfolge ausgeführt.
1.) Payload, nur wenn noch nicht "aktualisiert":
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.) Zeile als aktualisiert kennzeichnen, nur wenn noch nicht "aktualisiert":
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.) Flag zurücksetzen. Keine Endlosschleife wegen Triggerbedingung.
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
Führen Sie UPDATE
aus &SELECT
getrennt, um den verzögerten Effekt zu sehen. Bei gemeinsamer Ausführung (in einer Transaktion) zeigt das SELECT den neuen tbl.counter
aber die alte tbl2.trig_exec_count
.
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;
Aktualisieren Sie den Zähler jetzt mehrmals (in einer Transaktion). Die Payload wird nur einmal ausgeführt. 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;