Я хотел бы получить объяснение триггеров представлений 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;
$_$;
Кто-то может мне помочь? Есть ли другой способ обработки этого случая?