В PostgreSQL я не уверен, что понимаю посттриггеры:
Правильно ли в триггере (функции) AFTER insert or update FOR EACH STATEMENT
(в отличие, например, от MSSQL), если я запрашиваю таблицу триггера, я вижу данные до того, как к таблице будут применены какие-либо изменения?
И, если это правильно, как я могу применить ограничение ко всей таблице (например, чтобы сумма по некоторому столбцу не превышала определенного значения или чтобы не более N записей имели какой-то логический столбец со значением true и т. д. )?
Единственный подход, который мне удалось придумать, - это запрос к таблице, объединяющей ее с удаленными и вставленными записями, чтобы восстановить, какими будут данные после оператора, но я новичок в PG, и это кажется слишком большой работой для такого общее требование.
Я пробовал читать документацию, а также искать в Интернете, но не смог найти четкого ответа на вышеизложенное, даже примера.
Спасибо за любые разъяснения/советы.
Верно ли, что внутри AFTER [триггера] если я запрашиваю таблицу триггера, я вижу данные раньше какие-либо изменения применяются к таблице?
Нет, это скорее зависит от категории волатильности триггерной функции: функция VOLATILE увидит изменения таблицы, а функция STABLE или IMMUTABLE — нет. -- Действительно, я объявил свою функцию STABLE, поэтому не заметил изменений в таблице.
См. 38.7. Категории волатильности функций для более подробной информации. Вот выдержка:
Для функций, написанных на SQL или любом из стандартных процедурных языков есть второе важное свойство, определяемое категория волатильности, а именно видимость любых изменений данных, которые были созданы командой SQL, вызывающей функцию. ИЗМЕНЯЕМАЯ функция увидит такие изменения, СТАБИЛЬНАЯ или НЕИЗМЕННАЯ. функции не будет. Такое поведение реализуется с помощью снимков поведение MVCC (см. главу 13): функции STABLE и IMMUTABLE используют снимок, созданный на момент начала вызывающего запроса, тогда как Функции VOLATILE получают свежий снимок в начале каждого запроса. они исполняют.
Фактически, SQL-команды/CREATE FUNCTION также упоминает об этом:
[STABLE] не подходит для триггеров AFTER, которые хотят запрашивать строки. изменено текущей командой.
Наконец, вот базовый пример сравнения триггерных функций STABLE и VOLATILE, который не только подтверждает приведенные выше выводы, но также должен служить минимальным примером того, как писать (и не писать) триггеры AFTER для табличных ограничений ( это совершенно минимально, поскольку RAISE NOTICE никуда не пишет в DB Fiddle, и мы не можем вставлять записи в какую-либо таблицу журналирования из функций STABLE):
CREATE DATABASE "TestDb";
CREATE TABLE "TestTable_stable"
(
"Name" varchar(20) NOT NULL,
"Flag" bool NOT NULL,
PRIMARY KEY ("Name")
);
CREATE TABLE "TestTable_volatile"
(
"Name" varchar(20) NOT NULL,
"Flag" bool NOT NULL,
PRIMARY KEY ("Name")
);
CREATE FUNCTION "fn_TestTable_stable_TC"()
RETURNS trigger
LANGUAGE plpgsql
STABLE
AS '
DECLARE
_flagCount integer;
BEGIN
SELECT COUNT("Name")
FROM "TestTable_stable"
WHERE "Flag" = true
INTO STRICT _flagCount;
IF NOT (_flagCount <= 1) THEN
RAISE EXCEPTION
''(stable) FlagCount = %'', _flagCount;
END IF;
RETURN NULL;
END;
';
CREATE FUNCTION "fn_TestTable_volatile_TC"()
RETURNS trigger
LANGUAGE plpgsql
VOLATILE
AS '
DECLARE
_flagCount integer;
BEGIN
SELECT COUNT("Name")
FROM "TestTable_volatile"
WHERE "Flag" = true
INTO STRICT _flagCount;
IF NOT (_flagCount <= 1) THEN
RAISE EXCEPTION
''(volatile) FlagCount = %'', _flagCount;
END IF;
RETURN NULL;
END;
';
CREATE TRIGGER "TestTable_stable_TC"
AFTER INSERT OR UPDATE OF "Flag"
ON "TestTable_stable"
FOR EACH STATEMENT
EXECUTE FUNCTION "fn_TestTable_stable_TC"()
;
CREATE TRIGGER "TestTable_volatile_TC"
AFTER INSERT OR UPDATE OF "Flag"
ON "TestTable_volatile"
FOR EACH STATEMENT
EXECUTE FUNCTION "fn_TestTable_volatile_TC"()
;
-- (1) Test with implicit (i.e. statement-level) transactions:
INSERT INTO "TestTable_stable" -- OK
VALUES
('A', true),
('B', false)
;
INSERT INTO "TestTable_stable" -- NO ERROR! (needs volatile)
VALUES
('C', false),
('D', true)
;
INSERT INTO "TestTable_volatile" -- OK
VALUES
('A', true),
('B', false)
;
INSERT INTO "TestTable_volatile" -- ERROR (as expected)
VALUES
('E', false),
('F', true)
;
-- (2) Same test in a SERIALIZABLE transaction, same result:
TRUNCATE TABLE "TestTable_stable";
TRUNCATE TABLE "TestTable_volatile";
BEGIN ISOLATION LEVEL SERIALIZABLE;
INSERT INTO "TestTable_stable" -- OK
VALUES
('S1', true),
('S2', false)
;
INSERT INTO "TestTable_stable" -- NO ERROR! (needs volatile)
VALUES
('S3', false),
('S4', true)
;
INSERT INTO "TestTable_volatile" -- OK
VALUES
('V1', true),
('V2', false)
;
INSERT INTO "TestTable_volatile" -- ERROR (as expected)
VALUES
('V3', false),
('V4', true)
;
COMMIT;
Ссылка на код на DB Fiddle.