PostgreSQL 在查询 INSTEAD OF 触发器时使用 NEW

bel*_*daz 8 postgresql trigger syntax plpgsql

我无法让 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);
Run Code Online (Sandbox Code Playgroud)

我希望能够创建INSTEAD OF触发器以允许我直接插入到 中PurchaseView,例如:

INSERT INTO Product(product_name) VALUES ('foo');
INSERT INTO PurchaseView(product_name, when_bought) VALUES ('foo', NOW());
Run Code Online (Sandbox Code Playgroud)

我的想法是这样的:

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();
Run Code Online (Sandbox Code Playgroud)

然而,上述触发函数relation "new" does not exist在执行时会出现错误 ( )。我知道我可以编写显式使用and子句NEW中的属性的查询,但有时将其包含在连接中会很方便。有没有办法做到这一点?WHERESELECTNEW

当前(不满意)的解决方案

我能得到的最接近我想要的是

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;
Run Code Online (Sandbox Code Playgroud)

由于以下几个原因,这有点不令人满意:

  1. 我需要显式地编写NEWCTE WITH 查询中的所有属性,这对于大视图(尤其是那些属性自动确定的视图SELECT *)变得笨拙;

  2. 返回的结果没有更新SERIAL类型product_id,所以你不会得到预期的结果:

    INSERT INTO PurchaseView(product_name, when_bought) 
    VALUES ('foo', NOW())
    RETURNING *;
    
    Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 9

NEW记录,不是。基本:

稍微修改的设置

CREATE TABLE product (
  product_id serial PRIMARY KEY,
  product_name text UNIQUE NOT NULL  -- must be UNIQUE
);

CREATE TABLE purchase (
  purchase_id serial PRIMARY KEY,
  product_id  int REFERENCES product,
  when_bought date
);

CREATE VIEW purchaseview AS
SELECT pu.purchase_id, pr.product_name, pu.when_bought
FROM   purchase     pu
LEFT   JOIN product pr USING (product_id);

INSERT INTO product(product_name) VALUES ('foo');
Run Code Online (Sandbox Code Playgroud)

product_name必须是UNIQUE,否则在此列上查找可能会发现多行,这会导致各种混乱。

1.简单的解决方案

对于您的简单示例,仅查找单个列product_id,低相关子查询是最简单和最快的:

CREATE OR REPLACE FUNCTION insert_purchaseview_func()
  RETURNS trigger AS
$func$
BEGIN
   INSERT INTO purchase(product_id, when_bought)
   SELECT (SELECT product_id FROM product WHERE product_name = NEW.product_name), NEW.when_bought
   RETURNING purchase_id
   INTO   NEW.purchase_id;  -- generated serial ID for RETURNING - if needed

   RETURN NEW;
END
$func$  LANGUAGE plpgsql;

CREATE TRIGGER insert_productview_trig
INSTEAD OF INSERT ON purchaseview
FOR EACH ROW EXECUTE PROCEDURE insert_purchaseview_func();
Run Code Online (Sandbox Code Playgroud)

没有额外的变量。没有 CTE(只会增加成本和噪音)。来自NEW的列仅拼写一次(您的第 1 点)。

附加RETURNING purchase_id INTO NEW.purchase_id处理您的第 2 点:现在,返回的行包括新生成的purchase_id.

如果未找到该产品(NEW.product_name表中不存在product),则仍插入购买并且product_idNULL。这可能是也可能不是可取的。

2.

要跳过该行(并可能引发WARNING/ EXCEPTION):

CREATE OR REPLACE FUNCTION insert_purchaseview_func()
  RETURNS trigger AS
$func$
BEGIN
   INSERT INTO purchase AS pu
            (product_id,     when_bought)
   SELECT pr.product_id, NEW.when_bought
   FROM   product pr
   WHERE  pr.product_name = NEW.product_name
   RETURNING pu.purchase_id
   INTO   NEW.purchase_id;  -- generated serial ID for RETURNING - if needed

   IF NOT FOUND THEN  -- insert was canceled for missing product
      RAISE WARNING 'product_name % not found! Skipping INSERT.', quote_literal(NEW.product_name);
   END IF;

   RETURN NEW;
END
$func$  LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

这将NEW列搭载到SELECT .. FROM product. 如果找到产品,一切都会正常进行。如果不是,则不会从 返回任何行SELECT并且不会INSERT发生任何事情。特殊的 PL/pgSQL 变量FOUND仅在最后一个 SQL 查询至少处理了一行时才为真。

可能EXCEPTION不是WARNING引发错误并回滚事务。但我宁愿purchase.product_id NOT NULL无条件地声明和插入(查询 1 或类似的),以达到相同的效果:如果product_id是,则引发异常NULL。更简单,更便宜。

3.对于多次查找

CREATE OR REPLACE FUNCTION insert_purchaseview_func()
  RETURNS trigger AS
$func$
BEGIN
   INSERT INTO purchase AS pu
            (product_id,   when_bought)     -- more columns?
   SELECT pr.product_id, i.when_bought      -- more columns?
   FROM  (SELECT NEW.*) i                   -- see below
   LEFT   JOIN product  pr USING (product_name)
-- LEFT   JOIN tbl2     t2 USING (t2_name)  -- more lookups?
   RETURNING pu.purchase_id                 -- more columns?
   INTO   NEW.purchase_id;                  -- more columns?

   RETURN NEW;
END
$func$  LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

LEFT JOIN奇妆的INSERT无条件的一次。JOIN如果找不到,请改用跳过。

FROM (SELECT NEW.*) i变换记录 NEW到一个派生表与单,可在中使用像任何表FROM条款-你要找的最初阶段。

db<>在这里摆弄