Fixing broken foreign keys with ALTER TABLE ... WITH CHECK CHECK CONSTRAINT - sql-server

I have a broken foreign key in SQL Server 2005. Here is a reproduction:
CREATE TABLE t2(i2 BIGINT NOT NULL PRIMARY KEY)
CREATE TABLE t1(i1 BIGINT NOT NULL PRIMARY KEY)
ALTER TABLE t1 ADD CONSTRAINT fk FOREIGN KEY (i1) REFERENCES t2 (i2)
ALTER TABLE t1 NOCHECK CONSTRAINT fk
INSERT INTO t1 (i1) VALUES (0)
If I subsequently run:
ALTER TABLE t1 WITH CHECK CHECK CONSTRAINT fk
I get the error:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "fk". The conflict occurred in database "broken-fk", table "dbo.t2", column 'i2'.
I can fix this manually:
DELETE FROM t1 WHERE NOT EXISTS (SELECT 1 FROM t2 WHERE t2.i2 = t1.i1)
ALTER TABLE t1 WITH CHECK CHECK CONSTRAINT fk
but I'd prefer to do it in one step, as each presumably needs a table scan and the table is tens of gigabytes in size.
Is it possible to get the ALTER TABLE to fix the error (by dropping rows) rather than exiting with an error?
Thanks.

Is it possible to get the ALTER TABLE to fix the error
(by dropping rows) rather than exiting with an error?
The long and short - no.
Do it in two steps as you are familiar with.
You could stop at
ALTER TABLE t1 CHECK CONSTRAINT fk
which turns it on for new records but leaves the existing data alone?

Related

Deleting rows without checking foreign indexes, triggers, etc in SQL Server

I have a table that is referenced by 11 other tables as foreign key constraint. If I try to delete a row from it, apparently it checks the other tables to see if it should cascade delete other records, which is making the delete take too long. I've tried disabling constraints in the table I'm deleting and the other tables but it's not working (on the results panel I still get 11 lines of "0 rows affected").
I've already deleted the children, what should I so it doesn't go on these tables to check for cascades? Here's how I've been testing this:
BEGIN TRANSACTION
ALTER TABLE Table1 NOCHECK CONSTRAINT ALL
ALTER TABLE Table2 NOCHECK CONSTRAINT ALL
DELETE FROM Table2
WHERE id IN (100, 101)
ALTER TABLE Table2 CHECK CONSTRAINT ALL
ALTER TABLE Table1 CHECK CONSTRAINT ALL
ROLLBACK
BTW: You are in pretty dangerous position of breaking data integrity. Try this code:
CREATE TABLE C1(ID INT IDENTITY(1,1) PRIMARY KEY);
GO
INSERT INTO C1 DEFAULT VALUES;
GO 10
GO
SELECT * INTO C2 FROM C1;
GO
ALTER TABLE C2 WITH CHECK ADD CONSTRAINT FK_C12 FOREIGN KEY(ID) REFERENCES C1 (ID);
GO
BEGIN TRANSACTION
SELECT is_not_trusted from sys.foreign_keys WHERE name = 'FK_C12';
ALTER TABLE C1 NOCHECK CONSTRAINT ALL
ALTER TABLE C2 NOCHECK CONSTRAINT ALL
DELETE FROM C1 WHERE ID = 5;
ALTER TABLE C2 CHECK CONSTRAINT ALL
ALTER TABLE C1 CHECK CONSTRAINT ALL
SELECT is_not_trusted from sys.foreign_keys WHERE name = 'FK_C12';
ROLLBACK
That means after your FK is not trusted anymore you might come up with different/slower execution plans and an additional and maybe unnecessary I/O

Adding constraint conflicts with itself, even though it doesn't exist yet

