How to delete rows in tables that contain foreign keys to other tables - sql

Suppose there is a main table containing a primary key and there is another table which contains a foreign key to this main table. So if we delete the row of main table it will delete the child table also.
How do I write this query?

First, as a one-time data-scrubbing exercise, delete the orphaned rows e.g.
DELETE
FROM ReferencingTable
WHERE NOT EXISTS (
SELECT *
FROM MainTable AS T1
WHERE T1.pk_col_1 = ReferencingTable.pk_col_1
);
Second, as a one-time schema-alteration exercise, add the ON DELETE CASCADE referential action to the foreign key on the referencing table e.g.
ALTER TABLE ReferencingTable DROP
CONSTRAINT fk__ReferencingTable__MainTable;
ALTER TABLE ReferencingTable ADD
CONSTRAINT fk__ReferencingTable__MainTable
FOREIGN KEY (pk_col_1)
REFERENCES MainTable (pk_col_1)
ON DELETE CASCADE;
Then, forevermore, rows in the referencing tables will automatically be deleted when their referenced row is deleted.

From your question, I think it is safe to assume you have CASCADING DELETES turned on.
All that is needed in that case is
DELETE FROM MainTable
WHERE PrimaryKey = ???
You database engine will take care of deleting the corresponding referencing records.

You can alter a foreign key constraint with delete cascade option as shown below. This will delete chind table rows related to master table rows when deleted.
ALTER TABLE MasterTable
ADD CONSTRAINT fk_xyz
FOREIGN KEY (xyz)
REFERENCES ChildTable (xyz) ON DELETE CASCADE

If you have multiply rows to delete and you don't want to alter the structure of your tables
you can use cursor.
1-You first need to select rows to delete(in a cursor)
2-Then for each row in the cursor you delete the referencing rows and after that delete the row him self.
Ex:
--id is primary key of MainTable
declare #id int
set #id = 1
declare theMain cursor for select FK from MainTable where MainID = #id
declare #fk_Id int
open theMain
fetch next from theMain into #fk_Id
while ##fetch_status=0
begin
--fkid is the foreign key
--Must delete from Main Table first then child.
delete from MainTable where fkid = #fk_Id
delete from ReferencingTable where fkid = #fk_Id
fetch next from theMain into #fk_Id
end
close theMain
deallocate theMain
hope is useful

If you want to delete all the rows, you can use truncate with cascade:
TRUNCATE TABLE products CASCADE;

Need to set the foreign key option as on delete cascade...
in tables which contains foreign key columns.... It need to set at the time of table creation or add later using ALTER table

Related

Delete records of table which has 2 foreign keys that reference to same table

I have 2 tables, first one is Compartment and second one is AboveCompartment. Please see the below. Above compartment has 2 columns which are foreign keys and reference to the Compartment table. When I set the delete and update action as cascade for 2 foreign keys, I get the error below.
Introducing FOREIGN KEY constraint 'FK_AboveCompartment_Compartment1' on table 'AboveCompartment' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Below CompId and AboveCompId are foreign keys and reference to the Compartment table. Which way should I follow to add delete cascading? I used the trigger below but it also didn't work and get error added below.
AboveCompartment
Compartment
Trigger
ALTER TRIGGER [dbo].[delFromCompartment] on [dbo].[Compartment]
FOR DELETE
AS
DELETE FROM dbo.AboveCompartment
WHERE CompId IN(SELECT deleted.Id FROM deleted)
Error
You cannot implement this using cascades, as SQL Server does not let you.
You also cannot implement it using triggers, because the foreign key is enforced before you get to the trigger.
You need to write a stored procedure that first deletes the parent table rows, then the child table
CREATE OR ALTER PROCEDURE dbo.Delete_Compartment
#CompartmentId int
AS
SET XACT_ABORT, NOCOUNT ON; -- always use XACT_ABORT if you have a transaction
BEGIN TRAN;
DELETE AboveCompartment
WHERE CompId = #CompartmentId;
DELETE AboveCompartment
WHERE AboveCompId = #CompartmentId;
DELETE Compartment
WHERE Id = #CompartmentId;
COMMIT;
I must say, this table design is somewhat suspect. AboveCompId as a column name implies that it represents a single parent for multiple children, rather than multiple parents for multiple children.
If so then you should instead implement this as a self-referencing foreign key. Drop the AboveCompartment table, and add a column
ALTER TABLE Compartment
ADD AboveCompId int NULL REFERENCES Compartment (Id);
This foreign key also cannot be cascading. But now the delete is only on one table, but you can do it in a recursive fashion. As long as you delete all rows in one go, you shouldn't have an issue with foreign key conflicts.
CREATE OR ALTER PROCEDURE dbo.Delete_Compartment
#CompartmentId int
AS
SET NOCOUNT ON;
-- no transaction needed as it's one statement
WITH cte AS (
SELECT #CompartmentId AS Id
UNION ALL
SELECT c.Id
FROM Compartment c
JOIN cte ON cte.Id = c.AboveCompId;
)
DELETE c
FROM Compartment c
JOIN cte ON cte.Id = c.Id;

