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