I have these two tables in a PostgreSQL 12 database:
CREATE TABLE public.test (
entity_id uuid PRIMARY KEY DEFAULT uuid_generate_v4()
);
CREATE TABLE public.test_registration (
entity_id uuid REFERENCES test(entity_id),
row_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
entry_name text,
code text
);
entry_name
is populated by a trigger AFTER INSERT
:
CREATE OR REPLACE FUNCTION test_registration_entry_name()
RETURNS trigger AS
$$
BEGIN
UPDATE test_registration
SET entry_name = new_inserts.code
FROM new_inserts
WHERE new_inserts.row_id = test_registration.row_id;
RETURN NULL;
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER test_insert_trigger
AFTER INSERT ON test_registration
REFERENCING NEW TABLE AS new_inserts
FOR EACH STATEMENT
WHEN (pg_trigger_depth() = 0)
EXECUTE PROCEDURE test_registration_entry_name();
The trigger function differs from table to table.
My current issue is when I insert a row into test_registration
as such:
INSERT INTO test DEFAULT VALUES
RETURNING entity_id; -- let's say: 'a17e66c5-1049-4ba9-bed3-90bbc823e064'
And then insert a registration and return all row values for the inserted row:
INSERT INTO test_registration (entity_id,code)
VALUES ('a17e66c5-1049-4ba9-bed3-90bbc823e064'::uuid,'EB')
RETURNING *
I get all column values correctly, except for the entry_name
which is null. Which makes sense because entry_name
is being set after the insert.
So I tried to expose this by altering my INSERT
using the row_id
as such:
WITH create_query AS (INSERT INTO test_registration (entity_id,code)
VALUES ('a17e66c5-1049-4ba9-bed3-90bbc823e064'::uuid,'13')
RETURNING *) SELECT v.entity_id, v.row_id, b.entity_id,b.entry_name, b.row_id
from create_query AS v, test_registration AS b WHERE V.Row_id = b.Row_id
This then turns out to not return anything. create_query.row_id
seems to differ from the one in test_registration
– which makes no sense? Why would they be different?
How can I extract all column values for the inserted row, after the triggers has been triggered.
Best Answer
Impossible with an
AFTER
triggerThe version of the row visible to the
RETURNING
clause is the same version that's visible to anAFTER
trigger. TheUPDATE
in your trigger function creates a new row version. It's impossible to see the effects in theRETURNING
clause, which is based on the prior row version. Demo:db<>fiddle here
Effectively, the output of
RETURNING
is "dead on arrival", obsoleted immediately by theAFTER
trigger. You would need a separateSELECT
to see the new row version. (Can be in the same transaction.)Impossible with a CTE
Your query is bound to return nothing:
All sub-statements of a query with CTEs see the same snapshot of the database. Since the
SELECT
cannot yet see the row inserted in the CTE (androw_id
is populated withuuid_generate_v4()
) your implicitJOIN
is bound to return no row.See:
Proper trigger
I see nothing to warrant a
STATEMENT
-level, nor the need for anAFTER
trigger. Use a much simpler (and faster)ROW
-level triggerBEFORE
. ThenRETURNING *
will just work:db<>fiddle here
(The fiddle operates with integers because the additional module uuid-ossp providing
uuid_generate_v4()
is not installed on dbfiddle.uk. But it works all the same.)I added another (optional) trigger condition:
NEW.entry_name IS DISTINCT FROM NEW.code
to avoid needless work if the trigger wouldn't change anything.Related:
Aside: more efficient
INSERT
Do both inserts in a single query with a data-modifying CTE like this:
Then you can immediately reuse the auto-generated
entity_id
without another round-trip to the server. See: