To trudny problem. Ale można to zrobić za pomocą wyzwalaczy dla poszczególnych kolumn i warunkowego wykonywania wyzwalaczy wprowadzonych w PostgreSQL 9.0 .
Potrzebujesz flagi „zaktualizowanej” na wiersz dla tego rozwiązania. Użyj boolean
kolumna w tej samej tabeli dla uproszczenia. Ale może to być w innej tabeli lub nawet w tabeli tymczasowej na transakcję.
Kosztowny ładunek jest wykonywany raz na wiersz gdzie licznik jest aktualizowany (raz lub wiele razy).
Powinno to również działać no bo...
- ... pozwala uniknąć wielu wywołań wyzwalaczy u źródła (dobrze skaluje się)
- ... nie zmienia dodatkowych wierszy (minimalizuje rozdęcie tabeli)
- ... nie wymaga kosztownej obsługi wyjątków.
Rozważ następujące
Demo
Testowane w PostgreSQL 9.1 z osobnym schematem x
jako środowisko testowe.
Tabele i atrapy wierszy
-- DROP SCHEMA x;
CREATE SCHEMA x;
CREATE TABLE x.tbl (
id int
,counter int
,trig_exec_count integer -- for monitoring payload execution.
,updated bool);
Wstaw dwa wiersze, aby zademonstrować, że działa z wieloma wierszami:
INSERT INTO x.tbl VALUES
(1, 0, 0, NULL)
,(2, 0, 0, NULL);
Funkcje i wyzwalacze
1.) Wykonuj drogi ładunek
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.) Oznacz wiersz jako zaktualizowany.
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.) Zresetuj flagę „zaktualizowane”.
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;
Nazwy wyzwalaczy są istotne! Wywoływane dla tego samego zdarzenia, są wykonywane w kolejności alfabetycznej.
1.) Ładunek, tylko jeśli nie został jeszcze „zaktualizowany”:
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.) Oznacz wiersz jako zaktualizowany, tylko jeśli nie jest jeszcze „zaktualizowany”:
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.) Zresetuj flagę. Brak nieskończonej pętli z powodu warunku wyzwalania.
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
Uruchom UPDATE
&SELECT
osobno, aby zobaczyć odroczony efekt. Jeśli zostaną wykonane razem (w jednej transakcji), SELECT pokaże nowy tbl.counter
ale stary tbl2.trig_exec_count
.
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;
Teraz zaktualizuj licznik kilka razy (w jednej transakcji). Ładunek zostanie wykonany tylko raz. Witajcie!
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;