Get the count for deleted records when using cascade delete constraint

When using ON DELETE CASCADE constraint and deleting a "master" record is there a way to get the count of "child" records deleted?
For example using these table:
CREATE TABLE Master ( ID INT PRIMARY KEY (ID))
GO
CREATE TABLE Child ( ID INT, MasterID INT)
GO
ALTER TABLE Child ADD CONSTRAINT FK_Child_Master FOREIGN KEY(MasterID)
REFERENCES Master (ID)
ON DELETE CASCADE
GO
INSERT INTO Master (ID) VALUES (1);
INSERT INTO Child ( ID, MasterID) VALUES (1,1),(2,1),(3,1),(4,1),(5,1);
Now if I delete the master record like this:
DELETE FROM Master WHERE ID = 1;
SELECT ##ROWCOUNT;
The 5 rows in the Child table and the 1 row in the Master table are all deleted but the result is 1. It only counts the records deleted from the master table.
Is there a way to capture the number of rows deleted from the child table or do I need delete from the child table first, get the count, then delete from the master?
Yes you can use a stored procedure to do this like
create procedure spDeleteRecords
#id int
begin
select count(*) from child_table where id=#id
delete from parent_table where id=#id
end

Deleting row on Foreign key relation

I have table on which I have a foreign key constraint like below
ALTER TABLE [dbo].[Element] WITH CHECK
ADD CONSTRAINT [FK_Element_Band]
FOREIGN KEY([BandID]) REFERENCES [dbo].[Band] ([BandID])
GO
ALTER TABLE [dbo].[Element] CHECK CONSTRAINT [FK_Element_Band]
GO
Now I am trying to delete a row from the band table like this
DELETE FROM Band
WHERE TypeID = 21 AND BandUpperLimit = 10000 AND PID = 61
But I am getting a reference constraint error:
The DELETE statement conflicted with the REFERENCE constraint "FK_Element_Band". The conflict occurred in database "pricingModified", table "dbo.Element", column 'BandID'.
So this should happen if I have anything in the Elements table which is referencing Band table with the ID 21 but that's not the case since the SQL
SELECT *
FROM Element
WHERE BandID = 21
returns nothing.
Can someone please tell me why I am not able to delete row data from the parent table even though there is no reference present in the child table?
Thanks
Just specify ON DELETE CASCADE in your foreign key constraint. Then when you delete a row from the Band table it will automatically delete any child records in the Element table.
ALTER TABLE [dbo].[Element] WITH CHECK
ADD CONSTRAINT [FK_Element_Band]
FOREIGN KEY([BandID]) REFERENCES [dbo].[Band] ([BandID]) ON DELETE CASCADE
GO
Check with this query instead:
select b.*
from Element e
inner join Band b
on e.BandId = b.BandId
where b.TypeId = 21
and b.BandUpperLimit = 10000
and b.PID = 61
while creating child table
create table <child_table_name>
(
_______,
_______,
([FOREIGN KEY column_name]) [size of FOREIGN KEY column] references [parent table(primary key column_name)] on delete cascade
)
Using this you can easily delete record of fk using delete operation..
Try this, if it is successful then mark it positive...

On delete cascade for self-referencing table

