web-dev-qa-db-ja.com

結合を更新するこのPostgresビューを更新可能にするにはどうすればよいですか?

productssubscriptionsの2つのテーブルがあります。

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
);

私のアプリケーションでは、通常、製品のサブスクライバーの数を知る必要があります。このロジックをアプリケーションのすべての正しい場所に散在させるのは難しいです。したがって、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;

ただし、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.*);

しかしPostgresはそれが好きではありません:

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

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();

しかし、基本的に同じエラーメッセージが表示されます。

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)

Postgres 9.4を使用しています。任意のヒントをいただければ幸いです。

編集:このトリガーで少し近づきましたが、products_raw行型を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();

上記のトリガーで発生するエラーは

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)
5
Abe Voelker

基になるテーブルに存在しない列がビューに含まれているため、NEW。*は使用できません。追加する列を明示的にリストする必要があります。上で説明したように、これが機能しない理由は、デフォルトを指定しなかったためです。この目的でCOALESCE()を使用することを検討してください。

3
user78471