如何使执行连接的 Postgres 视图可更新?

Abe*_*ker 5 postgresql join view update

我有两张桌子,products并且subscriptions

CREATE TABLE products (
    id bigint NOT NULL,
    title character varying(75),
    description text,
    manufacturer_id bigint,
    created_at timestamp without time zone,
    updated_at timestamp without time zone,
    mpn text,
    visible boolean DEFAULT false NOT NULL
);

CREATE TABLE subscriptions (
    id bigint NOT NULL,
    product_id bigint NOT NULL,
    user_id bigint NOT NULL,
    created_at timestamp without time zone,
    updated_at timestamp without time zone
);
Run Code Online (Sandbox Code Playgroud)

在我的应用程序中,我通常需要知道某个产品的订阅者数量,而在应用程序中所有正确位置使用此逻辑是很棘手的。所以我想products用已经包含该信息的视图替换该表。所以我这样做了:

ALTER TABLE ONLY products
  RENAME TO products_raw;

CREATE VIEW products AS
  SELECT products_raw.*, COALESCE(a.subscriptions_count, 0) AS subscriptions_count
  FROM products_raw
  LEFT OUTER JOIN (
    SELECT b.product_id, COUNT(*) subscriptions_count
    FROM   subscriptions b
    GROUP BY b.product_id
  ) a ON a.product_id = products_raw.id;
Run Code Online (Sandbox Code Playgroud)

但是,products由于JOIN- 我希望在products_raw表上执行任何 INSERT/UPDATE/DELETE 操作,而subscriptions_count在适用时忽略虚拟,因此该视图不可自动更新。

这是我第一次创建视图或规则,但我尝试了以下规则:

CREATE RULE products_insert_rule AS ON INSERT TO products DO INSTEAD
  INSERT INTO products_raw VALUES(NEW.*);
Run Code Online (Sandbox Code Playgroud)

但是 Postgres 不喜欢这样:

PG::SyntaxError: ERROR:  INSERT has more expressions than target columns
LINE 13:         INSERT INTO products_raw VALUES(NEW.*);
                                                 ^
Run Code Online (Sandbox Code Playgroud)

我也尝试写一个INSTEAD OF INSERT触发器:

CREATE FUNCTION products_insert() RETURNS trigger AS $$
BEGIN
  INSERT INTO products_raw VALUES (NEW.*);
  RETURN NEW;
END; $$ LANGUAGE PLPGSQL;

CREATE TRIGGER products_insert INSTEAD OF INSERT ON products
  FOR EACH ROW EXECUTE PROCEDURE products_insert();
Run Code Online (Sandbox Code Playgroud)

但得到了基本相同的错误消息:

PG::SyntaxError: ERROR:  INSERT has more expressions than target columns
LINE 1: INSERT INTO products_raw VALUES (NEW.*)
                                         ^
QUERY:  INSERT INTO products_raw VALUES (NEW.*)
CONTEXT:  PL/pgSQL function products_insert() line 3 at SQL statement
: INSERT INTO "products" ("title", "description", "created_at", "updated_at") VALUES ($1, $2, $3, $4)
Run Code Online (Sandbox Code Playgroud)

我正在使用 Postgres 9.4。任何提示将不胜感激。

编辑:我更接近这个触发器,但由于我不确定如何将products_rawrowtype 转换为products. 我也不喜欢必须重复默认值(包括id序列)的方向......

CREATE FUNCTION products_insert() RETURNS trigger AS $$
DECLARE
    p products_raw%ROWTYPE;
BEGIN
  IF p.id IS NULL THEN
    p.id = NEXTVAL('products_id_seq');
  END IF;
  IF p.visible IS NULL THEN
    p.visible = false;
  END IF;
  INSERT INTO products_raw VALUES(p.*);

  RETURN p;
END; $$ LANGUAGE PLPGSQL;

CREATE TRIGGER products_insert INSTEAD OF INSERT ON products
  FOR EACH ROW EXECUTE PROCEDURE products_insert();
Run Code Online (Sandbox Code Playgroud)

我用上面的触发器得到的错误是

PG::DatatypeMismatch: ERROR:  returned row structure does not match the structure of the triggering table
DETAIL:  Number of returned columns (8) does not match expected column count (9).
CONTEXT:  PL/pgSQL function products_insert() during function exit
: INSERT INTO "products" ("title", "description", "created_at", "updated_at") VALUES ($1, $2, $3, $4)
Run Code Online (Sandbox Code Playgroud)

小智 5

您不能使用 NEW.*,因为视图包含基础表中不存在的列。您必须明确列出要添加的列。正如上面人们所解释的,这不起作用的原因是您没有提供默认值。您可能需要考虑使用 COALESCE() 来实现此目的。