I'm having trouble getting an INSTEAD OF trigger to work correctly, and I think I've misunderstood how to use NEW. Consider the following simplified scenario:
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);
I'd like to be able to create INSTEAD OF
triggers to allow me to insert directly into PurchaseView
, e.g.:
INSERT INTO Product(product_name) VALUES ('foo');
INSERT INTO PurchaseView(product_name, when_bought) VALUES ('foo', NOW());
What I had in mind was something along the lines of:
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();
However the above trigger function gives errors (relation "new" does not exist
) when executed. I know I can write queries that explicitly use attributes from NEW
in the WHERE
and SELECT
clauses, but sometimes it would be convenient to be able to include NEW
in a join. Is there a way to do this?
Current (unsatisfactory) solution
The closest I can get to what I want is
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;
This is a little unsatisfactory for several reasons:
I need to explicitly write all the attributes of
NEW
in CTE WITH query, which for large views (especially those whose attributes are automatically determined withSELECT *
) gets unwieldy;The returned result doesn't have the
SERIAL
typeproduct_id
updated, so you don't get the expected result for:INSERT INTO PurchaseView(product_name, when_bought) VALUES ('foo', NOW()) RETURNING *;