Help on SQL Server trigger - sql-server

Suppose I have 3 tables
t1
Nid name
1 aaa
2 bbb
3 ccc
delT1
Nid name
t2
Sid Nid value
1 1 AAA
2 1 BAC
3 2 CSA
In table t1 Nid is primary key which is foreign key for t2
Now what I want is when I delete value from t1 it automatically deletes all values from t2 where t1.Nid=t2.Nid and a value of deleted t1 get inserted into delT1
How can I create a trigger for this type of task?
Please help me since I am new to sql

A normal trigger wouldn't work: the foreign key would give an error before the code runs.
What you can do is set a CASCADE on your foreign key so a delete in T1 will delete from T2 automatically.
Personally, I'd use a stored proc and transaction to delete from T2 first, then T1.

Modify the FK in T2 to be ON DELETE CASCADE:
ALTER TABLE T2 DROP CONSTRAINT FK_T1_Nid; <-- your constraint name here
ALTER TABLE T2 ADD CONSTRAINT FK_T1_Nid FOREIGN KEY (Nid)
REFERENCES T1 (Nid) ON DELETE CASCADE;
Then create a trigger on T1 to push the information to delT1:
CREATE TRIGGER TR_T1_D ON T1 FOR DELETE
AS
SET NOCOUNT ON;
INSERT delT1
SELECT Nid, Name
FROM Deleted;
Note this trigger prevents you from using an OUTPUT clause on DELETEs against T1. BOL says:
If the OUTPUT clause is specified without also specifying the INTO keyword, the target of the DML operation cannot have any enabled trigger defined on it for the given DML action. For example, if the OUTPUT clause is defined in an UPDATE statement, the target table cannot have any enabled UPDATE triggers.

#gbn has the preferred way to go. Since you asked for a trigger, you could do this for comparison's sake:
CREATE TRIGGER t1_Delete ON t1
INSTEAD OF DELETE AS BEGIN SET NOCOUNT ON;
INSERT INTO delT1 (Nid, name)
SELECT Nid, name
FROM DELETED;
DELETE FROM t2
WHERE t2.Nid IN (SELECT Nid FROM DELETED);
DELETE FROM t1
WHERE t1.Nid IN (SELECT Nid FROM DELETED);
END;

Related

SQL Server trigger delete existing row and INSERTED itself

I need to delete an existing row when a new row is inserted.
For example, there is an existing row where its status is ready and ID is 2478.
When a new row is inserted, let's say status is completed and ID is 2478, the trigger would find matching ID 2478 and delete the row since status is completed.
At the same time, I also need to delete the inserted row as well (one with status completed)
Can this be done with trigger? ex: AFTER INSERT
Yes, this can be done in a trigger. The example below deletes all rows for a given ID whenever a row with status completed is inserted for that ID.
CREATE TABLE dbo.YourTable(
ID int
,Status varchar(10)
CONSTRAINT PK_YourTable PRIMARY KEY(ID, Status)
);
GO
CREATE TRIGGER TR_YourTable ON dbo.YourTable
FOR INSERT
AS
SET NOCOUNT ON;
DELETE target
FROM dbo.YourTable AS target
JOIN inserted ON inserted.ID = target.ID
WHERE inserted.Status = 'completed';
GO
INSERT INTO dbo.YourTable VALUES(2478,'pending');
SELECT * FROM dbo.YourTable;
INSERT INTO dbo.YourTable VALUES(2478,'ready');
SELECT * FROM dbo.YourTable;
INSERT INTO dbo.YourTable VALUES(2478,'completed');
SELECT * FROM dbo.YourTable;
GO

Deleting tables with foreign key constraints

