If you are creating the parent and child within the same database transaction and you cannot control the insert order, then the right approach is a deferred constraint, as suggested by @Ragesh.
Otherwise, you guessed right: the solution is to write some sort of trigger.
Foreign key constraints in Postgres are little more than a pair of triggers, specifically:
- A trigger on
INSERT
(or UPDATE
of the FK) of the child, to check that the parent exists, and to acquire a KEY SHARE
lock to ensure that the parent doesn't disappear before the COMMIT
- A trigger on
DELETE
(or UPDATE
of the PK) of the parent to check that it has no children
In this case, you just want the parent's trigger, but not the child's. Luckily, it's pretty straightforward to write yourself. Something like this should do it:
create table parent (id int primary key);
create table child (id int primary key, parent_id int);
create function trg_parent_fk_check() returns trigger language plpgsql as $$
begin
if exists(select 1 from child where parent_id = old.id) then
raise foreign_key_violation using
message = 'parent.id=' || old.id || ' is still referenced by child.parent_id';
end if;
end
$$;
create trigger trg_parent_fk_check_del
after delete on parent
for each row
execute function trg_parent_fk_check();
/* You probably shouldn't be modifying primary keys, but just in case... */
create trigger trg_parent_fk_check_upd
after update of id on parent
for each row
when (old.id <> new.id)
execute function trg_parent_fk_check();
Of course, this approach allows for the possibility that the child arrives, but the parent doesn't. You may want to include some scheduled process to find and delete orphaned records.