Why does my trigger to avoid duplicate rows not work? - sql

Before anyone suggests a unique index or key, I have a very good case for this.
I am using this trigger, from Trigger to prevent Insertion for duplicate data of two columns:
CREATE TRIGGER LogDuplicates ON bkPersonPoints
FOR INSERT
AS
if exists (select * from bkPersonPoints c
inner join inserted i
on c.Name = i.Name and c.Points = i.Points)
begin
rollback
end
GO
That answer is accepted and has 15 up-votes, so I would expect it to work, yet even on my very first insert, into an empty table:
insert bkPersonPoints (Name, Points) values ('Brady', 100)
I get the error:
The transaction ended in the trigger. The batch has been aborted.
APPENDIX: The table looks like this:
CREATE TABLE [dbo].[bkPersonPoints](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Points] [int] NOT NULL
) ON [APP_BR2_User]

This is happening because it is detecting the record you're currently inserting to the table. You need to filter that out of the EXISTS clause:
CREATE TRIGGER LogDuplicates ON bkPersonPoints
FOR INSERT
AS
if exists (select * from bkPersonPoints c
inner join inserted i
on c.Name = i.Name
and c.Points = i.Points
and c.id <> i.id)
begin
rollback
end
GO

Anyway, suggest CONSTRAINT
ALTER TABLE bkPersonPoints
ADD CONSTRAINT c_uniq_Name_and_points UNIQUE (Name, Points)

This answer was inspired by one posted on Apr 13 '20 at 18:34 in Trigger to prevent Insertion for duplicate data of two columns.
CREATE TRIGGER MyTrigger ON dbo.MyTable
INSTEAD OF INSERT
AS
if not exists (
select * from MyTable t
inner join inserted i
on i.name=t.name and i.date=t.date and i.id <> t.id )
begin
Insert into MyTable (Name, Date) Select Name, Date from inserted
end
else
THROW 51000, 'Statement terminated because a duplicate was found for the object', 1;
go

Related

Trigger to update foreign key field after insert on same table