I have a comment table that is self-referencing.
I tried to write on delete cascade but it take some exception
Introducing FOREIGN KEY constraint 'FK_Comments_Comments' on table 'Comments' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
and then try to write a trigger but it take exception again
CREATE TRIGGER [dbo].[T_comment_Trigger]
ON [dbo].[Comments]
FOR DELETE
AS
DELETE FROM Comments
WHERE ParentId =(SELECT deleted.id FROM deleted)
couldn't delete rows that have children
how can I do on delete cascade for my self-referencing table?
Assuming you're keeping your FOREIGN KEY constraint in place, you cannot fix the issue in a FOR DELETE trigger. FOR triggers (also known as AFTER triggers) fire after the activity has taken place. And a foreign key will prevent a row from being deleted if it has references. Foreign key checks occur before deletion.
What you need is an INSTEAD OF trigger. You also need to bear in mind that your current trigger only tried to deal with one "level" of referencing. (So, if row 3 references row 2 and row 2 references row 1, and you delete row 1, your trigger only tried to remove row 2)
So, something like:
CREATE TRIGGER [dbo].[T_comment_Trigger]
ON [dbo].[Comments]
INSTEAD OF DELETE
AS
;WITH IDs as (
select id from deleted
union all
select c.id
from Comments c
inner join
IDs i
on
c.ParentID = i.id
)
DELETE FROM Comments
WHERE id in (select id from IDs);
If there are other (non-self-referencing) cascading foreign key constraints, they all have to be replaced by actions in this trigger. In such a case, I'd recommend introducing a table variable to hold the list of all IDs that will eventually be deleted from the Comments table:
CREATE TRIGGER [dbo].[T_comment_Trigger]
ON [dbo].[Comments]
INSTEAD OF DELETE
AS
declare #deletions table (ID varchar(7) not null);
;WITH IDs as (
select id from deleted
union all
select c.id
from Comments c
inner join
IDs i
on
c.ParentID = i.id
)
insert into #deletions(ID)
select ID from IDs
-- Delete from FK referenced table
DELETE FROM OtherTable
WHERE CommentID in (select ID from #deletions)
--This delete comes last
DELETE FROM Comments
WHERE id in (select ID from #deletions);

Replace Create Trigger with Foreign Key (RI)

Create Trigger:
SELECT #oldVersionId = (SELECT DISTINCT VERSION_ID FROM Deleted)
SELECT #newVersionId = (SELECT DISTINCT VERSION_ID FROM Inserted)
SELECT #appId = (SELECT DISTINCT APP_ID FROM Deleted)
UPDATE [TableName]
SET [VERSION_ID] = #newVersionId
WHERE (([VERSION_ID] = #oldVersionId) AND ([APP_ID] = #appId) )
Can this Trigger be replace with a Foreign Key to update the VERSION_ID ?
What I think could be a problem is the AND condition, how to express that in a FK with On del/update Cascade?
FOREIGN KEY CONSTRAINTS don't update anything. They check the values being written to a record and cause the write to fail if they cause a constraint to fail.
Also, as #marc_s points out in his comment, triggers in MS SQL Server are set based. The INSERTED and DELETED tables can hold multiple records at once. Your code only works for one record.
You could try something along these lines...
UPDATE
table
SET
VERSION_ID = inserted.VERSION_ID
FROM
table
INNER JOIN
deleted
ON table.VERSION_ID = deleted.VERSION_ID
AND table.APP_ID = deleted.APP_ID
INNER JOIN
inserted
ON deleted.PRIMARY_KEY = inserted.PRIMARY_KEY
EDIT
I just read your comment, and I think I understand. You want a foreign key constraint with ON UPDATE CASCADE.
You use this format to create that with DDL.
ALTER TABLE DBO.<child table>
ADD CONSTRAINT <foreign key name> FOREIGN KEY <child column>
REFERENCES DBO.<parent table>(<parent column>)
{ON [DELETE|UPDATE] CASCADE}
Or you could just SQL Server Management Studio to set it up. Just make sure the ON UPDATE CASCADE is present.
I cannot really tell you what you're looking for - you're too unclear in your question.
But basically, if two tables are linked via a foreign key constraint, of course you can add a clause to that to make sure the child table gets updated when the parents table's PK changes:
ALTER TABLE dbo.ChildTable
ADD CONSTRAINT FK_ChildTable_ParentTable
FOREIGN KEY(ChildTableColumn) REFERENCES dbo.ParentTable(PKColumn)
ON UPDATE CASCADE
The ON UPDATE CASCADE does exactly that - if the referenced column (the PKColumn in ParentTable) changes, then the FK constraint will "cascade" that update down into the child table and update it's ChildTableColumn to match the new PKColumn
Read all about cascading referential integrity constraints and what options you have on MSDN Books Online