I'm adding delete cascading to a Table. The Clone table has a column DeviceID that is a foreign key to the Device table's DeviceID column. So the SQL script drops the original FK constraint, and attempts to add the new one:
IF EXISTS
(
SELECT *
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_NAME = 'FK_Clone_Device'
)
BEGIN
ALTER TABLE Clone
DROP CONSTRAINT FK_Clone_Device
END
IF NOT EXISTS
(
SELECT *
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_NAME = 'FK_Clone_Device_Cascade'
)
BEGIN
ALTER TABLE Clone
ADD CONSTRAINT FK_Clone_Device_Cascade
FOREIGN KEY (DeviceID) REFERENCES Device(DeviceID) ON DELETE CASCADE
END
When I run this script, I get the following error:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_Clone_Device_Cascade". The conflict occurred in database "DevelopmentDB", table "dbo.Device", column 'DeviceID'.
Maybe I'm misunderstanding the error message, but it sounds like it's conflicting with itself. I'm confused that it happened on the Device table though.
There is an index in the Clone table on DeviceID. Would that matter?
This is on SQL SERVER R2 (Azure)
Sounds like you currently have data in the table that would violate the FK you are trying to create. One way to test this is to add "WITH (NOCHECK)" to the ALTER TABLE statement and see if it lets you create the constraint.
If it does let you create the constraint with NOCHECK, you can either leave it that way and the constraint will only be used to test future inserts/updates, or you can investigate your data to fix the FK violations.
So your example would be:
ALTER TABLE Clone WITH NOCHECK
ADD CONSTRAINT FK_Clone_Device_Cascade
FOREIGN KEY (DeviceID) REFERENCES Device(DeviceID) ON DELETE CASCADE

How to insert data with the cross-referenced tables?

I have two tables. The table structure is given below.
CREATE TABLE t1
(
col1 int PRIMARY KEY,
col2 int REFERENCES t2(col4)
)
And
CREATE TABLE t2
(
col3 int PRIMARY KEY,
col4 int REFERENCES t1(col1)
)
Now how do I populate the tables using INSERT statement. I can do it by inserting a NULL value in the FOREIGN KEY COLUMN and then UPDATE that column. But I don't want to use the UPDATE statement. I want to do it using a INSERT statement.
How should I do it?
I sense a bad design decision here. If update is not an option, then disabling the constraint, inserting data and then re-enabling the constraint can be considered, but that is also an inherently bad approach, as the very purpose of enforcing those constraints in the first place is defeated.
Anyway, here is how:
ALTER TABLE t1 NOCHECK CONSTRAINT <YourFKConstraint>
ALTER TABLE t2 NOCHECK CONSTRAINT <YourFKConstraint>
GO
INSERT INTO t1
VALUES (1,1)
GO
INSERT INTO t2
VALUES (1,1)
GO
ALTER TABLE t1 CHECK CONSTRAINT <YourFKConstraint>
ALTER TABLE t2 CHECK CONSTRAINT <YourFKConstraint>
Please be warned that this approach will allow you to enter data that violates the constraint. Redesign would be the best option.

Add primary key column in SQL table

