Обрабатывать значения NULL в триггере представлений PostgreSQL, используя DEFAULT? - PullRequest
4 голосов
/ 21 мая 2019

Я хотел бы получить объяснение триггеров представлений Postgres.

Чтобы пояснить, что я хочу спросить, я приведу очень упрощенный пример моего случая. В этом примере у нас есть две таблицы (table_a, table_b), которые объединены вместе, что делает представление в примере (vw_table_ab).

В этом примере я буду использовать тривиальные имена и простые DDL / DML.

-- TABLE table_a
CREATE TABLE table_a
(
    id              serial PRIMARY KEY,
    timestamp_field timestamp DEFAULT now() NOT NULL,
    boolean_field   boolean   DEFAULT FALSE NOT NULL
);

-- TABLE table_b
CREATE TABLE table_b
(
    id              serial PRIMARY KEY,
    timestamp_field timestamp DEFAULT now() NOT NULL,
    boolean_field   boolean   DEFAULT FALSE NOT NULL,
    id_table_a      integer                 NOT NULL,
    CONSTRAINT "fk_table_a" FOREIGN KEY (id_table_a) REFERENCES table_a (id) ON DELETE CASCADE NOT DEFERRABLE,
    CONSTRAINT "u_table_a" UNIQUE (id_table_a)
);

-- VIEW vw_table_ab
CREATE VIEW vw_table_ab AS (
    SELECT a.timestamp_field AS timestamp_a,
           a.boolean_field   AS boolean_a,
           b.timestamp_field AS timestamp_b,
           b.boolean_field   AS boolean_b
    FROM table_a a
    JOIN table_b b ON a.id = b.id_table_a
);

Функция триггера для стандартных действий (INSERT, UPDATE и DELETE) связана с этим представлением через триггер INSTEAD OF.

-- TRIGGER FUNCTION fn_trigger
CREATE FUNCTION fn_trigger() RETURNS trigger LANGUAGE plpgsql AS
$_$
DECLARE
    sql TEXT;
BEGIN
    sql = 'SELECT ' || TG_TABLE_NAME || '_' || lower(TG_OP) || '($1);';

    IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
        EXECUTE (sql) USING NEW;
        RAISE NOTICE '%', sql;
        RETURN NEW;
    ELSE
        EXECUTE (sql) USING OLD;
        RAISE NOTICE '%', sql;
        RETURN OLD;
    END IF;
END;
$_$;

-- TRIGGER tr_table_ab
CREATE TRIGGER tr_table_ab
INSTEAD OF INSERT OR UPDATE OR DELETE ON vw_table_ab
FOR EACH ROW EXECUTE PROCEDURE fn_trigger();

В приведенном мной примере триггер вызывается только для действия вставки, и выполняемая функция выглядит так:

-- INSERT FUNCTION vw_table_ab_insert
CREATE FUNCTION vw_table_ab_insert(new vw_table_ab) RETURNS void LANGUAGE plpgsql AS
$_$
DECLARE
    id_table_a integer;
BEGIN
    INSERT INTO table_a (timestamp_field, boolean_field) VALUES (new.timestamp_a, new.boolean_a) 
    RETURNING id 
    INTO id_table_a;

    INSERT INTO table_b (timestamp_field, boolean_field, id_table_a) VALUES (new.timestamp_a, new.boolean_b, id_table_a);
END;
$_$;

Теперь мы можем добраться до моей проблемы. Я делаю вставку в представлении, и когда действие запускается, я получаю ошибку "Not not null" , потому что у меня есть некоторые NOT NULL ограничения на table_a и table_b, как в этом случае :

INSERT INTO vw_table_ab (timestamp_a, boolean_a, timestamp_b, boolean_b) VALUES (now(), NULL, now(), NULL);

Предположим, что предыдущий оператор сгенерирован в рамках языка программирования, и я не хочу обрабатывать этот случай в бэкэнд-коде, но я хочу обработать этот случай в PostgreSQL в функции вставки vw_table_ab_insert. Так что на данный момент моя проблема связана с параметром new функции, потому что у меня есть поля вида, которые NULL. Но эти поля имеют значение DEFAULT в определении базовой таблицы, и я хочу использовать это.

...
timestamp_field timestamp DEFAULT now() NOT NULL,
boolean_field   boolean   DEFAULT FALSE NOT NULL
...

Мой вопрос: Как я могу управлять значениями NULL в триггере представлений, используя DEFAULT, определенный в таблицах?

Первоначально я думал поместить IF ... THEN ... внутрь функции и переопределить нулевые значения с помощью выражения DEFAULT, но мне это не очень нравится. Например, функция должна выглядеть следующим образом:

CREATE FUNCTION vw_table_ab_insert(new vw_table_ab) RETURNS void LANGUAGE plpgsql AS
$_$
DECLARE
    id_table_a integer;
BEGIN
    IF new.timestamp_a IS NULL THEN
        new.timestamp_a = DEFAULT;
    END IF;

    IF new.boolean_a IS NULL THEN
        new.boolean_a = DEFAULT;
    END IF;

    IF new.timestamp_b IS NULL THEN
        new.timestamp_b = DEFAULT;
    END IF;

    IF new.boolean_b IS NULL THEN
        new.boolean_b = DEFAULT;
    END IF;

    INSERT INTO table_a (timestamp_field, boolean_field)
    VALUES (new.timestamp_a, new.boolean_a) 
    RETURNING id
    INTO id_table_a;

    INSERT INTO table_b (timestamp_field, boolean_field, id_table_a)
    VALUES (new.timestamp_a, new.boolean_b, id_table_a);
END;
$_$;

Кто-то может мне помочь? Есть ли другой способ обработки этого случая?

1 Ответ

2 голосов
/ 21 мая 2019

Самый простой способ - использовать ALTER VIEW ... ALTER col SET DEFAULT для определения значений по умолчанию в представлении, совпадающих со значениями по умолчанию в базовой таблице.

Затем вместо вставки явных значений NULL опустите столбцы из оператора INSERT или вставьте DEFAULT явно. Полученное представление будет вести себя как настоящая таблица.

...