Это сложная проблема. Но это можно сделать с помощью триггеров на столбец и условного запуска триггера , представленных в PostgreSQL 9.0 .
Для этого решения вам необходим флаг «update» в строке . Для простоты используйте столбец 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();
Test
Запустите 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;