I am student of RDBMS.
I have very basic question let say I have one existing Table in SQL server. What will be script to alter table.
Drop Column 'RowId' if exist.
Drop contraint if exist.
Add one new column 'RowId' into table.
Make this column as primary key.
Autoincrement type int.
In SQL Server 2005 or newer, you could use this script:
-- drop PK constraint if it exists
IF EXISTS (SELECT * FROM sys.key_constraints WHERE type = 'PK' AND parent_object_id = OBJECT_ID('dbo.YourTable') AND Name = 'PK_YourTable')
ALTER TABLE dbo.YourTable
DROP CONSTRAINT PK_YourTable
GO
-- drop column if it already exists
IF EXISTS (SELECT * FROM sys.columns WHERE Name = 'RowId' AND object_id = OBJECT_ID('dbo.YourTable'))
ALTER TABLE dbo.YourTable DROP COLUMN RowId
GO
-- add new "RowId" column, make it IDENTITY (= auto-incrementing)
ALTER TABLE dbo.YourTable
ADD RowId INT IDENTITY(1,1)
GO
-- add new primary key constraint on new column
ALTER TABLE dbo.YourTable
ADD CONSTRAINT PK_YourTable
PRIMARY KEY CLUSTERED (RowId)
GO
Of course, this script may still fail, if other tables are referencing this dbo.YourTable using foreign key constraints onto the pre-existing RowId column...
Update: and of course, anywhere I use dbo.YourTable or PK_YourTable, you have to replace those placeholder with the actual table / constraint names from your own database (you didn't mention what they were, in your question.....)
Note: this answer was added before questions update
Add new column (note: you can only have one IDENTITY column per table)
Drop old primary key
Add new primary key
Drop old column if needed
Sample script:
CREATE TABLE whatever (
OldPKColumn uniqueidentifier NOT NULL,
CONSTRAINT PK_whatever PRIMARY KEY (OldPKColumn)
)
ALTER TABLE whatever
ADD RowId int NOT NULL IDENTITY (1,1);
ALTER TABLE whatever
DROP CONSTRAINT PK_whatever;
ALTER TABLE whatever WITH CHECK
ADD CONSTRAINT PK_whatever PRIMARY KEY CLUSTERED (RowId);
ALTER TABLE whatever
DROP COLUMN oldPKcolumn;
And a random thought... are you trying to reset an IDENTITY column?
If so, then use DBCC CHECKIDENT
Just a comment to improve these great answers (can't use comments yet - I'm one reputation point away from that privilege) and as future reference for myself:
A new IDENTITY (autonumber) column can be added and made the primary key in a single statement as well:
ALTER TABLE [TableName] ADD [ColumnName] int IDENTITY PRIMARY KEY;
I prefer not to bother with constraint names when it doesn't help.
You can specify seed (and increment) values between parantheses after the IDENTITY keyword.

How do I use cascade delete with SQL Server?

I have 2 tables: T1 and T2, they are existing tables with data. We have a one to many relationship between T1 and T2. How do I alter the table definitions to perform cascading delete in SQL Server when a record from T1 is deleted, all associated records in T2 also deleted.
The foreign constraint is in place between them. I don't want to drop the tables or create a trigger to do the deletion for T2. For example, when I delete an employee, all the review record should be gone, too.
T1 - Employee,
Employee ID
Name
Status
T2 - Performance Reviews,
Employee ID - 2009 Review
Employee ID - 2010 Review
To add "Cascade delete" to an existing foreign key in SQL Server Management Studio:
First, select your Foreign Key, and open it's "DROP and Create To.." in a new Query window.
Then, just add ON DELETE CASCADE to the ADD CONSTRAINT command:
And hit the "Execute" button to run this query.
By the way, to get a list of your Foreign Keys, and see which ones have "Cascade delete" turned on, you can run this script:
SELECT
OBJECT_NAME(f.parent_object_id) AS 'Table name',
COL_NAME(fc.parent_object_id,fc.parent_column_id) AS 'Field name',
delete_referential_action_desc AS 'On Delete'
FROM sys.foreign_keys AS f,
sys.foreign_key_columns AS fc,
sys.tables t
WHERE f.OBJECT_ID = fc.constraint_object_id
AND t.OBJECT_ID = fc.referenced_object_id
ORDER BY 1
And if you ever find that you can't DROP a particular table due to a Foreign Key constraint, but you can't work out which FK is causing the problem, then you can run this command:
sp_help 'TableName'
The SQL in that article lists all FKs which reference a particular table.
Hope all this helps.
Apologies for the long finger. I was just trying to make a point.
You will need to,
Drop the existing foreign key constraint,
Add a new one with the ON DELETE CASCADE setting enabled.
Something like:
ALTER TABLE dbo.T2
DROP CONSTRAINT FK_T1_T2 -- or whatever it's called
ALTER TABLE dbo.T2
ADD CONSTRAINT FK_T1_T2_Cascade
FOREIGN KEY (EmployeeID) REFERENCES dbo.T1(EmployeeID) ON DELETE CASCADE
You can do this with SQL Server Management Studio.
→ Right click the table design and go to Relationships and choose the foreign key on the left-side pane and in the right-side pane, expand the menu "INSERT and UPDATE specification" and select "Cascade" as Delete Rule.
Use something like
ALTER TABLE T2
ADD CONSTRAINT fk_employee
FOREIGN KEY (employeeID)
REFERENCES T1 (employeeID)
ON DELETE CASCADE;
Fill in the correct column names and you should be set. As mark_s correctly stated, if you have already a foreign key constraint in place, you maybe need to delete the old one first and then create the new one.
ON DELETE CASCADE
It specifies that the child data is deleted when the parent data is deleted.
CREATE TABLE products
( product_id INT PRIMARY KEY,
product_name VARCHAR(50) NOT NULL,
category VARCHAR(25)
);
CREATE TABLE inventory
( inventory_id INT PRIMARY KEY,
product_id INT NOT NULL,
quantity INT,
min_level INT,
max_level INT,
CONSTRAINT fk_inv_product_id
FOREIGN KEY (product_id)
REFERENCES products (product_id)
ON DELETE CASCADE
);
For this foreign key, we have specified the ON DELETE CASCADE clause which tells SQL Server to delete the corresponding records in the child table when the data in the parent table is deleted. So in this example, if a product_id value is deleted from the products table, the corresponding records in the inventory table that use this product_id will also be deleted.
First To Enable ONCascade property:
1.Drop the existing foreign key constraint
2.add a new one with the ON DELETE CASCADE setting enabled
Ex:
IF EXISTS(SELECT 1 FROM sys.foreign_keys WHERE parent_object_id = OBJECT_ID(N'dbo.Response'))
BEGIN
ALTER TABLE [dbo].[Response] DROP CONSTRAINT [FK_Response_Request]
ALTER TABLE [dbo].[Response] WITH CHECK ADD CONSTRAINT [FK_Response_Request] FOREIGN KEY([RequestId])
REFERENCES [dbo].[Request] ([RequestId])
ON DELETE CASCADE
END
ELSE
BEGIN
ALTER TABLE [dbo].[Response] WITH CHECK ADD CONSTRAINT [FK_Response_Request] FOREIGN KEY([RequestId])
REFERENCES [dbo].[Request] ([RequestId])
ON DELETE CASCADE
END
Second To Disable ONCascade property:
1.Drop the existing foreign key constraint
2.Add a new one with the ON DELETE NO ACTION setting enabled
Ex:
IF EXISTS(SELECT 1 FROM sys.foreign_keys WHERE parent_object_id = OBJECT_ID(N'dbo.Response'))
BEGIN
ALTER TABLE [dbo].[Response] DROP CONSTRAINT [FK_Response_Request]
ALTER TABLE [dbo].[Response] WITH CHECK ADD CONSTRAINT [FK_Response_Request] FOREIGN KEY([RequestId])
REFERENCES [dbo].[Request] ([RequestId])
ON DELETE CASCADE
END
ELSE
BEGIN
ALTER TABLE [dbo].[Response] WITH CHECK ADD CONSTRAINT [FK_Response_Request] FOREIGN KEY([RequestId])
REFERENCES [dbo].[Request] ([RequestId])
ON DELETE NO ACTION
END
If the one to many relationship is from T1 to T2 then it doesn't represent a function and therefore cannot be used to deduce or infer an inverse function that guarantees the resulting T2 value doesn't omit tuples of T1 join T2 that are deductively valid, because there is no deductively valid inverse function. ( representing functions was the purpose of primary keys. ) The answer in SQL think is yes you can do it. The answer in relational think is no you can't do it. See points of ambiguity in Codd 1970. The relationship would have to be many-to-one from T1 to T2.
I think you cannot just delete the tables property what if this is actual production data, just delete the contents that dont affect the table schema.

Resources