使用 DEFAULT 处理 PostgreSQL 视图触发器中的 NULL 值?
Handle NULL values in trigger of PostgreSQL views using DEFAULT?
我想解释一下 Postgres 视图的触发器。
为了弄清楚我想问什么,我将给你一个我的案例的非常简单的例子。
在此示例中,我们有两个 tables (table_a
, table_b
) 连接在一起,形成示例中的视图 (vw_table_ab
).
在这个例子中,我将使用简单的名称和简单的 DDLs/DMLs。
-- 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) || '();';
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 null violation" 错误,因为我在 table_a
上有一些 NOT NULL
约束和 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
。但是这些字段在基础 table 的定义中有一个 DEFAULT
值,我想使用它。
...
timestamp_field timestamp DEFAULT now() NOT NULL,
boolean_field boolean DEFAULT FALSE NOT NULL
...
我的问题:
如何使用 table 中定义的 DEFAULT 管理视图触发器中的 NULL 值?
最初我想把 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;
$_$;
有人可以帮助我吗?这种情况还有其他处理方法吗?
最简单的方法是使用 ALTER VIEW ... ALTER col SET DEFAULT
在视图上定义与基础 table.
上的默认值相同的默认值
然后不插入显式 NULL,而是省略 INSERT
语句中的列或显式插入 DEFAULT
。您生成的视图将表现得像真实的 table.
我想解释一下 Postgres 视图的触发器。
为了弄清楚我想问什么,我将给你一个我的案例的非常简单的例子。
在此示例中,我们有两个 tables (table_a
, table_b
) 连接在一起,形成示例中的视图 (vw_table_ab
).
在这个例子中,我将使用简单的名称和简单的 DDLs/DMLs。
-- 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) || '();';
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 null violation" 错误,因为我在 table_a
上有一些 NOT NULL
约束和 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
。但是这些字段在基础 table 的定义中有一个 DEFAULT
值,我想使用它。
...
timestamp_field timestamp DEFAULT now() NOT NULL,
boolean_field boolean DEFAULT FALSE NOT NULL
...
我的问题: 如何使用 table 中定义的 DEFAULT 管理视图触发器中的 NULL 值?
最初我想把 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;
$_$;
有人可以帮助我吗?这种情况还有其他处理方法吗?
最简单的方法是使用 ALTER VIEW ... ALTER col SET DEFAULT
在视图上定义与基础 table.
然后不插入显式 NULL,而是省略 INSERT
语句中的列或显式插入 DEFAULT
。您生成的视图将表现得像真实的 table.