VALIDATE CONSTRAINT sometimes validates, sometimes doesn't - postgresql

I have a table named base_types that contains this constraint:
ALTER TABLE public.base_types
ADD CONSTRAINT base_type_gas_type_fk FOREIGN KEY (gas_type)
REFERENCES public.gas_types (gas_type) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
And I have a table named alarm_history that contains five constraints, including this one:
ALTER TABLE public.alarm_history
ADD CONSTRAINT alarm_history_device_fk FOREIGN KEY (device)
REFERENCES public.bases (alarm_device) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
I am trying to convert a database from one that didn't bother with anything weird and useless like constraints into one that uses them. I am beginning with this script:
delete from gas_types;
select conversion.convert_base_types();
alter table base_types validate constraint base_type_gas_type_fk;
select conversion.convert_alarm_history();
alter table alarm_history validate constraint alarm_history_base_fk;
alter table alarm_history validate constraint alarm_history_charge_fk;
alter table alarm_history validate constraint alarm_history_cooler_fk;
alter table alarm_history validate constraint alarm_history_device_fk;
alter table alarm_history validate constraint alarm_history_furnace_fk;
I duly get an error message telling me that the gas_type field in my new base_types record doesn't match anything in the gas_types table, since the gas_types table is empty. But if I comment out the base_types commands, I get 18,000 nice, shiny new records in the alarm_history table, despite the fact that every single one of them violates at least one of that table's five foreign key constraints, since all of the tables those keys are referring to are empty. I need to ensure that my converted data is consistent, and therefore I need to validate my constraints, but that's obviously not happening. Why not?

Since the constraints above are created as DEFERRABLE INITIALLY DEFERRED, they are not checked until the DML statements (your delete statement) are committed or in your case you until you explicitly validate the constraint.
This is the normal and expected operation of an initially deferred deferrable constraint.
To change this functionality within your current transaction you can issue a SET CONSTRAINTS command to alter this:
SET CONSTRAINTS alarm_history_device_fk IMMEDIATE;
delete from gas_types;
Which should raise a foreign key violation alerting you earlier that you have data dependent on the records you are tying to delete.

Related

postgres not setting foreign key to null when truncating

I'm trying to truncate a set of tables, but it keeps complaining about a foreign key.
but that foreign key is set to on delete Set null
to reproduce:
create table test_players (id SERIAL PRIMARY KEY, name VARCHAR(255));
create table test_items (id SERIAL PRIMARY KEY, name VARCHAR(255), player_id INTEGER FOREIGN KEY (player_id) REFERENCES test_players(id) ON DELETE SET NULL);
now if you truncate the test_players it will complain:
ERROR: cannot truncate a table referenced in a foreign key constraint
DETAIL: Table "test_items" references "test_players".
HINT: Truncate table "test_items" at the same time, or use TRUNCATE ... CASCADE.
SQL state: 0A000
what must I do to make me be able to delete test_players without deleting the test_items?
You cannot do what you are attempting. You will have to do this in 3 steps.
Update test_items and for each player_id. Well technically you don't need this, but if you don't give yourself data integrity issues.
Drop the test_items to test_players FK.
Then truncate test_players
The reason is that truncate basically just zaps the table, it does NOT process individual rows. Therefore it would not process the FK set null, it throws the error you got instead. In fact even if the child table is empty, or for that matter even if the parent is empty. See fiddle here. The fiddle also contains a function to do it, and a test for it.
The of course you could just Delete from test_players and let the triggers take care of updating test_items. Takes longer, esp if larger table, but you keep your FK. Of course there's
Recreate your FK.

Why doesn't VALIDATE CONSTRAINT find this foreign key violation?

I don't think I understand what VALIDATE CONSTRAINT is supposed to do. I am converting a database for use in the upcoming major upgrade of my company's flagship software product. Past developers were lazy, and didn't bother specifying foreign keys where they should have. For the new version of our product, appropriate foreign keys will be specified and enforced.
So, I want to import data into my new database and then make sure there are no foreign key violations. After wrestling with when transactions begin and end and dealing with circular keys and getting nowhere at amazing speed, I decided merely disable all triggers on all tables (which disables foreign key constraint checking in PostgreSQL, since they use triggers under the hood), import my data, re-enable the triggers, and then issue a VALIDATE CONSTRAINT command. However, in my little test script, the validation fails to find any constraint violations. Why not?
Here's the test script. I am creating a table named gas_types_minimum with a column named gas_type. I am not creating any records in that table. Then, I create a table named base_types_minimum with a column named base_type and a column named gas_type. I disable its triggers so I can insert a base_type record even though there is no gas_type record. Then, I insert a Hydrogen base type with a gas type of 'H2'. Then, I turn triggers back on and validate the constraint. I get no error.
DROP TABLE IF EXISTS base_types_minimum;
DROP TABLE IF EXISTS gas_types_minimum;
CREATE TABLE public.gas_types_minimum
(
gas_type character varying(32) COLLATE pg_catalog."default",
CONSTRAINT gas_type_minimum_pkey PRIMARY KEY (gas_type)
);
CREATE TABLE public.base_types_minimum
(
base_type character varying(32) COLLATE pg_catalog."default" NOT NULL,
gas_type character varying(32) COLLATE pg_catalog."default",
CONSTRAINT base_type_minimum_pkey PRIMARY KEY (base_type),
CONSTRAINT base_type_minimum_gas_type_minimum_fk FOREIGN KEY (gas_type)
REFERENCES public.gas_types_minimum (gas_type) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
);
alter table base_types_minimum disable trigger all;
insert into base_types_minimum values ('Hydrogen', 'H2');
alter table base_types_minimum enable trigger all;
alter table base_types_minimum validate constraint base_type_minimum_gas_type_minimum_fk;
The reason is that the foreign key constraint is already marked as valid, so it is not checked.
VALIDATE CONSTRAINT is only useful for constraints that were defined as NOT VALID, which your constraint was not. There is no supported way to invalidate a constraint later on, because it is not considered useful.
By disabling the triggers you effectively broke integrity, and there is no way to recover. That is why you can only disable a trigger that implements a foreign key if you are a superuser (these are expected to know what they are doing).
The best thing for you to do is to drop the broken foreign key constraint.
There is one – unsupported! – way how you can mark the constraint invalid:
UPDATE pg_catalog.pg_constraint
SET convalidated = FALSE
WHERE conname = 'base_type_minimum_gas_type_minimum_fk';
You can only do that as superuser, and I don't recommend it. Just drop that foreign key constraint.

