-- Scenario 1: Outer command causes a foreign key trigger to be queued -- and this results in a window of time where we have records -- in the referencing table which don't yet exist in the -- referenced table. DROP TABLE IF EXISTS j1; DROP TABLE IF EXISTS j2; DROP TABLE IF EXISTS records_violating_fkey; CREATE TABLE j2 (id INT NOT NULL PRIMARY KEY); CREATE TABLE j1 ( id INT PRIMARY KEY, j2_id INT NOT NULL REFERENCES j2 (id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE ); INSERT INTO j2 VALUES(10),(20); INSERT INTO j1 VALUES(1,10),(2,20); -- create a table to store records that 'violate' the fkey. CREATE TABLE records_violating_fkey (j2_id INT NOT NULL); CREATE OR REPLACE FUNCTION j1_update() RETURNS TRIGGER AS $$ BEGIN RAISE notice 'Trigger fired'; INSERT INTO records_violating_fkey SELECT j2_id FROM j1 WHERE NOT EXISTS(SELECT 1 FROM j2 WHERE j2_id = j2.id); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER j1_update_trigger BEFORE UPDATE ON j2 FOR EACH ROW EXECUTE PROCEDURE j1_update(); UPDATE j2 SET id = id+1; -- returns 1 row. SELECT * FROM records_violating_fkey; ------------------------------------------------------------------------------ -- Scenario 2: Inner command causes a foreign key trigger to be queued. DROP TABLE IF EXISTS j1; DROP TABLE IF EXISTS j2; CREATE TABLE j2 (id INT NOT NULL PRIMARY KEY); CREATE TABLE j1 ( id INT PRIMARY KEY, j2_id INT NOT NULL REFERENCES j2 (id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE ); INSERT INTO j2 VALUES(10),(20); INSERT INTO j1 VALUES(1,10),(2,20); CREATE OR REPLACE FUNCTION update_j2(p_id int) RETURNS int AS $$ BEGIN RAISE NOTICE 'Updating j2 id = % to %', p_id, p_id + 1; UPDATE j2 SET id = id + 1 WHERE id = p_id; RETURN 1; END; $$ LANGUAGE plpgsql; -- try and get some records to be returned by causing an update on the record that is not the current record. SELECT * FROM j1 WHERE NOT EXISTS(SELECT 1 FROM j2 WHERE j2_id = j2.id) AND update_j2((SELECT MIN(j2_id) FROM j1 ij1 WHERE ij1.j2_id <> j1.j2_id)) = 1;