SQL Server using triggers and geting rid of ON DELETE CASCADE - sql

I have 2 tables, A and B.
Table B has a foreign key pointing to primary key of table A. The foreign key on table B has ON DELETE CASCADE, so the corresponding records from B is deleted when a record from A is deleted.
My requirement is to track all added/updated/deleted records in history tables. I have trigger on each table to insert the records into history tables(AHistories and BHistories tables).
I do not like the order ON DELETE CASCADE deletes the records. Trigger A is executed after trigger B, so I have to work around to get the ID of AHistory into BHistory record.
I am wanting to get rid of ON DELETE CASCADE and perform Delete on the records of B in trigger A then insert the deleted record of B into BHistories there.
To demonstrate the idea, I made the case simple, but I have a few more tables that have a foreign key pointing to the primary key in table A. Personally, I would like if I can specify the order and what I do on delete cascade.
Does this stink as an approach? Any comments are appreciated.

As bad as triggers are but sometimes they are the only way to implement complex business requirements. I would do something as follows in the following example PK_ID refers to Primary Key Column.
CREATE TRIGGER tr_Table_A_InsteadOfDelete
ON dbo.TableA
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
-- Insert into History Table from TableB
INSERT INTO TABLE_B_History
SELECT B.*
FROM TableB B INNER JOIN deleted d
ON B.PK_ID = d.PK_ID
-- Delete rows from TableB
DELETE FROM TableB
WHERE EXISTS (SELECT 1
FROM deleted
WHERE PK_ID = TableB.PK_ID)
-- Insert into History Table from TableA
INSERT INTO TABLE_A_History
SELECT A.*
FROM TableA A INNER JOIN deleted d
ON A.PK_ID = d.PK_ID
-- Delete rows from TableA
DELETE FROM TableA
WHERE EXISTS (SELECT 1
FROM deleted
WHERE PK_ID = TableA.PK_ID)
COMMIT TRANSACTION;
END

Using triggers for audit purposes is as obvious as it's wrong. If you have sufficient permissions, they can be disabled too easily.
Nevertheless, what you want to do can be achieved with the combination of:
INSTEAD OF DELETE triggers, where you can delete or move rows around in any order;
Disabling nested triggers. This might has unpleasant side effects, if you use triggers for more than just the audit, because it's a database-wide option.
Just don't forget that in such a trigger, you have to manually delete rows from its underlying table, as well.

Related

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

What is the difference between truncate, drop and delete of tables? And when to choose for which?