How to set FK relationship to NULL in postgreSQL

How to set FOREIGN KEY (packet_id) REFERENCES girvi_packet(id) DEFERRABLE INITIALLY DEFERRED to NULL ?
If parent get deleted postgreSQL raises error that parent is missing I want to suppress this behavior. This constraint is added by default by some 3rd part tool. But my use case demands otherwise.
The purpose of the foreign key is to prevent orphan children. The only way you can do is to delete the constrain
ALTER TABLE ... DROP CONSTRAINT ...

What happens when two equal foreign keys with conflicting on-deletes are defined on the same table in PostgreSQL?

In order to delete some rows referenced by a foreign key constraint without cascading on delete, I created a temporary foreign key constraint, deleted the row, and then deleted the temporary constraint:
ALTER TABLE rel_user_right
ADD CONSTRAINT temp_fk_rel_user_right_user_right_02
FOREIGN KEY (right_id) REFERENCES user_right (id)
ON DELETE CASCADE;
DELETE FROM user_right WHERE "name" LIKE '%.statusLight.%';
ALTER TABLE rel_user_right
DROP CONSTRAINT temp_fk_rel_user_right_user_right_02;
where this table already had the following constraint defined on it:
ALTER TABLE rel_user_right
ADD CONSTRAINT fk_rel_user_right_user_right_02
FOREIGN KEY (right_id) REFERENCES user_right (id);
This worked fine for me, but seems to have failed on my colleague's computer. As you can see, the two FK constraints define conflicting ON DELETE behaviour. Is precedence defined in this situation, or is it non-deterministic?
Postgres allows to create two references differing only in ON DELETE clause.
I could find no information on the impact of such a case.
In my tests I was unable to cover the existing constraint with new one (i.e. DELETE was always restricted despite of the existence of the second cascading constraint).
However this behaviour is undocumented and one should not rely on it.
The normal way to proceed should be replacing the old constraint with new one:
ALTER TABLE rel_user_right
ADD CONSTRAINT fk_rel_user_right_user_right_temp
FOREIGN KEY (right_id) REFERENCES user_right (id)
ON DELETE CASCADE,
DROP CONSTRAINT fk_rel_user_right_user_right;
DELETE FROM user_right WHERE "name" LIKE '%.statusLight.%';
ALTER TABLE rel_user_right
ADD CONSTRAINT fk_rel_user_right_user_right
FOREIGN KEY (right_id) REFERENCES user_right (id),
DROP CONSTRAINT fk_rel_user_right_user_right_temp;
DISABLE CONSTRAINT would be useful here, but there is no such feature in Postgres (there have been attempts to implement it, but they did not end in success). You can use DISABLE TRIGGER for it, but the above solution is simpler and more natural.

ALTER TABLE [dbo].[MyTable] CHECK CONSTRAINT [FK_MyTable_SomeCol]

If I script a table with a foreign key, it looks like this:
GO
ALTER TABLE [dbo].[MyTable] WITH CHECK ADD CONSTRAINT [FK_MyTable_SomeCol] FOREIGN KEY([SomeCol])
REFERENCES [dbo].[MyOtherTable] ([SomeCol])
GO
ALTER TABLE [dbo].[MyTable] CHECK CONSTRAINT [FK_MyTable_SomeCol]
GO
What is the second part for (ALTER TABLE [dbo].[MyTable] CHECK CONSTRAINT [FK_MyTable_SomeCol])?
It's an artifact of the way that the constraint is scripted - although it's unnecessary to specify these options (since they're the defaults for new constraints), the same generator can also generate NOCHECK options in exactly the same manner.
Documentation for ALTER TABLE indicates two distinct uses of CHECK/NOCHECK:
WITH CHECK | WITH NOCHECK
Specifies whether the data in the table is or is not validated against a newly added or re-enabled FOREIGN KEY or CHECK constraint. If not specified, WITH CHECK is assumed for new constraints, and WITH NOCHECK is assumed for re-enabled constraints.
And:
{ CHECK | NOCHECK } CONSTRAINT
Specifies that constraint_name is enabled or disabled.
So one option is saying "check the current contents of the table", the other is saying "Validate new data as it is added".
This is a way of implementing referential integrity for your tables.