I have three tables
GroupTable
GroupId
Name
Each group has a one to many with Users
Users
UserId
Name
GroupId
And each User has one to many with 'Challenges'
Challenges
Name
UserId
I want to be able to delete a group with the users assigned to that specific group
I have tried this where I do manage to delete the Group based on id without getting a foreign key constraint error but ALL the users added to the user table and ALL the challenges get deleted as well
ALTER TABLE GroupTable NOCHECK CONSTRAINT ALL
ALTER TABLE UserTable NOCHECK CONSTRAINT ALL
ALTER TABLE Challanges NOCHECK CONSTRAINT ALL
DELETE FROM GroupTable
WHERE ID = #GroupId
DELETE FROM child
FROM Challanges as child
INNER JOIN UserTable AS parent
ON child.UserId = parent.ID
WHERE parent.GroupId = #GroupId
DELETE FROM parent
FROM UserTable AS parent
WHERE GroupId = GroupId
How can I ammend the above so that I only delete the gropu with the specific Users and there challenges assigned to the Group?
Do not disable the constraints for this, as it will compromise data integrity.
Either use on delete cascade option on the foreign keys, or delete the data from all three tables in the correct order, inside a single transaction.
To add on cascade delete to an existing foreign key you must use the alter table statement to drop the existing constraint and then add it again with the on delete cascade option:
ALTER TABLE table_name
DROP CONSTRAINT constraint_name
ALTER TABLE table_name
ADD CONSTRAINT constraint_name
FOREIGN KEY (column_name)
REFERENCES other_table_name(other_column_name) ON DELETE CASCADE
(of course, this can be done using ssms's design table window)
Deleting the rows from the related tables in the correct order will ensure you will encounter no problems with the existing foreign key constraints, wrapping all the delete statement in a single transaction will ensure you will only delete from every table or none at all:
DECLARE #GroupId int = 5
BEGIN TRY
BEGIN TRANSACTION
DELETE c
FROM Challanges c
INNER JOIN UserTable u ON(c.UserId = u.UserId)
WHERE u.GroupId = #GroupId
DELETE
FROM Users
WHERE GroupId = #GroupId
DELETE
FROM GroupTable
WHERE ID = #GroupId
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
END CATCH

Multiple Delete query in procedure in sql server

I have around 40 delete query statement in a procedure for delete record from multiple tables for foreign key records.
example
Create Proc usp_delete_record(#id int)
as
Begin
Delete from table1 where id=#id;
Delete from table2 where id=#id;
Delete from table3 where id=#id;
Delete from table4 where id=#id;
Delete from table5 where id=#id;
Delete from table6 where id=#id;
Delete from table7 where id=#id;
Delete from table8 where id=#id;
....................
.................
Delete from table40 where id=#id;
End
It is very slow or hang execution.
How to handle this?
no needed of a procedure since u have it as a foreign key. just delete in the parent table with cascade.
so recreate your foriegn key for all child tables as
ALTER TABLE <childtables> WITH CHECK
ADD CONSTRAINT <fk_blah_blah> FOREIGN KEY(id)
REFERENCES <parenttable> (id)
ON DELETE CASCADE
Once you have this in place, you can delete the single record in parent table and so all 40 tables data are deleted
USE ON DELETE CASCADE:
CREATE TABLE parent (parent_id integer primary key );
CREATE TABLE child (child_name TEXT primary key,parent_id integer REFERENCES parent (parent_id) ON DELETE CASCADE);

Modular cascading deletion on SQL Server

I have a table (let's call it tMainTable) which is currently referred by many other table (and their number might just grow over time).
Several of those tables cannot just have the ON DELETE CASCADE clause due to cycling detection.
I know I could remove the ON DELETE CASCADE clause if I write an INSTEAD OF trigger on tMainTable.
But then whenever a new table is added as descendant of tMainTable one would have to edit the trigger which (as a "modularity" addict) is just lame (and risky - imagine an unexperienced user just breaks the whole thing).
So I'm seriously looking for an alternative way to proceed.
And I just have something in my mind.
What if I create a table tMainTableID which just hold a copy of every ID of tMainTable (copy made with AFTER trigger for insertion)?
Then, I create an INSTEAD OF trigger on tMainTable which first delete the corresponding row of tMainTableID, then delete the actual rows of tMainTable.
If I do that, I should have something really nice to work with: a table (tMainTableID) which I can "attach" AFTER DELETE triggers on to delete anything that needs to be deleted before rows of tMainTable get actually deleted.
-- Made by ME on 2014-01-27
CREATE TRIGGER tMainTableID_AFDEL_tTableChild01
AFTER DELETE
ON tMainTableID
AS
BEGIN
DELETE T
FROM deleted AS D
INNER JOIN tTableChild01 AS T ON (
T.RefToMainID = D.ID
)
END
GO
-- Made by ME on 2015-01-01
CREATE TRIGGER tMainTableID_AFDEL_tTableChild02
AFTER DELETE
ON tMainTableID
AS
BEGIN
DELETE T
FROM deleted AS D
INNER JOIN tTableChild02 AS T ON (
T.RefToMainID = D.ID
)
END
GO
-- Made by Iamun Kompetent on 2016-01-01
CREATE TRIGGER tMainTableID_AFDEL_tTableChild03
AFTER DELETE
ON tMainTableID
AS
BEGIN
DELETE T
FROM inserted AS D
INNER JOIN tTableChild02 AS T ON (
D.ID = D.ID
)
END
GO
Do you see anything wrong with this approach or even better, do you know a better way to attain modularity?
-- EDIT: Simple example
create table tTest20140128 (
id int not null primary key
)
go
create table tTest20140128_Child01 (
id int not null primary key references tTest20140128(id) on delete no action
)
go
create table tTest20140128_Child02 (
id int not null primary key references tTest20140128(id) on delete no action
)
go
insert tTest20140128 values (1), (2), (3)
insert tTest20140128_Child01 values (1), (2), (3)
insert tTest20140128_Child02 values (1), (2), (3)
go
delete from tTest20140128 -- Error
go
-- table for holding copies of ids
create table tTest20140128_ID (
id int not null primary key references tTest20140128(id) on delete no action
)
go
insert tTest20140128_ID
select id from tTest20140128
go
-- trigger that keeps tTest20140128_ID up to date for new ids
create trigger tTest20140128_AFINS
on tTest20140128
after insert
as
begin
insert tTest20140128_ID
select id from inserted
end
go
-- Instead of delete (keeps tTest20140128_ID up to date for deleted ids)
create trigger tTest20140128_IODEL
on tTest20140128
instead of delete
as
begin
delete ID
from deleted AS D
inner join tTest20140128_ID AS ID ON (
ID.id = D.id
)
delete from T
from deleted AS D
inner join tTest20140128 AS T on (
T.id = D.id
)
end
go
-- Sorta "attching listeners to event"
-- tTest20140128_Child01
create trigger tTest20140128_tTest20140128_Child01
on tTest20140128_ID
after delete
as
begin
delete T
from deleted as D
inner join tTest20140128_Child01 AS T on (
T.id = D.id
)
end
go
-- tTest20140128_Child02
create trigger tTest20140128_tTest20140128_Child02
on tTest20140128_ID
after delete
as
begin
delete T
from deleted as D
inner join tTest20140128_Child02 AS T on (
T.id = D.id
)
end
go
-- New tests
insert tTest20140128 values (4), (5), (6)
insert tTest20140128_Child01 values (4), (5), (6)
insert tTest20140128_Child02 values (4), (5), (6)
go
select COUNT (*) as [COUNT after insert] from tTest20140128
go
delete from tTest20140128 -- No Error
go
select COUNT (*) as [COUNT" after delete] from tTest20140128
go
drop table tTest20140128_ID
drop table tTest20140128_Child02
drop table tTest20140128_Child01
drop table tTest20140128
The table creation sounds unnecessary... but your INSTEAD OF trigger sounds like a great idea:
How about making an INSTEAD OF trigger on tMainTable, that will first "delete anything that needs to be deleted before rows of tMainTable", and then "delete the actual rows of tMainTable"?

How cascade Update/Delete works internally in SQL Server?

Ok, I believe the question was not clear. Here i rewrite this in other way.
Suppose i create two tables,
table1(c1 int PRIMARY KEY)
table2(table1c11 int)
There is a relation between table1 and table2
i.e. table1.c1=table2.table1c11
And, i execute the following statement in the table1 and table2
insert into table1(c1)
values('a'),('b'),('c'),('d'),('e')
insert into table2(table1c11)
values('a'),('a'),('b'),('d')
And now what I want to achieve is that, once I update the value of c1 in table1 the corresponding data in table2 gets changed automatically. For this I need to create the constraint in table1 and table2 relationships and apply the CASCADE UPDATE.
So, later I apply a new SQL update statement in table1 i.e.
Update table1 set c1=c1+'updated'
Then the data in table2 gets changed also, But what if I want to achieve the same functionality via INSTEAD OF UPDATE TRIGGER, then I need to write the instead of update trigger and inside that, I need to handle that with two magic tables INSERTED and DELETED.
But the main point is that, in this case, I have only one column present in the table1 and I am updating that same column, so how could i map the inserted and deleted rows. Same thing is being done by the SQL Server as well if I use CASCADing.
So, the question arises how SQL Server handles batch update in case of the primary key data changes in the table.
So, the question arises how SQL Server handles batch update in case of
the primary key data changes in the table.
SQL Server builds a query plan for the update statement that update both tables.
Create the tables:
create table T1
(
T1ID int primary key
);
create table T2
(
T2ID int primary key,
T1ID int references T1(T1ID) on update cascade
)
Add some data:
insert into T1 values(1), (2)
insert into T2 values(1, 1), (2, 1), (3, 2)
Update primary key of T1:
update T1
set T1.T1ID = 3
where T1.T1ID = 1
The query plan for the update looks like this:
The plan has two Clustered Index Update steps, one for T1 and one for T2.
Update 1:
How does SQL Server keep track of the rows to update when more than one primary key value is updated?
update T1
set T1.T1ID = T1.T1ID + 100
The Eager Spool in the top branch (update of T1) saves the old T1ID and the new calculated T1ID (Expr1013) to a temporary table that is used by the lower branch (update of T2). The Hash Match in the lower branch is joining the Table Spool with T2 on the old T1ID. Output from the Hash Match to the update of T2 is T2ID from the Clustered Index Scan of T2 and the new calculated T1ID (Expr1013) from the Table Spool.
Update 2:
If you need to replace the cascade update with a instead of trigger you need to have a way to join the inserted and deleted tables in the trigger. That can be done with a surrogate key in T1.
Tables:
create table T1
(
T1ID int primary key,
ID int identity unique
);
create table T2
(
T2ID int primary key,
T1ID int references T1(T1ID)
);
The trigger could look like this.
create trigger tr_T1 on T1 instead of update as
insert into T1(T1ID)
select T1ID
from inserted;
update T2
set T1ID = I.T1ID
from inserted as I
inner join deleted as D
on I.ID = D.ID
where D.T1ID = T2.T1ID;
delete from T1
where T1ID in (
select T1ID
from deleted
);
SQL Fiddle

Resources