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 forINSERT INTO PurchaseView(product_name, when_bought) VALUES ('foo', NOW()) RETURNING *;
SELECT NEW.product_name
and so on. Then you don't join, theFROM
clause will have only the other table, the join condition goes intoWHERE
. – dezso Jul 18 '16 at 15:55NATURAL JOIN
). Also not sure how best to deal withLEFT JOINS
as in the example I've given. – beldaz Jul 18 '16 at 20:14INSTEAD OF
trigger for aVIEW
- especially if the VIEW is large. If yourVIEW
is defined asSELECT * FROM TABLE JOIN TABLE JOIN TABLE ..
then you should probably redefine yourVIEW
so that it doesn't useSELECT *
.. – Joishi Bodio Jul 18 '16 at 21:16