Това е труден проблем. Но може да се направи с тригери за всяка колона и условно изпълнение на тригери, въведени в PostgreSQL 9.0 .
Нуждаете се от „актуализиран“ флаг на ред за това решение. Използвайте boolean
колона в същата таблица за простота. Но може да е в друга таблица или дори във временна таблица на транзакция.
Скъпият полезен товар се изпълнява веднъж на ред където броячът се актуализира (веднъж или многократно).
Това също трябва да работи добре, защото ...
- ... той избягва множество извиквания на тригери в основата (добре мащабира)
- ... не променя допълнителните редове (минимизиране на раздуването на таблицата)
- ... не се нуждае от скъпа обработка на изключения.
Помислете за следното
Демо
Тествано в PostgreSQL 9.1 с отделна схема x
като тестова среда.
Таблици и фиктивни редове
-- DROP SCHEMA x;
CREATE SCHEMA x;
CREATE TABLE x.tbl (
id int
,counter int
,trig_exec_count integer -- for monitoring payload execution.
,updated bool);
Поставете два реда, за да демонстрирате, че работи с множество реда:
INSERT INTO x.tbl VALUES
(1, 0, 0, NULL)
,(2, 0, 0, NULL);
Задействащи функции и тригери
1.) Изпълнявайте скъп полезен товар
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.) Маркирайте реда като актуализиран.
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.) Нулирайте флага „актуализиран“.
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;
Имената на тригерите са подходящи! Извикани за същото събитие, те се изпълняват по азбучен ред.
1.) Полезен товар, само ако все още не е "актуализиран":
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.) Маркирайте реда като актуализиран, само ако все още не е „актуализиран“:
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.) Флаг за нулиране. Няма безкраен цикъл поради условие на задействане.
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();
Тест
Изпълнете UPDATE
&SELECT
отделно, за да видите отложения ефект. Ако се изпълни заедно (в една транзакция), SELECT ще покаже новия tbl.counter
но старият tbl2.trig_exec_count
.
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;
Сега актуализирайте брояча няколко пъти (в една транзакция). Полезният товар ще бъде изпълнен само веднъж. Вуаля!
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;