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

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.

Related

Make a previously existing foreign key column have a unique constraint in postgres

I need to create a migration for an already existing table to make it's foreign key have a UNIQUE constraint. How do I do this?
From the examples I found in the documentation, it is mostly done when the table is created. The issue is I need to add this onto a column that already exists and is already set as a foreign key. This is what the table looks like at it's creation:
CREATE TABLE IF NOT EXISTS "myTable" (
"_id" SERIAL NOT NULL,
"myForeignKeyId" INTEGER NOT NULL,
"name" VARCHAR(255) NOT NULL,
CONSTRAINT "pk_myTable" PRIMARY KEY ("_id"),
CONSTRAINT "fk_myTable_myForeignKeyId" FOREIGN KEY ("myForeignKeyId") REFERENCES "myOtherTable" ("_id")
);
What I want to do is on a migration make myForeignKeyId unique. How do I do that?
I have tried to following:
CREATE UNIQUE INDEX CONCURRENTLY "myTable_myForeignKeyId"
ON province ("myForeignKeyId");
ALTER TABLE IF EXISTS "myTable"
ADD CONSTRAINT "myForeignKeyId"
UNIQUE USING INDEX "myTable_myForeignKeyId";
First off, when I try this in a migration I get the error:
CREATE INDEX CONCURRENTLY cannot run inside a transaction block
So that part cannot be done, but even just doing it through SQL, the second part doesn't work either as it claims myForeignKeyId already exists. Even if I add an ALTER COLUMN myForeignKeyId it just says there is an error on that line.
This seems like it should be a simple enough operation, how can I do this?
After digging some more found quite a simple way to do this, was clearly originally off target.
To add a unique constraint to a column:
ALTER TABLE "myTable"
ADD CONSTRAINT "myUniqueKeyNameOfChoice" UNIQUE ("myColumn");
To remove it:
ALTER TABLE "myTable"
DROP CONSTRAINT "myUniqueKeyNameOfChoice";

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.

VALIDATE CONSTRAINT sometimes validates, sometimes doesn't

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.

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.

T-SQL foreign key check constraint

When you create a foreign key constraint in a table and you create the script in MS SQL Management Studio, it looks like this.
ALTER TABLE T1 WITH CHECK ADD CONSTRAINT FK_T1 FOREIGN KEY(project_id)
REFERENCES T2 (project_id)
GO
ALTER TABLE T1 CHECK CONSTRAINT FK_T1
GO
What I don't understand is what purpose has the second alter with check constraint.
Isn't creating the FK constraint enough? Do you have to add the check constraint to assure reference integrity ?
Another question: how would it look like then when you'd write it directly in the column definition?
CREATE TABLE T1 (
my_column INT NOT NULL CONSTRAINT FK_T1 REFERENCES T2(my_column)
)
Isn't this enough?
First it creates the constraint and here you can specify whether data allready in the table should be checked or not against your new constraint. WITH { CHECK | NOCHECK }
The second part specifies that the constraint is enabled. ALTER TABLE TableName { CHECK | NOCHECK } CONSTRAINT ConstraintName
The second statement is compelled by the "WITH CHECK" in the first statement. There is a setting you can toggle to not do this.