Search code examples
sqlpostgresqlplpgsqldatabase-triggersql-view

Returning a column from an INSERTED record via an updateable view


I have a view I need to allow my users to update and insert on. Importantly, when they Insert, they need to be able to return the new value from the Inserted row, however right now they get NULL. It must be a view, as in the SELECT of the view, it needs to be able to return values that are the result of a join.

My underlying table definitions:

CREATE TABLE my_assets (
    asset_id bigserial not null primary key,
    asset_price NUMERIC(32,10) -- This will vary constantly via an independent process
);

CREATE TABLE my_transactions (
    id bigserial not null primary key,
    asset_id bigint not null REFERENCES my_assets(asset_id),
    some_text varchar(100)
);
INSERT INTO my_assets(asset_price) SELECT 100 as asset_price;

My view that shows the result of the table:

CREATE VIEW my_transactions_view AS
SELECT tx.id, tx.asset_id, tx.some_text, a.asset_price
FROM my_transactions tx
JOIN my_assets a ON tx.asset_id = a.asset_id

My trigger that allows insertion into my_transactions_view:

CREATE OR REPLACE FUNCTION trigfx_insert_to_my_transactions_view()
RETURNS trigger AS
    $BODY$
BEGIN
    INSERT INTO my_transactions(asset_id, some_text)
    SELECT NEW.asset_id, NEW.some_text;
    RETURN NEW;
END
    $BODY$
    LANGUAGE 'plpgsql';

CREATE TRIGGER trig_my_transactions_view INSTEAD OF INSERT on my_transactions_view
FOR EACH ROW EXECUTE PROCEDURE trigfx_insert_to_my_transactions_view();

All good so far. However, the problem arises from trying to run the below SQL:

INSERT INTO my_transactions_view(asset_id, some_text)
    SELECT 1 as asset_id, 'Hello World' as some_text
    RETURNING id, asset_id, some_text;

The returned table returns NULL for ID, but I want to return the newly updated ID from the my_transactions table:

|---------------------|------------------|------------------|
|         ID          |     asset_id     |    some_text     |
|---------------------|------------------|------------------|
|        null         |         1        |  Hello World     |
|---------------------|------------------|------------------|

Running a subsequent SELECT * FROM my_transactions_view DOES produce the updated result:

|------------------|------------------|------------------|------------------|
|         ID       |     asset_id     |    some_text     |    asset_price   |
|------------------|------------------|------------------|------------------|
|         1        |         1        |  Hello World     |  100.0000000     |
|------------------|------------------|------------------|------------------|

but I need it produced during the RETURNING of the INSERT statement.

Thank you!!!


Solution

  • Turns out we can avoid the extra function call via SELECT INTO from a CTE:

    CREATE OR REPLACE FUNCTION trigfx_insert_to_my_transactions_view()
    RETURNS trigger AS
        $BODY$
    BEGIN
        WITH ins_q as (INSERT INTO my_transactions(asset_id, some_text)
        values (NEW.asset_id, NEW.some_text)
        RETURNING id, asset_id, some_text)
        SELECT ins_q.id, ins_q.asset_id, ins_q.some_text
        INTO NEW.id, NEW.asset_id, NEW.some_text
        FROM ins_q;
        RETURN NEW;
    END
    $BODY$
    LANGUAGE plpgsql;
    

    See online example here. I was running into initialisation errors (lastval is not yet defined in this session) with the new.id := lastval(); approach.