Delete from 2 tables with foreign key constraints - sql

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.

Related

How to create a trigger for this situation?

I have a problem inserting values into a Class table.
I want to write a trigger to prevent happening "an instructor teaches in different class_Id at the same time".
How can I do this?
CREATE TABLE Class
(
Class_ID BIGINT,
c_InstrumentID BIGINT NOT NULL,
c_StudentID BIGINT,
c_InstructorID BIGINT NOT NULL,
c_InstituteId BIGINT NOT NULL,
c_TermSeason NVARCHAR(10),
c_TermYear INT,
c_TimeOfClass TIME NOT NULL,
c_DayOfClass NVARCHAR(30),
c_Eligibility INT,
c_RemainingSession INT,
CONSTRAINT cons_Season
CHECK(c_TermSeason IN ('Spring', 'Summer', 'Fall', 'Winter')),
CONSTRAINT cons_TimeClass
CHECK(c_TimeOfClass BETWEEN '08:30:00' AND '20:30:00'),
CONSTRAINT cons_RemainSession
CHECK (c_RemainingSession BETWEEN 0 AND 12),
FOREIGN KEY(c_InstrumentID)
REFERENCES Instrument(Instrument_ID) ON DELETE NO ACTION,
FOREIGN KEY(c_StudentID)
REFERENCES Student(Student_ID) ON DELETE NO ACTION,
FOREIGN KEY(c_InstructorID)
REFERENCES Instructor(Instructor_ID) ON DELETE NO ACTION,
FOREIGN KEY(c_InstituteId)
REFERENCES Institute(Institute_ID) ON DELETE NO ACTION,
PRIMARY KEY (Class_ID)
)
This is the trigger which I've created:
CREATE OR ALTER TRIGGER One_InstructorDuplicate
ON Class
AFTER INSERT
AS
BEGIN
IF (NOT EXISTS (SELECT *
FROM Class C, ((SELECT * FROM CLASS)
EXCEPT (SELECT * FROM inserted)) AS newC
WHERE newC.c_InstructorID = C.c_InstructorID
AND newC.c_DayOfClass != C.c_DayOfClass
AND newC.c_TermSeason != C.c_TermSeason
AND newC.c_TermYear != C.c_TermYear
AND newC.c_TimeOfClass != C.c_TimeOfClass))
ROLLBACK TRAN
END;
Use inserted and JOIN to the Class table. Check for existence of rows in table that matches your requirement (c_DayOfClass, c_TermSeason etc)
CREATE OR ALTER TRIGGER One_InstructorDuplicate
ON Class
AFTER INSERT
AS
BEGIN
IF EXISTS
(
SELECT *
FROM inserted i
INNER JOIN Class c ON i.c_InstructorID = c.c_InstructorID
WHERE i.Class_ID <> c.Class_ID
AND i.c_DayOfClass = c.c_DayOfClass
AND i.c_TermSeason = c.c_TermSeason
AND i.c_TermYear = c.c_TermYear
AND i.c_TimeOfClass = c.c_TimeOfClass
)
BEGIN
ROLLBACK TRAN
END
END;

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

how to add cascade constraint on existing columns

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.

Trigger After Update SQL

I have Customer table. To simplify lets say i have two columns
Id
Name
I have a second table (Log) that I want to update ONLY when the Id column of my customer changes. Yes you heard me right that the primary key (Id) will change!
I took a stab but the NewId that gets pulled is the first record in the Customer table not the updated record
ALTER TRIGGER [dbo].[tr_ID_Modified]
ON [dbo].[customer]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF UPDATE (Id)
BEGIN
UPDATE [log]
SET NewId = Id
FROM customer
END
END
Many would make the argument that if you are changing PK values, you need to rethink the database/table design. However, if you need a quick & dirty fix, add a column to the customer table that is unique (and not null). Use this column to join between the [inserted] and [deleted] tables in your update trigger. Here's a sample script:
CREATE TABLE dbo.Customer (
Id INT CONSTRAINT PK_Customer PRIMARY KEY,
Name VARCHAR(128),
UQColumn INT IDENTITY NOT NULL CONSTRAINT UQ_Customer_UQColumn UNIQUE
)
CREATE TABLE dbo.[Log] (
CustomerId INT NOT NULL,
LogMsg VARCHAR(MAX)
)
INSERT INTO dbo.Customer
(Id, Name)
VALUES
(1, 'Larry'),
(2, 'Curley'),
(3, 'Moe')
INSERT INTO dbo.[Log]
(CustomerId, LogMsg)
VALUES
(1, 'Larry is cool'),
(1, 'Larry rocks'),
(2, 'Curley cracks me up'),
(3, 'Moe is mean')
CREATE TRIGGER [dbo].[tr_Customer_Upd]
ON [dbo].[customer]
FOR UPDATE
AS
BEGIN
UPDATE l
SET CustomerId = i.Id
FROM inserted i
JOIN deleted d
ON i.UQColumn = d.UQColumn
JOIN [Log] l
ON l.CustomerId = d.Id
END
SELECT *
FROM dbo.[Log]
UPDATE dbo.Customer
SET Id = 4
WHERE Id = 1
SELECT *
FROM dbo.[Log]

