Modular cascading deletion on SQL Server - 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"?

Related

Re-Inserting deleted rows into the same table SQL Server 2005

After searching many pages I still can't find the answer about re-inserting deleted rows in the same table - not another table.
I have a table named timetable with the primary key made up from 3 columns Schoolcode, Year, Term.
I need for some reason need to insert deleted rows into the same table.
I get the error
Violation of PRIMARY KEY constraint
with the following trigger
ALTER TRIGGER [dbo].[AFTER_delete_]
ON [dbo].timetable
AFTER delete
AS
BEGIN
IF EXISTS (SELECT * FROM deleted)
BEGIN
INSERT INTO timetable
SELECT *
FROM deleted A
WHERE NOT EXISTS (SELECT 1 FROM timetable B
WHERE B.Schoolcode = A.Schoolcode
AND B.Year = A.Year
AND B.Term = A.Term);
END
END
thanks any way.I test the code below and that did work.
ALTER TRIGGER [dbo].[Instead_OfDelSert_Status]
ON [dbo].[Status]
INSTEAD OF delete,insert
AS
BEGIN
PRINT 'You must disable or delete Trigger Instead_OfDelSert_Status to insert or
delete rows!'
END

SQL Server: Capturing All the columns that have changed in a separate table

In my SQl Server I have a table of around 40 attributes/columns. There is a daily load which might update any of these columns. I want to capture the changes in these columns in a separate table with a reason code column telling which column value changed. There might be instances where more than one column value might get changed in a single daily load, in that case the changed log table should capture all these changes separately in rows with each row depicting the individual change.
For Example:
TableA(column1(pk),column2,column3,column4)
values(1,100,ABC,999)
After update:
TableA(column1(pk),column2,column3,column4)
values(1,100,ACD,901)
The corresponding change log table should have two entries:
TabChangeLog(column1,before,after,reason);
values(1,ABC,ACD,'column3 changed')
values(1,999,901,'column4 changed')
I tried implementing this through triggers but am not able to figure out a way to separate each of these changes in separate rows when there are more than one changes. Please help
You need to create a trigger like :
create trigger trigger_name
instead of update as
if update(column1)
begin
insert into TabChangeLog
select inserted.column1, inserted.column3, deleted.column3, 'column3', 'update/change'
from inserted i inner join deleted d
on i.column1 = d.column2
end
if update(column2)
begin
insert into TabChangeLog
select inserted.column1, inserted.column2, deleted.column2, 'column2', 'update/change'
from inserted i inner join deleted d
on i.column1 = d.column2
end
...
https://www.tutorialgateway.org/instead-of-update-triggers-in-sql-server/
Microsoft SQL Server 2016 has a thing called Temporal Tables which would probably simplify your job a lot. It lets you rewind a dataset through time to see the changes:
https://learn.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables?view=sql-server-2017
If you don't want to go that route and use triggers instead. UPDATE triggers have two tables inserted and deleted that let you know what the row state was before and after.
*Edit: These are tables so you have to use SELECT INTO etc to interact with them you can't do conditional logic (if /else)
CREATE TABLE [dbo].[Table1](
[Id] [int] NOT NULL,
[Tail] [int] NOT NULL,
CONSTRAINT [PK_Table1_1] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
)
CREATE TABLE Table1_Audit
(
Audit varchar(100)
)
--drop trigger Table1_OnUPDATE
CREATE TRIGGER Table1_OnUPDATE
ON dbo.Table1
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
INSERT INTO Table1_Audit ([Audit])
select CONCAT('Tail changed to' ,inserted.Tail,' for pk Id=',inserted.Id) from inserted inner join
deleted on inserted.Id = deleted.Id --pk must be the same
where
inserted.Tail <> deleted.Tail --field x must be different
END
GO
--truncate table Table1_Audit
--update Table1 set Tail = 5
select * from Table1_Audit

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

What is the expected result table during a SQL update trigger?

During a SQL trigger update trigger is there an easy way to get the whole expected result table (that is, what the table would look like after the trigger executes?)
Here's the only thing I could think of (which boils down to the table MINUS the deleted table + the inserted table):
SELECT *
FROM TheTable t
WHERE
NOT EXISTS
(
SELECT 1
FROM DELETED d
WHERE
d.primaryKey1 = t.primaryKey1
AND d.primaryKey2 = t.primaryKey2
-- ...
)
UNION ALL
SELECT *
FROM INSERTED
UPDATE:
The above is unnecessarily complicated inside of a FOR/AFTER trigger. It suffices just to query the table itself. (Thanks to #usr for the wake-up call.) However, for an instead of trigger you would do something similar to get the resulting table, although it's likely you would actually want to be constructing the table during it's execution.
Use an AFTER trigger to look at the table in the final state.
As already suggested you can use an after insert/update trigger. In this trigger context you have the table with the new values, but the insert or update is not really over, so any throw will rollback the operation. Example:
-- drop table testConstraint
create table testConstraint
(
Id INT,
Name varchar(10)
-- CONSTRAINT CK_testConstraint_misc CHECK (dbo.checkTest(Id, Name) <> 0)
)
create TRIGGER trgConstraint
ON dbo.testConstraint
AFTER INSERT, UPDATE AS
BEGIN
IF EXISTS (SELECT 1 FROM testConstraint where Id > 10)
THROW 51000, 'Invalid record found', 1;
END
GO
-- ok
insert into testConstraint (Id, Name) values (1, 'n1'), (2, 'n2'), (3, 'n3')
go
select * from testConstraint
go
-- will fail
insert into testConstraint values (11, 'n11')
go
select * from testConstraint
GO
-- will fail
update testConstraint set Id = 20 where Id = 2
go
select * from testConstraint
GO

Help on SQL Server trigger

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;

Resources