how to add cascade constraint on existing columns - sql

I have a table t1 which has a primary key t1id.I have another table t2 which has column t1id which is a forign key to t1id of t1 table.Please see the query
create table T1
(
t1idint primary key IDENTITY(1,1),
Name varchar(200) not null
);
create table T2
(
t2idint primary key IDENTITY(1,1),
t1id int,
nod bigint,
foreign key ( t1id) references T1(t1id)
);
and many more tables linked with T2 table that I am not showing here.
I have inserted some values in both Tables T1 and T2.
Now to delete a row from T1 ,I have to 1st delete a row from T2 and then only I can be able to delete a row from T1 because of foriegn key relationship.
So I thought adding cascading constraint would be good idea
I tried like the below
ALTER TABLE T2
ADD CONSTRAINT fk_T2id
FOREIGN KEY (t1id)
REFERENCES T1(t1id)
ON DELETE CASCADE;
but I got the below error
Msg 1785, Level 16, State 0, Line 1
Introducing FOREIGN KEY constraint 'fk_t1id' on table 'T2' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Msg 1750, Level 16, State 0, Line 1
Could not create constraint. See previous errors.

I found myself in your situation many times, but never used a cascade. I do a SP that will get the ID I want to delete and I do the delete myself in a transaction. See example
CREATE PROCEDURE [dbo].[spConfiguration_Table1_Del]
#ID int
AS
Declare #ERR int
set #ERR = 0
begin tran
DELETE FROM Table1 WHERE ID = #ID
set #ERR = ##Error
if #ERR = 0 begin
DELETE FROM Table2 WHERE ID IN (SELECT ID FROM Table3 WHERE ID = #ID)
set #ERR = ##Error
end
if #ERR = 0 begin
DELETE FROM Table3 WHERE ID = #ID
set #ERR = ##Error
end
if #ERR = 0 commit tran
else rollback tran
This way you can control the way the delete happens, also if there is an error in deleting from one table, the transaction will rollback all the deleted rows and you keep your data consistent.

Related

How to prevent insert when condition is met

I have a two tables
CREATE TABLE table1 (
ID int not null,
Status int not null,
TimeStamp datetime2(3)
CONSTRAINT pkId PRIMARY KEY (ID)
);
and
CREATE TABLE Status (
StatusID int not null,
StatusName varchar(64) not null
CONSTRAINT pStatus PRIMARY KEY (StatusID)
);
with inserted values
INSERT INTO table1 (ID,StatusID,TimeStamp) VALUES
(1,1,'2021-06-15 07:30:31'),
(2,2,'2021-07-15 07:30:31'),
(3,3,'2021-08-15 07:30:31'),
(4,4,'2021-09-15 08:30:31'),
(5,5,'2021-09-15 07:30:31'),
(6,5,'2021-09-15 07:30:31'),
(7,4,'2021-09-15 07:30:31'),
(8,2,'2021-09-15 07:30:31'),
(9,1,'2021-09-15 07:30:31');
and
INSERT INTO dbo.Status (StatusID,StatusName) VALUES
(1,'wants to enroll'),
(2,'granted enrollment'),
(3,'declined enrollment'),
(4,'attending the course'),
(5,'finished the course');
I wrote a trigger but it prevent all insert when the StatusID < 4 and what I want is to prevent insert into TimeStamp only when the condition is StatusID < 4. I use SQL Server.
CREATE TRIGGER dbo.Prevent_Insert4
ON dbo.table1
FOR INSERT
AS
SET NOCOUNT ON
IF EXISTS(SELECT *
FROM dbo.table1 t
JOIN inserted i ON t.ID=i.ID
WHERE t.StatusID<4)
BEGIN
RAISERROR('You can not update when StatusId value < 4', 16, 1)
ROLLBACK TRAN
SET NOCOUNT OFF
RETURN
END
SET NOCOUNT OFF
Thank you in advance.
Sounds like you just need a CHECK constraint
ALTER TABLE table1
ADD CONSTRAINT CHK_Status
CHECK(Status >= 4 OR TimeStamp IS NULL);
If you really, really wanted to use triggers for this (not recommended) then it should look like this
CREATE OR ALTER TRIGGER dbo.Prevent_Insert4
ON dbo.table1
FOR INSERT, UPDATE
AS
SET NOCOUNT ON;
IF EXISTS(SELECT 1
FROM inserted i
WHERE i.StatusID < 4
AND i.TimeStamp IS NOT NULL)
BEGIN
THROW 50001, 'You can not set TimeStamp when StatusId value < 4', 1;
-- no need for rollback, will happen automatically if you use THROW
END;
go
With your code you are doing rollback of inserted row when condition is met. If I understood you correctly you just to want override timestamp with null value if StatusId < 4.
If so, you should write that instead of rollback
IF EXISTS(SELECT *
FROM dbo.table1 t
JOIN inserted i ON t.ID=i.ID
WHERE t.StatusID<4)
BEGIN
UPDATE t SET t.TimeStamp = NULL
FROM inserted i
JOIN dbo.table1 t ON i.ID = t.ID
WHERE t.StatusID<4
END

INSERT statement conflicting with FOREIGN KEY constraint, but there's no conflict apparent

I'm not sure why I'm getting this error, because no data is being inserted into the column mentioned in the error. It's a PK field set to IDENTITY, so the value is auto-filled with each record added. The function is supposed to insert a record into an audit table, called by a trigger on the source table. Here's the error and my code.
Msg 547, Level 16, State 0, Procedure tblTriggerAuditRecord_TTeamPlayers, Line 33 [Batch Start Line 344]
The INSERT statement conflicted with the FOREIGN KEY constraint "Z_TTeamPlayers_TTeams_FK". The conflict occurred in database "dbSQL1", table "dbo.Z_TTeams", column 'intTeamAuditID'.
--Problematic Code
DELETE FROM TTeamPlayers
DELETE FROM TTeams
WHERE strTeam = 'Reds'
SELECT * FROM TTeams
SELECT * FROM Z_TTeams
=========================
-- both these tables are updated when a DELETE is run. I have the FK constraint set to CASCADE so that when it's deleted out of the child table Z_TTeamPlayers, the parent record is deleted.
CREATE TABLE Z_TTeamPlayers
(
intTeamPlayerAuditID INTEGER IDENTITY NOT NULL
,intTeamAuditID INTEGER NOT NULL
,intPlayerAuditID INTEGER NOT NULL
,UpdatedBy VARCHAR(50) NOT NULL
,UpdatedOn DATETIME NOT NULL
,strAction VARCHAR(10) NOT NULL
,strModified_Reason VARCHAR(1000)
--,CONSTRAINT PlayerTeam_UQ UNIQUE ( intTeamID, intPlayerID )
,CONSTRAINT Z_TTeamPlayers_PK PRIMARY KEY ( intTeamPlayerAuditID )
)
CREATE TABLE Z_TTeams
(
intTeamAuditID INTEGER IDENTITY NOT NULL
,intTeamID INTEGER NOT NULL
,strTeam VARCHAR(50) NOT NULL
,strMascot VARCHAR(50) NOT NULL
,UpdatedBy VARCHAR(50) NOT NULL
,UpdatedOn DATETIME NOT NULL
,strAction VARCHAR(10) NOT NULL
,strModified_Reason VARCHAR(1000)
,CONSTRAINT Z_TTeams_PK PRIMARY KEY ( intTeamAuditID )
)
==============================
ALTER TABLE Z_TTeamPlayers ADD CONSTRAINT Z_TTeamPlayers_TTeams_FK
FOREIGN KEY ( intTeamAuditID ) REFERENCES Z_TTeams ( intTeamAuditID ) ON DELETE CASCADE
==============================
-- --------------------------------------------------------------------------------
-- Create Trigger for Z_TTeamPlayers
-- --------------------------------------------------------------------------------
GO
CREATE TRIGGER tblTriggerAuditRecord_TTeamPlayers on TTeamPlayers
AFTER UPDATE, INSERT, DELETE
AS
DECLARE #Now DATETIME
DECLARE #Modified_Reason VARCHAR(1000)
DECLARE #Action VARCHAR(10)
SET #Action = ''
-- Defining if it's an UPDATE, INSERT, or DELETE
BEGIN
IF (SELECT COUNT(*) FROM INSERTED) > 0
IF (SELECT COUNT(*) FROM DELETED) > 0
SET #Action = 'UPDATE'
ELSE
SET #Action = 'INSERT'
ELSE
SET #Action = 'DELETE'
END
SET #Now = GETDATE() -- Gets current date/time
IF (#Action = 'INSERT')
BEGIN -- Begin INSERT info
INSERT INTO Z_TTeamPlayers(intTeamAuditID, intPlayerAuditID, UpdatedBy, UpdatedOn, strAction, strModified_Reason)
SELECT I.intTeamID, I.intPlayerID, SUSER_NAME(), GETDATE(), #Action, I.strModified_Reason
FROM INSERTED as I
INNER JOIN TTeamPlayers as T ON T.intTeamPlayerID = I.intTeamPlayerID
END -- End Insert Info
ELSE
IF (#Action = 'DELETE')
BEGIN -- Begin INSERT info
INSERT INTO Z_TTeamPlayers(intTeamAuditID, intPlayerAuditID, UpdatedBy, UpdatedOn, strAction, strModified_Reason)
SELECT D.intTeamID, D.intPlayerID, SUSER_SNAME(), GETDATE(), #Action, ''
FROM DELETED as D
END -- End Delete Info
ELSE -- #Action = 'UPDATE'
BEGIN --begin UPDATE info get modified reason
IF EXISTS (SELECT TOP 1 I.strModified_Reason FROM INSERTED as I, TPlayers as T WHERE I.intPlayerID = T.intPlayerID
AND I.strModified_Reason <> '')
BEGIN -- beging insert of UPDATE info
INSERT INTO Z_TTeamPlayers(intTeamAuditID, intPlayerAuditID, UpdatedBy, UpdatedOn, strAction, strModified_Reason)
SELECT I.intTeamID, I.intPlayerID, SUSER_SNAME(), GETDATE(), #Action, I.strModified_Reason
FROM TTeamPlayers as T
INNER JOIN INSERTED as I ON T.intPlayerID = I.intPlayerID
-- set modified reason column back to empty string
UPDATE TPlayers SET strModified_Reason = NULL
WHERE intPlayerID IN (SELECT TOP 1 intPlayerID FROM INSERTED)
END
ELSE
BEGIN -- begin if no modified reasson supplied
PRINT 'Error and rolled back, please enter modified reason'
ROLLBACK
END
END
z_TTeamPlayers.intTeamAuditID references your audit table's primary key. In your code you are inserting that value into z_TTeamPlayers... INSERT INTO Z_TTeamPlayers(intTeamAuditID... while it doesn't exist (as a primary key) in your audit table yet... thus it fails.
Here is a demo.
I get that you are trying to audit, but i'm not sure your business logic on teams and players. You seem to have your design a bit backwards. You could always use versioning in SQL Server.
As a guess you probably want a design similar to this instead

Alter table to add incrementally primary key records where ID values are null

I have a table in sql server with ID field as the primary key. In the rows of the ID field, some have primary key values while some rows do not have primary key values because the ID column allows null. Now I want to run a query to insert values incrementally into the rows that are null so that they can have primary key values. I have tried using an ALTER command but no head way
because you didn't provide any table structure description and we don't know if there are any business key or some unique combinations of data exists to identify a row without primary key then the easiest way, imho, is to use update cursor:
begin tran
-- rollback
-- commit
select * from [Table_1] where id is null
declare #Id int, #i int = 0
,#MaxId int
set #MaxId = (select Max(Id) from [Table_1] )
declare Update_cur cursor local
for select Id from [Table_1] where id is null
for update of Id
open Update_cur
fetch next from Update_cur into #Id
while ##FETCH_STATUS = 0 begin
set #i += 1
update [Table_1] set Id = #MaxId + #i where CURRENT OF Update_cur
fetch next from Update_cur into #Id
end
close Update_cur
deallocate Update_cur
select * from [Table_1] order by Id
P.S. don't forget to commit or rollback transaction after performing tests
You can DROP that column and ADD again with Auto Increment value.
ALTER TABLE your_table DROP COLUMN ID
ALTER TABLE your_table ADD ID INT IDENTITY(1,1)
This will generate all values from the start and as a result you will lose existing value (upto 6).

Delete from 2 tables with foreign key constraints

I have 2 tables tlbinvoice and tblRel_Inv_Course in which InvoiceID is the foreign key. When I tried to delete a row from the Invoice table, I get an error
Cannot delete foreign key constraint
Below are the 2 queries and data:
select * from invoice where InvoiceID=19
InvoiceID invimagetype location
-----------------------------------
19 image/jpeg network
select * from Rel_Inv_Course where CourseID=4262
Rel_I_C_ID CourseID InvoiceID
----------------------------------
2255 4262 19
What I tried:
delete from [TAP].[dbo].Invoice
where InvoiceID = (select InvoiceID
from Rel_Inv_Course
where CourseID = 4262)
delete from Rel_Inv_Course
where CourseID = 4262
But I can't do this. I need to delete from both the rows of the tables with invoice id as 19. Please help.
As the comments said all you need to do is flip your delete statements and you should be good:
You may consider wraping them in a begin tran so you can check that your deletes only delete the data you want as well:
Begin Tran
DECLARE #INVOICEID INT
SET #INVOICE = (select InvoiceID from Rel_Inv_Course where CourseID=4262)
delete from Rel_Inv_Course where CourseID=4262
delete from [TAP].[dbo].Invoice where InvoiceID =(#INVOICEID)
--Select * from Rel_Inv_Course
--Select * from [dbo].Invoice
--If satisfied with deletes finally commit tran
--If not satisfied --> Rollback Tran
Commit Tran
This might be easier to explain with some sample data and DDL:
USE Sandbox;
GO
CREATE TABLE dbo.Parent (ID int NOT NULL,
SomeString varchar(100) NOT NULL);
GO
CREATE TABLE dbo.Child (ID int NOT NULL,
ParentID int NOT NULL,
AnotherString varchar(100) NOT NULL);
GO
ALTER TABLE dbo.Parent ADD CONSTRAINT PK_PID PRIMARY KEY CLUSTERED (ID);
ALTER TABLE dbo.Child ADD CONSTRAINT PK_CID PRIMARY KEY CLUSTERED (ID);
ALTER TABLE dbo.Child
ADD CONSTRAINT FK_PID
FOREIGN KEY (ParentID)
REFERENCES dbo.Parent (ID);
GO
INSERT INTO dbo.Parent (ID,
SomeString)
VALUES (1, 'sdfkgjbhasdfg'),
(2, 'sdfkjsdbhkf');
GO
INSERT INTO dbo.Child (ID,
ParentID,
AnotherString)
VALUES (1, 1, 'asdfkiashjbd'),
(2, 1, '#asldjasbhdk,'),
(3, 2, 'asfjasdfj');
GO
--Try to delete a row in Parent:
DELETE FROM dbo.Parent
WHERE ID = 2;
--No surprise it failed
GO
--Try to delete a row in child
DELETE FROM dbo.Child
WHERE ID = 2;
--This worked fine.
GO
--
--If we check, however, ParentID 1 and 2 are still in the table:
SELECT *
FROM dbo.Child;
--We want to delete ID 1 in parent, so we need to delete the other row
DELETE FROM dbo.Child
WHERE ParentID = 1;
--Now delete in Parent
DELETE FROM dbo.Parent
WHERE ID = 1;
GO
DROP TABLE dbo.Child;
DROP TABLE dbo.Parent;
You'll notice that the first delete on Parent failed, as it conflicts with the foreign key constraint. After, however, deleting all the rows in child for that ID, you can delete the parent row.
The same logic applies with your data. Delete the relevant rows in the child table first, and then you delete the data in your parent table. Alternatively, implement cascading, and then you simply need to delete the row in the parent, and the deletes will cascade down.

SQL check constraint on multiple tables

So If the "Type" is 0, i should be able to add my person in Table B, else not, but the "Type" column is not and shouldn't be in Table B.
You can do this with a foreign key constraint and some trickery.
First, set up a unique constraint on TableA for both type and person:
alter table TableA add constraint unq_TableA_type_person on TableA(type, person);
This allows you set to set up a foreign key constraint. However, you need a type column. For that, you can use a computed column:
alter table TableB add type_for_a as (0); -- it is always 0
Now, just use a foreign key constraint:
alter table TableB add constraint fk_tableA_type_person
foreign key (type_for_a, person) references tableA(type, person);
Voila! You have the constraint in place without having to write any code.
CREATE TABLE T1 (TypeID INT NOT NULL, people VARCHAR(50));
GO
CREATE TABLE T2 ( people VARCHAR(50));
GO
-- creating trigger to insert on the behalf when there is a particular type
CREATE TRIGGER dbo.AfterInsertTrigger
ON T1
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
declare #id int,
#someval char(1)
insert into dbo.T2
select i.people FROM Inserted i
where i.TypeID=0 -- checks only when the id is 0
END
GO
-- inserting people with different id s into Table1
INSERT T1 (TypeID, people) SELECT 1, 'A';
INSERT T1 (TypeID, people) SELECT 0, 'B';
GO
--selecting from tables see what got affected.
select * from T1
select *from T2
--Clean up
DROP TABLE T2;
DROP TABLE T1;
GO