PostgreSQL после триггера для ограничения таблицы

В PostgreSQL я не уверен, что понимаю посттриггеры:

  • Правильно ли в триггере (функции) AFTER insert or update FOR EACH STATEMENT (в отличие, например, от MSSQL), если я запрашиваю таблицу триггера, я вижу данные до того, как к таблице будут применены какие-либо изменения?

  • И, если это правильно, как я могу применить ограничение ко всей таблице (например, чтобы сумма по некоторому столбцу не превышала определенного значения или чтобы не более N записей имели какой-то логический столбец со значением true и т. д. )?

Единственный подход, который мне удалось придумать, - это запрос к таблице, объединяющей ее с удаленными и вставленными записями, чтобы восстановить, какими будут данные после оператора, но я новичок в PG, и это кажется слишком большой работой для такого общее требование.

Я пробовал читать документацию, а также искать в Интернете, но не смог найти четкого ответа на вышеизложенное, даже примера.

Спасибо за любые разъяснения/советы.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Верно ли, что внутри 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.

Другие вопросы по теме