Get the count for deleted records when using cascade delete constraint - sql

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

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;

SQL trigger INSERT AFTER

I am trying to setup a trigger in phpMyAdmin. have two tables, one parent, one child. Every time a new user in users is created I want to automaticly create a line in the permissions table with the (AI) generated id from users as user_id. (permissions.user_id is a foreigh key of users.id)
users:
id email password
--------------------------------------
1 eric#test.com sdgsdhdfhs
2 john#test.com dfgsdgdfhg
3 mary#test.com plkdfjvjvv
permissions:
index user_id allow_read allow_write allow_delete
-------------------------------------------------------------
1 1 1 0 1
2 2 1 1 1
3 3 0 0 0
I've tried (without success):
INSERT
INTO permissions
(user_id)
VALUES
(IDENT_CURRENT('users'))
and
INSERT
INTO permissions
(user_id)
VALUES
(##IDENTITY)
To access the data of the row that caused a trigger to be executed, you can use NEW and OLD aliases. For INSERT triggers only NEW is available. For DELETE triggers only OLD is available. In UPDATE triggers you can use both. They are used the same way as table aliases (e.g. NEW.id / OLD.id).
Given a parent and a child table as follows:
create table parent_table(
id int auto_increment primary key,
pdata varchar(50)
);
create table child_table(
id int auto_increment primary key,
parent_id int not null,
cdata varchar(50) default '',
foreign key (parent_id) references parent_table(id)
);
To insert a child row when a parent row is inserted:
create trigger insert_parent
after insert on parent_table
for each row
insert into child_table(parent_id)
values (new.id);
To delete all related child rows wehen a parent row is deleted:
create trigger delete_parent
before delete on parent_table
for each row
delete from child_table
where parent_id = old.id;
Demo: http://rextester.com/EOW74217
However a delete trigger is not necessary if you define your foreign key with ON DELETE CASCADE
foreign key (parent_id) references parent_table(id) on delete cascade
All related child rows will be deleted without a trigger when you delete the parent row.
Demo: http://rextester.com/CWB43482

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);

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

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

DB2 cascade delete command?

Is there a special syntax to execute a cascade delete on DB2 or is it only possible to create "cascadable" tables by defining them with the "ON DELETE CASCADE" option?
What I am trying to achieve is delete other table rows based on a same foreign key when I delete that key, but this is done on an already existing and data-filled database.
As you stated, you either have to create FKs with an ON DELETE CASCADE clause or to pre-delete the other rows with a subselect-delete.
So, if you don't have an ON DELETE CASCADE clause you have to do
DELETE FROM ORDERS_ITEMS WHERE ORDER_ID in (
SELECT ORDER_ID FROM ORDERS WHERE STATUS = 'Canceled'
);
DELETE FROM ORDERS_ADDRESS WHERE ORDER_ID in (
SELECT ORDER_ID FROM ORDERS WHERE STATUS = 'Canceled'
);
DELETE FROM ORDERS WHERE STATUS = 'Canceled';
It is simple, but is somewhat redundant so you may use the WITH statement.
If the request to select the required rows is quite big, and if you don't have at least a RR isolation level, you may have to use a TEMPORARY table :
DECLARE GLOBAL TEMPORARY TABLE TMP_IDS_TO_DELETE (ID BIGINT) NOT LOGGED;
INSERT INTO SESSION.TMP_IDS_TO_DELETE (ID)
SELECT ORDER_ID FROM ORDERS WHERE STATUS = 'Canceled';
DELETE FROM ORDERS_ITEMS WHERE ORDER_ID in (
SELECT ID FROM SESSION.TMP_IDS_TO_DELETE
);
DELETE FROM ORDERS_ADDRESS WHERE ORDER_ID in (
SELECT ID FROM SESSION.TMP_IDS_TO_DELETE
);
DELETE FROM ORDERS WHERE ORDER_ID in (
SELECT ID FROM SESSION.TMP_IDS_TO_DELETE
);
This way you are sure that you will delete the same rows in each table, and a FK error will still fire up if you miss something. Per default, the temporary table will empty itself on commit.