Replace Create Trigger with Foreign Key (RI) - sql

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

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;

How to not to delete if record exists as a foreign in another table?

I have 2 tables. If I delete a record from table1 then first query should check if it's pk exists as a foreign key in table2 then it should not delete the record else it should.
I used this but throwing syntax error
DELETE FROM Setup.IncentivesDetail
INNER JOIN Employee.IncentivesDetail ON Setup.IncentivesDetail.IncentivesDetailID = Employee.IncentivesDetail.IncentiveDetail_ID
WHERE Setup.IncentivesDetail.IncentivesDetailID= #IncentivesDetailID
AND Employee.IncentivesDetail.IncentiveDetail_ID= #IncentivesDetailID
UPDATE:
Based on the answers below I have done this, is it correct ?
If Not Exists(Select * from Employee.IncentivesDetail where IncentivesDetail.IncentiveDetail_ID= #IncentivesDetailID)
Begin
Delete from Setup.IncentivesDetail
WHERE Setup.IncentivesDetail.IncentivesDetailID= #IncentivesDetailID
End
Else
Begin
RAISERROR('Record cannot be deleted because assigned to an employee',16,1)
RETURN
End
Maybe like this?
DELETE FROM Setup.IncentivesDetail
WHERE Setup.IncentivesDetail.IncentivesDetailID= #IncentivesDetailID
AND NOT EXISTS(SELECT 1 FROM Employee.IncentivesDetail WHERE IncentiveDetail_ID= #IncentivesDetailID)
But I must admit, this smells a bit... Are you doing a clean up or ist this something you are doing regularely?
What you are describing is the definition of the foreign key constraint.
If you already have a foreign key between these tables, make sure it's not marked as ON DELETE CASCADE.
If it is, you should delete it and re-create it without that ON DELETE CASCADE see this link from MSDN for details.
If you don't already have a foreign key constraint, you need to create one:
ALTER TABLE Setup.IncentivesDetail
ADD CONSTRAINT FK_Setup_IncentivesDetail_IncentivesDetailID FOREIGN KEY (IncentivesDetailID)
REFERENCES Employee.IncentivesDetail (IncentiveDetail_ID )
;

multiple cascade paths, on update cascade

I have a problem with sql server, when I want to have 3 table and make relationship between them, and change the "On Update" property to "cascade".
this problem happend when I want to save the diagram:
Introducing FOREIGN KEY constraint 'FK_Company_Slave' on table 'Company' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
in this picture, I define my data base, FK and ....
thanks.
First:
A FOREIGN KEY in one table points to a PRIMARY KEY in another table. if you don't want to use PRIMARY KEY on other table in order to foreign key, you must be create unique index on the table.
Second:
you can create after trigger on Master table in order to develop on update cascade manually. In other word your foreign key between Company table and Master table created without on update cascade and then create following trigger on master to update company table after changed row in Master table.
create trigger triggername on dbo.[Master]
After Insert
AS Begin
Update Company
Set MasterKey = I.MasterKey
From Inserted I
Inner join Deleted D on D.Code = I.Code
Where Company.MasterKey = D.MasterKey
End

How to update 2 columns in 2 tables that have foreign key

I know the question of how to update multiple tables in SQL has been asked before and the common answer seems to be do them separately in a transaction.
However, the 2 columns I need to update have a foreign key so cannot be updated separately.
e.g.
Table1.a is a foreign key to Table2.a
One of the entries in the tables is wrong, e.g. both columns are 'xxx' and should be 'yyy'
How do I update Table1.a and Table2.a to be 'yyy'?
I know I could temp remove the key and replace but surely there's another way.
Thanks
You can't do the update simultaneously, however you can force SQL to do the update. You need to make sure your foreign keys have the referential triggered action ON UPDATE CASCADE
e.g.
ALTER TABLE YourTable
ADD CONSTRAINT FK_YourForeignKey
FOREIGN KEY (YourForeignKeyColumn)
REFERENCES YourPrimaryTable (YourPrimaryKeyColumn) ON UPDATE CASCADE
Not being a fan of on update cascade, I would suggest a different route.
First you do not update the Parent table, you add a new record with the value you want (and the same data as the other record for all other fields). Then you have no difficulty updating the child tables to use this value instead of that value. Further you now have the ability to to do the work in batches to avoid locking the system up while the change promulgates through it. Once all the child tables have been updated, you can delete the original bad record.
my answer is based on the following link: http://msdn.microsoft.com/en-us/library/ms174123%28v=SQL.90%29.aspx
You need to make sure that your table_constraint will be defined as ON UPDATE CASCADE
CREATE TABLE works_on1
(emp_no INTEGER NOT NULL,
project_no CHAR(4) NOT NULL,
job CHAR (15) NULL,
enter_date DATETIME NULL,
CONSTRAINT prim_works1 PRIMARY KEY(emp_no, project_no),
CONSTRAINT foreign1_works1 FOREIGN KEY(emp_no) REFERENCES employee(emp_no) ON DELETE CASCADE,
CONSTRAINT foreign2_works1 FOREIGN KEY(project_no) REFERENCES project(project_no) ON UPDATE CASCADE)
and then when you will change the value of your primary key
see the following quote:
For ON DELETE or ON UPDATE, if the CASCADE option is specified, the
row is updated in the referencing table if the corresponding
referenced row is updated in the parent table. If NO ACTION is
specified, SQL Server Compact Edition returns an error, and the update
action on the referenced row in the parent table is rolled back.
For example, you might have two tables, A and B, in a database. Table
A has a referential relationship with table B: the A.ItemID foreign
key references the B.ItemID primary key.
If an UPDATE statement is executed on a row in table B and an ON
UPDATE CASCADE action is specified for A.ItemID, SQL Server Compact
Edition checks for one or more dependent rows in table A. If any
exist, the dependent rows in table A are updated, as is the row
referenced in table B.
Alternatively, if NO ACTION is specified, SQL Server Compact Edition
returns an error and rolls back the update action on the referenced
row in table B when there is at least one row in table A that
references it.

Self referencing foreign-key constraints and delete

what is the recommended way to handle self-referencing foreignkey constraints in SQL-Server?
Table-Model:
fiData references a previous record in tabData. If i delete a record that is referenced by fiData, the database throws an exception:
"The DELETE statement conflicted with the SAME TABLE REFERENCE
constraint "FK_tabDataPrev_tabDataNext". The conflict occurred in
database "MyDataBase", table "dbo.tabData", column 'fiData'"
if Enforce Foreignkey Constraint is set to "Yes".
I don't need to cascade delete records that are referenced but i would need to set fiData=NULL where it's referenced. My idea is to set Enforce Foreignkey Constraint to "No" and create a delete-trigger. Is this recommendable or are there better ways?
Thank you.
Unlike Andomar, I'd be happy using a trigger - but I wouldn't remove the constraint checking. If you implement it as an instead of trigger, you can reset the other rows to null before performing the actual delete:
CREATE TRIGGER T_tabData_D
on tabData
instead of delete
as
set nocount on
update tabData set fiData = null where fiData in (select idData from deleted)
delete from tabData where idData in (select idData from deleted)
It's short, it's succinct, it wouldn't be necessary if SQL Server could handle foreign key cascades to the same table (in other RDBMS', you may be able to just specify ON DELETE SET NULL for the foreign key constraint, YMMV).
Triggers add implicit complexity. In a database with triggers, you won't know what a SQL statement does by looking at it. In my experience triggers are a bad idea with no exceptions.
In your example, setting the enforced constrained to "No" means you could add a nonexistent ID. And the query optimizer will be less effective because it can't assume the key is valid.
Consider creating a stored procedure instead:
create procedure dbo.NukeTabData(
#idData int)
as
begin transaction
update tabData set fiData = null where fiData = #idData
delete from tabData where idData = #idData
commit transaction
go
This very late to answer.
But for some one who is searching like me.
and want to cascade
here is very good explanation
http://devio.wordpress.com/2008/05/23/recursive-delete-in-sql-server/
The Problem
Although you can define a foreign key with CASCADE DELETE in SQL Server, recursive cascading deletes are not supported (i.e. cascading delete on the same table).
If you create an INSTEAD OF DELETE trigger, this trigger only fires for the first DELETE statement, and does not fire for records recursively deleted from this trigger.
This behavior is documented on MSDN for SQL Server 2000 and SQL Server 2005.
The Solution
Suppose you have a table defined like this:
CREATE TABLE MyTable (
OID INT, -- primary key
OID_Parent INT, -- recursion
... other columns
)
then the delete trigger looks like this:
CREATE TRIGGER del_MyTable ON MyTable INSTEAD OF DELETE
AS
CREATE TABLE #Table(
OID INT
)
INSERT INTO #Table (OID)
SELECT OID
FROM deleted
DECLARE #c INT
SET #c = 0
WHILE #c <> (SELECT COUNT(OID) FROM #Table) BEGIN
SELECT #c = COUNT(OID) FROM #Table
INSERT INTO #Table (OID)
SELECT MyTable.OID
FROM MyTable
LEFT OUTER JOIN #Table ON MyTable.OID = #Table.OID
WHERE MyTable.OID_Parent IN (SELECT OID FROM #Table)
AND #Table.OID IS NULL
END
DELETE MyTable
FROM MyTable
INNER JOIN #Table ON MyTable.OID = #Table.OID
GO