PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Führen Sie den verzögerten Trigger nur einmal pro Zeile in PostgreSQL aus

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;