What's the difference between truncate, drop and delete of tables? And when to choose for which? Does anyone has a quick comparison? I've seen a lot of info about this, but haven't found it in a clear overview yet. I hope this post helps in the understanding.
I mean like being used in these statements in t-sql:
truncate table TableX
drop table TableX
delete table_name
Based on an answer by #Michal here and some more searching I made a comparison beneath for the following statements (in t-sql): truncate table TableX, drop table TableXand delete table_name.
Truncate Drop Delete
Speed [Fast] Slow Slowest
Rolback possibility No No [Yes]
Specifiable conditions No No [Yes]
Scope All records All record+Headers Some records/All records
=whole table
Cascading effects No* No* [Yes]**
**For example: in a Table_1 there is a PK, in Table_2 there is a FK that relates with
the PK of Table_1, other words there is referential integrity. If the PK has `'ON DELETE CASCADE'`
and `delete Table_1` is ordered, then the data in Table_2 will be deleted too,
automatically. For more info about ON DELETE CASCADE and ON ALTER CASCADE, see:
https://technet.microsoft.com/en-us/library/ms186973%28v=sql.105%29.aspx.
Cascading does automatic alterations and deletes of depending objects such as foreign keys (FK),
views, and triggers. Sometimes very useful, sometimes very dangerous..
*The drop and truncate statements of a Table_1 (with PK and FK in Table_2, as decribed
in **) can't be executed, because the ssdms prohibits that. To accomplish the truncation
or dropping of a Table_1: first get rid of the FK in Table_2, by altering the table design, or
by dropping table2.
See the comparison to base the decision when to use which statement...
As a thumb:
If you want to get rid of only records:
use delete when a conditional deleting is required, use truncate when all records may be get rid of. When you want to be able to rollback then use delete.
If you want to get rid of the whole table, including the headers (columns with settings) then choose drop.
If you want to get rid of values and automatically the related objects (and cascading is defined in the table), use delete. (PS: in other dialects it seems there are ways to accomplish it even when the table is not designed with cascading, but as far as I know there isn't in t-sql/msss; but correct me if I'm wrong)
PS: if you want to alter or delete the preferences of a column, then use (in t-sql dialect):
Alter:
alter table tableX
alter columnX datatypeX
Delete:
alter table tableX
drop column columnX
--And here's some code to play with
--table to truncate, drop or delete
create table TableX(
[Name] [nchar](25) null,
[ID_Number] [int] not null)
--tables with PK and FK
create table Table_1(
[Name] [nchar](25) null,
[ID_Number] [int] not null primary key)
create table Table_2(
[ID_Number] int not null foreign key references Table_1(ID_Number) on delete cascade,
[Buys] [int] null)
--the on delete cascade make it happen that when a ID_Number is Table_1 is deleted, that row
is automatically deleted in Table_2 too. But not the other way around,
therefor alter the design of Table_1.
insert into Table_1 (Name,ID_Number) values ('A',1),('B',2),('C',3);
insert into Table_2 (ID_Number,Buys) values (1,10),(2,20),(3,30);
select * from Table_1
select * from Table_2
truncate table table_2
truncate table table_1
drop table table_2
drop table table_1
delete Table_1
delete from dbo.table_1 where name='A'
delete from Table_1 where name like '%'
delete from dbo.table_2 where ID_Number=2

Join query to delete a record both in child class and parent class

I need to delete a record in both child table and parent table with the reference to another column.The primary key of one table is equal to foreign key of another table.eg.table A.pkid=table B.fkid
How do I use join query or cascade to delete it.
I tried couple of queries but it shows invalid syntax.
delete from
table A table B where pkid=(SELECT fk_id from table B)
where name='SEP' from table B
delete from
table A join table B ON table A.fk_id=(SELECT pk_id FROM table B)
where name='SEP' from table B
delete from
table A join table B ON A.fk_id=B.pk_id
where name='SEP' from table B
Could you please refine my query or give me a link where I can get some help on this? Thanks a lot.
It looks like all you need is something simple:
BEGIN WORK;
DELETE FROM A WHERE fk_id IN (SELECT pk_id FROM B WHERE name = 'SEP');
DELETE FROM B WHERE name = 'SEP';
COMMIT WORK;
The alternative is to define the PK-FK relationship in the schema with the ON DELETE CASCADE option, and children will be automatically deleted on deletion of the parent.

How to cascade update 2 tables whose foreign key is not set to cascade?

I got 2 tables. They have a foreign key relation. But the foreign key is not set to update cascade. Now I want to update the table's primary key. SQL Server always prevent me from doing that because of the FK. How could I do it in SQL command? I don't have the right to modify the FK.
Thanks.
Why would you want to do this? It strikes me as potentially hazardous.
Your SQL would need to update the related table first NULLing the links back to the PK table and storing the IDs of the records effected. Then you can update the PK in the PK table. Then go back and update the FKs in the related tables.
Create new rows based on the existing row's attribute values but using the new key value. Do the same for all referencing tables. Then, using the new old key value, delete rows from the referencing tables then the referenced table. Something like:
INSERT INTO Table1 (key_col, attrib_col1)
SELECT 'new_key_value', attrib_col1
FROM Table1
WHERE key_col = 'old_key_value';
INSERT INTO Table2 (key_col, attrib_col2)
SELECT 'new_key_value', attrib_col2
FROM Table2
WHERE key_col = 'old_key_value';
DELETE
FROM Table2
WHERE key_col = 'old_key_value';
DELETE
FROM Table1
WHERE key_col = 'old_key_value';
You should only need to INSERT new rows for the parent table and UPDATE the child rows after the fact...
INSERT INTO ParentTable (PKColumn, attribute1, attribute2)
SELECT 'NewPKValue', attribute1, attribute2
FROM ParentTable
WHERE PKColumn = 'OldPKValue'
;
UPDATE ChildTable
SET FKColumn = 'NewPKValue'
WHERE FKColumn = 'OldPKValue'
;
DELETE
FROM ParentTable
WHERE PKColumn = 'OldPKValue'
;
Now for the gotchas:
1.) The above code wont work if there are any unique indexes defined on non-PK columns in the parent table and you need to use the current records' non-PK data values without modification.
2.) Since you're asking this question, I'm assuming your Parent table is not using an IDENTITY column as the PK.
3.) The code is certainly less than efficient. If your db has to do this infrequently on a few rows, you should be fine. If you need to do this 80 times per second, then I would strongly suggest you talk to the programmer/DBA or vendor, if they're at all available, about updating the FK definition to include ON UPDATE CASCADE.
4.) Make sure that there aren't any triggers on either table that would lead to unintended side effects when you create the new parent records or update the child records.

