I have a problem regarding ON INSERT BEFORE triggers firing on rows that are inserted via insert on conflict update upserts.
I have a postgres (v.17) database with a base table or super table that 5 other tables have a one-to-one relation to or "extend" (a sort of class table inheritance). So for every row in one of this four tables there must be a corresponding row in the base table.
CREATE TABLE super_table (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
-- other fields
);
CREATE TABLE sub_table1(
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMAR KEY,
super_id BIGINT REFERENCES super_table UNIQUE
-- other fields
);
-- other sub_tables ...
This class table inheritance warrants that several rules are fullfilled:
- every row in a subtable always has a corresponding row in the super table
- for a given row in a subtable the super_table_id always stays the same
- if a row in a subtable is deleted the corresponding row in the super table is deleted
- ... and some more
Relevant with regard to my problem are the first two rules. To ensure that they are fullfilled I use two triggers. An on insert and and on update trigger.
On every of the 5 tables an ON INSERT trigger creates a new row in the base table and returns the id:
IF NEW.super_id IS NULL THEN
INSERT INTO super_table (type, created_at, updated_at, is_deletable)
VALUES ('some_type', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, false)
RETURNING id INTO NEW.super_id;
END IF;
RETURN NEW;
Correspondingly there's an ON UPDATE Trigger that ensures that no matter what base_id is always set to NEW.super_id = OLD.super_id to guarantee that there will be only one row in the super_table in any of the subtables.
Problem:
My problem arises when I do Batch inserts with ON CONFLICT: That leads to both triggers firing, also on rows that are updated. The reason is that a before insert trigger will fire during insert on conflict operations before a conflict arises if a unique constraint is violated and postgres trys an update. This behaviour leads to a situation where for all inserts that resolve to updates the On insert trigger fires and creates a new row in the super table. Then the conflict arises and the operatio is aborted and postgres tries an update. That leads to this new super_table_id not inserted in the row in the subtable. Instead the on update trigger then ensures that the OLD.super_table_id is used. So integrity is preserved and for updated rows the super_table_id stays the same but a lot of usless rows are created in the super_table.
I thought about adding a new field super_table_id_created BOOLEAN to every subtable and make super_table_id NULL so that I could change the ON INSERT trigger to AFTER which would ensure that it fires during INSERT ON CONFLICT operations only after a conflict arises and thus only for rows that are actually inserted not updated. The on update trigger could then use this field.
Does anyone know a solution that either:
- allows me to maintain the super_table and subtables without triggers?
- or uses a differen approach to ensure integrity on a database level?