inserting into A errors because of a foreign key contraint issue

Can someone help explain this to me and resolve it?
http://sqlfiddle.com/#!6/2adc7/9
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_tblMobileForms_tblForms". The conflict occurred in database "db_6_2adc7", table "dbo.tblForms", column 'fm_id'.: insert into tblMobileForms(fm_name) values ('lol')
My schema has the ID from tblMobileForms be a foreign key to tblForms.fm_id
To do what you are trying to do you cannot set up the FK on tblMobileForms as an identity. See my fiddle below for more information.
http://sqlfiddle.com/#!6/be6f7/2
Alternatively what you could do is to have tblMobileForms have it's own separate surrogate key and have a different FK column to the tblForms table.
The PK on the tblMobileForms table has the same name as the FK on the same table. Seeing the PK is an IDENTITY column, you can end up with non-matching values.
In my fiddle, the tblForms table contained IDs in the upper 60s. Running the INSERT in the child table would add a record with id 1, which does not exist in the parent table.
I'd create a new row in the tblMobileForms table, and reference that to the parent table.
You could use an INSTEAD OF trigger to apply a random ID to each mobile form as it is inserted:
CREATE TRIGGER dbo.tblMobileForms_Insert
ON dbo.tblMobileForms
INSTEAD OF INSERT
AS
BEGIN
DECLARE #Inserted TABLE (fm_ID INT, fm_html_file VARBINARY(MAX), fm_name NVARCHAR(50));
INSERT #Inserted (fm_ID, fm_html_File, fm_Name)
SELECT fm_ID, fm_html_File, fm_Name
FROM inserted;
IF EXISTS (SELECT 1 FROM #Inserted WHERE fm_ID IS NULL)
BEGIN
WITH NewRows AS
( SELECT fm_ID, fm_html_File, fm_Name, RowNumber = ROW_NUMBER() OVER (ORDER BY fm_name)
FROM #Inserted
WHERE fm_ID IS NULL
), AvailableIDs AS
( SELECT fm_ID, RowNumber = ROW_NUMBER() OVER (ORDER BY fm_ID)
FROM tblForms f
WHERE NOT EXISTS
( SELECT 1
FROM tblMobileForms m
WHERE f.Fm_ID = m.fm_ID
)
AND NOT EXISTS
( SELECT 1
FROM inserted i
WHERE f.fm_ID = i.fm_ID
)
)
UPDATE NewRows
SET fm_ID = a.fm_ID
FROM NewRows n
INNER JOIN AvailableIDs a
ON a.RowNumber = n.RowNumber
IF EXISTS (SELECT 1 FROM #Inserted WHERE fm_ID IS NULL)
BEGIN
RAISERROR ('Not enough free Form IDs to allocate an ID to the inserted rows', 16, 1);
RETURN;
END
END
INSERT dbo.tblMobileForms (fm_ID, fm_html_File, fm_Name)
SELECT fm_ID, fm_html_file, fm_name
FROM #Inserted
END
When each row is inserted the trigger will check for the next available ID in tblForms and apply it sequentially to the inserted rows where fm_id is not specified. If there are no free ID's in tblForms then the trigger will throw an error so a 1 to 1 relationship is maintained (The error would be thrown anyway since tblMobileForms.fm_id is also a PK).
N.b. this requires tblForms.fm_ID to just be an int column, and not identity.