Sql Server 2008 check constraint

I have two tables, Table1 and Table2 with same primary key (FKTestID).
If I want to delete one row in Table1 and same FKTestID are in Table2 it will not work. You can only delete a row from Table1 if Table1.FKTestID not equals any FKTestID in Table2.
Please help me with this constraint?
You need to set the constraint to cascade on delete.
You can do this through SQL management studio by modifying the constraint.
Or you can do this via SQL when you created the constraint by including ON DELETE CASCADE at the end
You could also do it with the ALTER TABLE command.
Here is a code sample implementing what Simon suggested above.
CREATE TABLE dbo.Table1 (
FKTestID int NOT NULL PRIMARY KEY
)
GO
CREATE TABLE dbo.Table2 (
FKTestID int NOT NULL PRIMARY KEY
)
GO
ALTER TABLE dbo.Table2
ADD CONSTRAINT FK_Table2_FKTestID
FOREIGN KEY (FKTestID)
REFERENCES dbo.Table1 (FKTestID)
ON DELETE CASCADE
GO
INSERT INTO dbo.Table1 VALUES (1)
INSERT INTO dbo.Table2 VALUES (1)
INSERT INTO dbo.Table1 VALUES (2)
INSERT INTO dbo.Table2 VALUES (2)
DELETE FROM dbo.Table1 WHERE FKTestID = 1
SELECT 'Table1' AS [Table], * FROM dbo.Table1
SELECT 'Table2' AS [Table], * FROM dbo.Table2
=============================================
Table FKTestID
------ -----------
Table1 2
Table FKTestID
------ -----------
Table2 2
Note that I agree with Mitch Wheat's comment about CASCADE DELETE being dangerous. This feature is interesting, but I have never, ever used it in a production system.
If you are asking how to get rid of the constraint so that you can delete, DO NOT consider doing that. The constraint is there for a reason. If you don't know the reason, don't delete it.
Others have suggested adding a cascade delete. I suggest that this is poor idea as you can cause performance problems on the database. It is better to write a script that deletes the records in the correct order. In this case you need to delete the matching records from tables2 first, then run the delete on table 1.
You also need to evaluate the data in table2 before deciding to delete from it either by script or by cascade delte. If you should not be deleting those records from table2, then you should not be delting the records from table1 (by removing the constraint) as this will casue the table 2 records to be orphaned and you will lose the integrity of the data (which should be your first concern in any database actions (security and performance being a very close second and third)).
Let me give you a scenario where the data in table 2 would indicate you should not delete the record. Suppose you have a customer table and and order table. You want to delete customer A, but he has an order in the past. If you delete both records, you will mess up all the accounting information on orders. If you delete the customer but not the order (by eliminating the constraint), then you have an order that you can no longer tell who it was sent to. The correct thing to do in a case like this is to have an ISactive file in the customers table and mark him as an inactive customer. You would of course need to redesign the code that searches for customer information to make sure it includes the flag to only select active customers which is why this sort of thing should be thought out at the beginning of development not later (one reason why it is worth your while to hire database specialists for the design phase as many application developers don't consider maintaining the data over time as part of their design process).