我无法让 INSTEAD OF 触发器正常工作,而且我想我误解了如何使用 NEW。考虑以下简化场景:
CREATE TABLE Product (
product_id SERIAL PRIMARY KEY,
product_name VARCHAR
);
CREATE TABLE Purchase (
purchase_id SERIAL PRIMARY KEY,
product_id INT REFERENCES Product,
when_bought DATE
);
CREATE VIEW PurchaseView AS
SELECT purchase_id, product_name, when_bought
FROM Purchase LEFT JOIN Product USING (product_id);
我希望能够创建INSTEAD OF
触发器以允许我直接插入PurchaseView
,例如:
INSERT INTO Product(product_name) VALUES ('foo');
INSERT INTO PurchaseView(product_name, when_bought) VALUES ('foo', NOW());
我的想法是这样的:
CREATE OR REPLACE FUNCTION insert_purchaseview_func()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO Purchase(product_id, when_bought)
SELECT product_id, when_bought
FROM NEW
LEFT JOIN Product USING (product_name)
RETURNING * INTO NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER insert_productview_trig
INSTEAD OF INSERT
ON PurchaseView
FOR EACH ROW
EXECUTE PROCEDURE insert_purchaseview_func();
然而,上面的触发函数relation "new" does not exist
在执行时会给出错误( )。我知道我可以编写显式使用and子句NEW
中的属性的查询,但有时能够包含在连接中会很方便。有没有办法做到这一点?WHERE
SELECT
NEW
当前(不满意)的解决方案
我能得到的最接近我想要的是
CREATE OR REPLACE FUNCTION insert_purchaseview_func()
RETURNS trigger AS
$BODY$
DECLARE
tmp RECORD;
BEGIN
WITH input (product_name, when_bought) as (
values (NEW.product_name, NEW.when_bought)
)
INSERT INTO Purchase(product_id, when_bought)
SELECT product_id, when_bought
FROM input
LEFT JOIN Product USING (product_name)
RETURNING * INTO tmp;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
由于以下几个原因,这有点令人不满意:
我需要
NEW
在 CTE WITH 查询中显式编写 的所有属性,这对于大视图(尤其是那些属性由 自动确定的视图SELECT *
)变得笨拙;返回的结果没有更新
SERIAL
类型product_id
,因此您不会得到预期的结果:INSERT INTO PurchaseView(product_name, when_bought) VALUES ('foo', NOW()) RETURNING *;
NEW
是记录,不是表格。基本:稍作修改的设置
product_name
必须是UNIQUE
,否则在此列上查找可能会找到多行,这会导致各种混乱。1.简单的解决方案
对于您的简单示例,仅查找单个列
product_id
,低相关子查询是最简单和最快的:没有额外的变量。没有 CTE(只会增加成本和噪音)。来自的列
NEW
仅拼写一次(您的第 1 点)。附加
RETURNING purchase_id INTO NEW.purchase_id
的会照顾您的第 2 点:现在,返回的行包括新生成的purchase_id
.如果未找到产品(
NEW.product_name
表中不存在product
),则仍会插入购买并且product_id
是NULL
。这可能是可取的,也可能不是可取的。2.
要跳过该行(并可能引发
WARNING
/EXCEPTION
):NEW
这将列搭载到SELECT .. FROM product
. 如果找到产品,一切都会正常进行。如果不是,则不会从 the 返回任何行,SELECT
也不会发生任何INSERT
事情。特殊的 PL/pgSQL 变量FOUND
仅在最后一个 SQL 查询至少处理了一行时才为真。可以
EXCEPTION
代替WARNING
引发错误并回滚事务。但我宁愿无条件地声明和插入(查询 1 或类似的),效果相同:如果ispurchase.product_id NOT NULL
引发异常。更简单,更便宜。product_id
NULL
3.用于多次查找
LEFT JOIN
s 再次使无条件INSERT
。JOIN
如果找不到,请改为跳过。FROM (SELECT NEW.*) i
将记录NEW
转换为具有单行的派生表,该表可以像FROM
子句中的任何表一样使用 - 最初是您要查找的。db<>在这里摆弄
正如评论中所建议的,看起来我能做的最接近我想要的是(修复我在问题中的原始方法):
这至少使该
RETURNING
子句正常工作。看起来NEW
必须显式声明的属性。以下:ERROR: column "product_name" specified in USING clause does not exist in left table
触发触发器时的结果。