I have two tables:
Table1 (surveyid [PKID], surveyname)
Table2 (visitid [PKID], surveyname, surveyid [FKID - refers to Table1]).
After inserting a new row into Table2, I would like to update Table2.surveyid with the surveyid from Table1, based on matching surveyname.
I thought it maybe wasn't possible (or good practice?) to create a trigger to update the same table. But I seem to have created a trigger that will do this. The problem is that after insert, the trigger updates the surveyid for every row, instead of just the newly inserted rows.
This trigger code works, but how do I ensure the trigger only updates the surveyid for newly inserted rows, and not all rows?
CREATE TRIGGER tr_update_table2_fk
ON Table2
AFTER INSERT
AS
BEGIN
UPDATE Table2
SET surveyid = (SELECT t1.surveyid
FROM Table1 t1
WHERE t1.surveyname = Table2.surveyname)
END;
Thank you MattM and DaleK, you've helped me figure out the answer. I was adding the inserted table into the subquery where clause before, instead of the query where clause. This does the trick:
CREATE TRIGGER tr_update_table2_fk
on Table2
AFTER INSERT
AS
BEGIN
UPDATE Table2 SET
surveyid = (
SELECT t1.surveyid
FROM Table1 t1
WHERE t1.surveyname = Table2.surveyname
)
WHERE Table2.visitid IN (SELECT visitid FROM inserted)
END;
Yes, the inserted table is the answer.
I'd use it to capture the visitids of the inserted rows, then filter by them in a WHERE clause on the end of your UPDATE statement in your trigger.
E.g.
CREATE OR ALTER TRIGGER tr_update_table2_fk
ON Table2
AFTER INSERT
AS
BEGIN
DROP TABLE IF EXISTS #VisitIds ;
CREATE TABLE #VisitIds ( [id] INT ) ;
INSERT INTO #VisitIds ( [id] )
SELECT [visitid]
FROM inserted ;
UPDATE Table2
SET [surveyid] =
(
SELECT t1.[surveyid]
FROM Table1 AS t1
WHERE t1.[surveyname] = Table2.[surveyname]
)
WHERE [visitid] IN ( SELECT [id] FROM #VisitIds ) ;
END
GO

How to write a check to avoid the message "INSERT statement conflicted with the FOREIGN KEY constraint"?

I read and understood the entries in following asked question: INSERT statement conflicted with the FOREIGN KEY constraint
. I do get the point, however, I'm in this situation where I have around 1 Gb of records need to be inserted into a table, some of those records have conflicted foreign key. The query looks like this:
IF NOT EXISTS (SELECT * FROM [dbo].[tbl_R_TaskHistory] WHERE [TaskID] =
10000529)
BEGIN insert into [dbo].[tbl_History]
([TaskID],[UserID],[ActD],[RequestD],[No],[SignID],[Completed])
values (10000529,'A0000187',NULL,5738366,0,NULL,CAST(N'2011-03-16
04:53:37.210' AS DateTime)) END
The conflict ocurs on RequestID, so I was thinking there must be a way to make a check to avoid the error messages.
My point is that I want my query to check if the RequestID has not FOREIGN KEY constraint it will not insert this record and move to the next one.
If your query contains only one row, you can just expand the check like this:
IF NOT EXISTS (SELECT * FROM [dbo].[tbl_R_TaskHistory] WHERE [TaskID] = 10000529) AND EXISTS(SELECT 1 FROM [dbo].[...referencing table...] WHERE [RequestD] = 5738366)
BEGIN
insert into [dbo].[tbl_History] ([TaskID],[UserID],[ActD],[RequestD],[No],[SignID],[Completed])
values (10000529,'A0000187',NULL,5738366,0,NULL,CAST(N'2011-03-16 04:53:37.210' AS DateTime));
END
Anyway, if you are inserting many rows at the same time and for performance considerations, it will be better to store the values in buffer table. Something like this:
insert into #tbl_History ([TaskID],[UserID],[ActD],[RequestD],[No],[SignID],[Completed])
values (10000529,'A0000187',NULL,5738366,0,NULL,CAST(N'2011-03-16 04:53:37.210' AS DateTime))
,(...)
,(...)
,(...)
Then, just perform an inner join to your referencing table:
insert into [dbo].[tbl_History] ([TaskID],[UserID],[ActD],[RequestD],[No],[SignID],[Completed])
SELECT [TaskID],[UserID],[ActD],[RequestD],[No],[SignID],[Completed]
FROM #tbl_History A
INNER JOIN [dbo].[...referencing table...] B
ON A.[RequestD] = B.[RequestD];
This syntax also works
declare #a int = 5;
declare #b int = 18;
insert into sample (a, b)
select #a, #b
where not exists (select 1 from sample where b = #b)
and exists (select 1 from student where iden = #a)
This avoids creating a #temp
insert into sample (a, b)
select a, b
from ( values (5,19)
, (5,30)
, (5,31)
, (5,32)
, (7,41)
, (7,42)
) v(a,b)
where not exists (select 1 from sample where b = v.b)
and exists (select 1 from student where iden = v.a)

Adding multiple values to a Table in SQL using a trigger and audit table

The issue I am having is when I insert more than one value into a table or delete a value that exists more than once in a table. I am unsure how to work around this issue.
`CREATE TRIGGER [dbo].[Q5Trigger]
ON [dbo].[WF]
AFTER INSERT, DELETE
AS
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'AuditTable')
BEGIN
CREATE TABLE [dbo].[AuditTable](
Word VARCHAR(100),
Frequency INT,
Date DATETIME,
Type VARCHAR(100)
)
END
IF EXISTS (SELECT * FROM inserted)
BEGIN
INSERT INTO AuditTable VALUES((SELECT Word FROM inserted),(SELECT Frequency FROM inserted), CURRENT_TIMESTAMP, 'Inserted')
END
IF EXISTS (SELECT * FROM deleted)
BEGIN
INSERT INTO AuditTable VALUES((SELECT Word FROM deleted),(SELECT Frequency FROM deleted), CURRENT_TIMESTAMP, 'Deleted')
END`
You misunderstand how triggers and INSERTED and DELETED work. When you insert 50 records into a table with a trigger, the trigger gets called once and the INSERTED table has 50 records in it. To do what you are doing, you must insert into the 1 table ALL the records of the other table.
INSERT INTO AuditTable (Word,Frequency,LogWhen,LogType)
SELECT Word,Frequency, CURRENT_TIMESTAMP, 'Inserted' FROM inserted
Your delete would be very similar to this.

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.

Create trigger prevent insert

I'm trying to execute the following trigger:
create trigger t23
on studies
after insert, update, delete
as
begin
REFERENCING NEW ROW NewStudent
FOR EACH ROW
WHEN (30 <= (SELECT SUM(credits) FROM Studies)
DELETE FROM NewStudent N
WHERE N.spnr = NewStudent.spnr
end
I'm trying to create a trigger which only inserts a student if the credits is < or == to '30'. The "Credits" is a type int.
I'm getting numerous errors trying to implement this trigger. I really have tried everything and i m out of options. Could someone who is expert in the field point me in the right direction?
The example "Using a DML AFTER trigger to enforce a business rule between the PurchaseOrderHeader and Vendor tables" in the CREATE TRIGGER MSDN documentation does exaclty what you're looking for:
USE AdventureWorks2008R2;
GO
IF OBJECT_ID ('Purchasing.LowCredit','TR') IS NOT NULL
DROP TRIGGER Purchasing.LowCredit;
GO
-- This trigger prevents a row from being inserted in the Purchasing.PurchaseOrderHeader table
-- when the credit rating of the specified vendor is set to 5 (below average).
CREATE TRIGGER Purchasing.LowCredit ON Purchasing.PurchaseOrderHeader
AFTER INSERT
AS
DECLARE #creditrating tinyint, #vendorid int;
IF EXISTS (SELECT *
FROM Purchasing.PurchaseOrderHeader p
JOIN inserted AS i
ON p.PurchaseOrderID = i.PurchaseOrderID
JOIN Purchasing.Vendor AS v
ON v.BusinessEntityID = p.VendorID
WHERE v.CreditRating = 5
)
BEGIN
RAISERROR ('This vendor''s credit rating is too low to accept new purchase orders.', 16, 1);
ROLLBACK TRANSACTION;
RETURN
END;
The key here is ROLLBACK TRANSACTION, just adapt the example to suit your need and you're done.
Edit: This should accomplish what you're looking for, but I have not tested it so your mileage may vary.
create trigger dbo.something after insert as
begin
if exists ( select * from inserted where sum(credits) > 30 )
begin
rollback transaction
raiserror ('some message', 16, 1)
end
end
Another edit, based on some assumptions (please note I wrote this script on the fly since I can't test it right now):
create table dbo.students
(
student_id int not null,
name varchar (50) not null
)
create table dbo.courses
(
course_id int not null,
name varchar (50) not null,
required_credits int not null
)
create table dbo.results
(
student_id int not null,
course_id int not null,
course_result int not null
)
create trigger dbo.check_student_results on dbo.results after insert as
(
declare #check int
select #check = count(*)
from inserted as a
join dbo.courses as b on b.course_id = a.course_id
where b.required_credits > a.course.result
if #check <> 0
begin
rollback transaction
raiserror('The student did not pass the course.', 16, 1)
end
)
This way when you insert records in the dbo.results table the constraint checks if the student has passed the course, and cancels the insertion if appropriate. However, it's better to check this things in the application layer.