Good evening.
I had a task to create a trigger which will compare records inserted by
insert into tbl(row1, row2)
values('val1', 'val2')
So I wrote:
CREATE TRIGGER duplikat_miejsce ON miejsce
AFTER INSERT
AS
if exists ( select * from miejsce i
inner join inserted t on i.ulica=t.ulica and i.numer=t.numer and i.miasto=t.miasto and i.kod=t.kod)
begin
RAISERROR ('Adres juz istnieje',1,2)
rollback
end
go
Trigger itself creates. But it doesn't work properly. It gives messages:
Adres juz istnieje
Msg 50000, Level 1, State 2
Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.
And what is most important, it gives the errors, when i DON'T DUPLICATE ANY OF COLUMNS AT ALL. It deny try of inserting ANY record to the table "miejsce"
Insert command I used:
insert into miejsce(id_miejsce, ulica, numer, miasto, kod, telefon, uwagi)
values (6, 'Widmowa', '14', 'Warszawka', '88-800', null, null)
You have created an AFTER Trigger which fire when the changes has been made to the database. you need to create an Instead of trigger so you can roll back any invalid operations before it is committed to the disk.
Something like this......
CREATE TRIGGER duplikat_miejsce ON miejsce
INSTEAD OF INSERT
AS
BEGIN
IF EXISTS (select * from miejsce i
inner join inserted t
on i.ulica = t.ulica
and i.numer = t.numer
and i.miasto = t.miasto
and i.kod = t.kod)
BEGIN
RAISERROR ('Adres juz istnieje',16,1)
END
ELSE
BEGIN
INSERT INTO miejsce (id_miejsce, ulica, numer, miasto, kod, telefon, uwagi)
SELECT t.id_miejsce, t.ulica, t.numer, t.miasto, t.kod, t.telefon, t.uwagi
FROM inserted t
WHERE NOT EXISTS (select 1
from miejsce i
WHERE i.ulica = t.ulica
and i.numer = t.numer
and i.miasto = t.miasto
and i.kod = t.kod)
END
END
If you want to raise error , error severity level must be above 10 because any error under severity level 11 are considered as warring messages not error.
The newly inserted row is already in the table when check is made inside the trigger. Here's a simplified sample to demonstrate it:
create table t (i int, j int);
go
insert t values (1,1);
go
create trigger tr
on t
after insert
as
select * from t;
if exists(select * from t inner join inserted i on t.i = i.i and t.j = i.j)
begin
raiserror ('Adres juz istnieje',1,2);
rollback;
end
go
insert t values(2,2)
go
drop table t
go
If you have a proper primery key, use it in the where clause.
Related
I can detect duplicate records, but when I'm inserting new data it will detect it as a duplicate record even if doesn't already exist.
Here is my code:
ALTER TRIGGER [dbo].[SDPRawInventory_Dup_Trigger]
ON [dbo].[SDPRawInventory]
AFTER INSERT
AS
DECLARE #Year float,
#Month float,
#SDPGroup nvarchar(255);
SELECT
#Year = i.InvYear, #Month = i.InvMonth, #SDPGroup = i.SDPGroup
FROM inserted i;
IF (SELECT COUNT(*) FROM SDPRawInventory A
WHERE A.InvYear = #Year
AND A.InvMonth = #Month
AND A.SDPGroup = #SDPGroup) >= 1
BEGIN
RAISERROR ('Duplicate data', 16, 1)
ROLLBACK;
END
ELSE
BEGIN
INSERT INTO SDPRawInventory
SELECT * FROM inserted;
END
This is the table
And to clarify there is no primary key nor unique identifier.
If you are unable to put a constraint in place, then you need to handle the fact that Inserted may have multiple records. And because its an after insert trigger, you don't need to do anything if no duplicates are found because the records are already inserted.
ALTER TRIGGER [dbo].[SDPRawInventory_Dup_Trigger]
ON [dbo].[SDPRawInventory]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1
FROM dbo.SDPRawInventory S
INNER JOIN Inserted I ON
-- Test for a duplicate
S.InvYear = I.InvYear
AND S.InvMonth = I.InvMonth
AND S.SDPGroup = I.SDPGroup
-- But ensure the duplicate is a *different* record - assumes a unique ID
AND S.ID <> I.ID
)
BEGIN
THROW 51000, 'Duplicate data.', 1;
END;
END;
Note the simplified and modern error handling.
EDIT: And if you have no unique key, and no permission to add one, then you need an instead of trigger to only insert non-duplicates e.g.
ALTER TRIGGER [dbo].[SDPRawInventory_Dup_Trigger]
ON [dbo].[SDPRawInventory]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
-- Reject the entire insert if a single duplicate exists
-- Note if multiple records are inserted, some of which are duplicates and some of which aren't, they all get rejected
IF EXISTS (
SELECT 1
FROM dbo.SDPRawInventory S
INNER JOIN Inserted I ON
-- Test for a duplicate
A.InvYear = I.InvYear
AND A.InvMonth = I.InvMonth
AND A.SDPGroup = I.#SDPGroup
)
-- Test that Inserted itself doesn't contain duplicates
OR EXISTS (SELECT 1 FROM Inserted GROUP BY InvYear, InvMonth, SDPGroup HAVING COUNT(*) > 1)
BEGIN
THROW 51000, 'Duplicate data.', 1;
END;
INSERT INTO dbo.SDPRawInventory (SDP_SKU_DESC, WholeQty, InvYear, InvMonth, SDPGroup, invUOM, LooseQty)
SELECT SDP_SKU_DESC, WholeQty, InvYear, InvMonth, SDPGroup, invUOM, LooseQty
FROM Inserted I
WHERE NOT EXISTS (
SELECT 1
FROM dbo.SDPRawInventory S
-- Test for a duplicate
WHERE S.InvYear = I.InvYear
AND S.InvMonth = I.InvMonth
AND S.SDPGroup = I.SDPGroup
);
END;
Note: This doesn't do anything to handle existing duplicates.
This trigger is executed after the new records were inserted, so it will at least find the original records in the SELECT COUNT statement. Changing >= 1 into >= 2 can only partially fix this when inserting is guaranteed to occur one record as a time. Moreover, it will still fail when there were already multiple duplicated of the newly inserted record in the database before the insert.
You need to exclude the latest inserted records from the COUNT. But a better idea would probably be to add a UNIQUE constraint for preventing duplicates, so no trigger would be necessary.
If adding a constraint is not possible yet, you should initiate a clean-up process to eliminate the existing duplicates beforehand. Everything else is looks pretty flawed to me, since it is unlikely the current approach will ever bring the table into a state with no duplicates.
You are creating the infinite loop. You just have to remove the insertion part from your trigger.
ELSE
BEGIN
INSERT INTO SDPRawInventory
SELECT * FROM inserted;
END
This should not be in the trigger as trigger is called as part of insertion. you should not write actual insertion in to table in trigger.
In SQL Server I am trying to write an INSTEAD OF INSERT trigger on a view that will alter multiple tables when edited through MS Access. The idea is to have an insert statement for records that don't exist and an update for when an associated ID is found. When I use the following trigger it works as intended...
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [ofs].[IO_Trig_OFS]
ON [ofs].[V_OFS_CONTRACTORS]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
-- Check for duplicate Contact. If there is no duplicate, do an insert.
IF (NOT EXISTS (SELECT P.ContactID
FROM [ofs].[OFS_CONTACTS] P, inserted I
WHERE P.ContactID = I.ContactID))
INSERT INTO [ofs].[OFS_CONTACTS]
SELECT
[ContractorID],
[ContactFirstName], [ContactLastName],
[ContactPriority], [JobTitle],
[EmailAddress], [WorkPhone], [WorkExtension],
[CellPhone], [ContactSource]
FROM
inserted
ELSE
--If there is a duplicate, change to UPDATE so that there will not
--be a duplicate key violation error.
UPDATE [ofs].[OFS_CONTACTS]
SET [ContactFirstName] = I.ContactFirstName,
[ContactLastName] = I.ContactLastName,
[ContactPriority] = I.ContactPriority,
[JobTitle] = I.JobTitle,
[EmailAddress] = I.EmailAddress,
[WorkPhone] = I.WorkPhone,
[WorkExtension] = I.WorkExtension,
[CellPhone] = I.CellPhone,
[ContactSource] = I.ContactSource
FROM
[ofs].[OFS_CONTACTS] E, inserted I
WHERE
E.ContactID = I.ContactID
END;
But when I add another table to edit for inserts/updates in the same manner I get this error:
Msg 213, Level 16, State 1, Procedure IO_Trig_OFS, Line 11
Column name or number of supplied values does not match table definition.
I have a feeling it is poor syntax but it would help to get another set of eyes on it. Thanks!
Here is a trigger
CREATE TRIGGER [dbo].[CheckApplyId]
ON [dbo].[AppliedStudent_event] INSTEAD OF INSERT
AS
DECLARE #studentId INT
DECLARE #compReq_Id INT
BEGIN
SELECT #studentId = studentId
FROM INSERTED
SELECT #compReq_Id = compReq_Id
FROM INSERTED
IF EXISTS(SELECT StudentId,
compreq_id
FROM AppliedStudent_event
WHERE StudentId = #studentId
AND compreq_id = #compReq_Id)
BEGIN
ROLLBACK
PRINT 'User Already Applied'
END
END
When in insert a data into a table using command:
INSERT INTO AppliedStudent_event (StudentId, compreq_id)
VALUES (3026, 1)
Message is:
(1 row(s) affected)
But when I execute a sql command no data is inserted in the table.
Can you please tell why are you using trigger because you use only assign the variable #studentId and #compReq_Id from inserted table.
That's a broken trigger because inserted can contain multiple (or no) rows - so a statement like SELECT #ScalarVariable = column from inserted is always wrong.
And it's unnecessary since you can just place a UNIQUE constraint on the StudentId and compreq_id columns:
ALTER TABLE AppliedStudent_event
ADD CONSTRAINT UQ_Student_Events
UNIQUE (StudentId,compreq_id)
And it's further broken because you've specified it as an instead of trigger - that says that your code is going to be responsible for the actual insert - but your code doesn't actually do that. That's why no data ends up in the table.
If you insist on doing it as a trigger, it's actually tricky to get everything correct (that's why I'd really recommend the UNIQUE constraint). It'll end up being something like this:
CREATE TRIGGER [dbo].[CheckApplyId]
ON [dbo].[AppliedStudent_event] INSTEAD OF INSERT
AS
IF EXISTS(select
StudentId,compreq_id,COUNT(*)
from inserted
group by StudentId,compreq_id
HAVING COUNT(*) > 1)
OR EXISTS (select *
from inserted i
inner join
AppliedStudent_event e
on
i.StudentId = e.StudentId and
i.compreq_id = e.compreq_id)
BEGIN
ROLLBACK
PRINT 'User Already Applied'
END
ELSE
BEGIN
INSERT INTO AppliedStudent_event(StudentId,compreq_id /* Other columns? */)
SELECT StudentId,compreq_id /* And again, other columns */
FROM inserted
END
Ok, so I want to create a trigger that fires when someone tries to insert into the table "products" and checks for a valid foreign key. For right now (This is NOT the end design in the least, but I'm using it for testing), I want the trigger to check that the inserted line references a valid ID in the Manufacturer table, and if no such row exists in the Manufacturer table, insert one with the proper ID and some general information for the remaining fields. My current code is as follows:
create trigger checkman
on dbo.products
instead of insert
as
declare
#manid char(5),
#manName varchar(50),
#transactionName varchar(20) = 'transaction1'
Begin
select #manid=Man_ID from Inserted
begin try
/*begin tran #transactionName*/
Insert into Manufacturers (Man_ID, Man_Name, Man_Description) VALUES #manid, 'Unknown Name', 'This is an unknown manufacturer');
insert into dbo.products select * from inserted;
end try
begin catch
/*rollback tran #transactionName;*/
insert into dbo.products select * from inserted;
end catch
End
The problem is that whenever I run my insert with a Manufacturer ID that already exists, I get this error:
(0 row(s) affected)
Msg 3930, Level 16, State 1, Procedure checkman, Line 20
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
The statement has been terminated.
I get the same error when I put the "Insert into Manufacturers" line in the catch block, only this time it appears when I try to insert with an ID that doesn't yet exist.
Don't handle this in a try/catch block, it isn't necessary. Also you need to allow for the fact that inserted can contain multiple rows. You can do this using a NOT EXISTS query:
CREATE TRIGGER CheckMan
ON dbo.products
INSTEAD OF INSERT
AS
INSERT Manufacturers (Man_ID, Man_Name, Man_Description)
SELECT DISTINCT Man_ID, 'Unknown Name', 'This is an unknown manufacturer'
FROM inserted i
WHERE NOT EXISTS
( SELECT 1
FROM Manufacturers m
WHERE m.Man_ID = i.Man_ID
);
INSERT dbo.Products
SELECT *
FROM inserted;
HOWEVER, I don't advocate this approach, I think it would be much better to just rely on the referencial integrity provided by the foreign key itself, and if the Man_ID does not exist let the insert fail, and if necessary ensure all Man_IDs exist before even attempting to insert.
I changed your trigger to following. In doing so, I made a few implicit assumptions. Assuming here that Products is a 3 column table (col1 & col2 being the other columns apart from Man_ID).
Hope this helps
alter
trigger checkman
on dbo.products
instead of insert
as
declare
#manid char(5),
#col1 varchar(50),
#transactionName varchar(20) = 'transaction1',
#col2 Varchar(500)
Begin
select #manid=Man_ID, #col1 = col1, #col2 = col2 from Inserted
IF (NOT EXISTS (SELECT Man_ID
FROM Manufacturers Where Man_ID = #manid
))
INSERT INTO ManuFacturers (Man_ID, Man_name, Man_Description) Values (#manid, 'Unknown Name', 'This is an unknown manufacturer')
INSERT INTO Products (Man_ID, col1, col2) values (#manid, #col1, #col2)
--begin try
-- /*begin tran #transactionName*/
-- Insert into Manufacturers (Man_ID, Man_Name, Man_Description) VALUES (#manid, 'Unknown Name', 'This is an unknown manufacturer')
-- insert into dbo.products select * from inserted;
--end try
--begin catch
-- /*rollback tran #transactionName;*/
-- insert into dbo.products select * from inserted;
--end catch
End
In MS SQL Server 2008 R2, we want a pre-insert and pre-update trigger which checks something and allows or rollbacks (via raiserror) the running insert/update.
Question: In INSTEAD OF trigger. Does one really has to explicitly write the insert or update? Because we want the default insert or update to be done and only do the "precheck".
Yes.
You do need to write the explicit INSERT or UPDATE.
The trigger runs INSTEAD OF the DML operation. If you leave the trigger blank then no action will happen other than the INSERTED / DELETED tables being created and populated in tempdb.
Although from discussion in the comments I would not use a trigger for this at all but use a unique filtered index CREATE UNIQUE INDEX ix ON T(a,b,c) WHERE c <> ''. This is likely to be more performant and avoid potential logic issues when dealing with concurrency.
You probably do not want an INSTEAD OF trigger unless you want to replace the actual insert or update. In your case, you want a FOR INSERT, UPDATE trigger instead.
This example trigger prints a message to the client when anyone tries to add or change data in the titles table.
USE pubs
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'reminder' AND type = 'TR')
DROP TRIGGER reminder
GO
CREATE TRIGGER reminder
ON titles
FOR INSERT, UPDATE
AS RAISERROR ('inserts and updates to the titles table is not allowed', 16, 1)
GO
You could also use things like IF EXISTS or COLUMNS_UPDATED as well.
Here another example that uses rollback.
USE pubs
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'employee_insupd' AND type = 'TR')
DROP TRIGGER employee_insupd
GO
CREATE TRIGGER employee_insupd
ON employee
FOR INSERT, UPDATE
AS
/* Get the range of level for this job type from the jobs table. */
DECLARE #min_lvl tinyint,
#max_lvl tinyint,
#emp_lvl tinyint,
#job_id smallint
SELECT #min_lvl = min_lvl,
#max_lvl = max_lvl,
#emp_lvl = i.job_lvl,
#job_id = i.job_id
FROM employee e INNER JOIN inserted i ON e.emp_id = i.emp_id
JOIN jobs j ON j.job_id = i.job_id
IF (#job_id = 1) and (#emp_lvl <> 10)
BEGIN
RAISERROR ('Job id 1 expects the default level of 10.', 16, 1)
ROLLBACK TRANSACTION
END
ELSE
IF NOT (#emp_lvl BETWEEN #min_lvl AND #max_lvl)
BEGIN
RAISERROR ('The level for job_id:%d should be between %d and %d.',
16, 1, #job_id, #min_lvl, #max_lvl)
ROLLBACK TRANSACTION
END
I'm not sure if you have a transaction or not, but in your case you would want something like the following:
USE myDatabase
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'myTable' AND type = 'TR')
DROP TRIGGER tr_myTrigger
GO
CREATE TRIGGER tr_myTrigger
ON myTable
FOR INSERT, UPDATE
AS
if(exists(select * from inserted where rtrim(c) <> ''))
begin
-- check to make sure the insert(s) are unique
if(exists(
select * from inserted i
join dbo.myTable t on i.a = t.a and i.b = t.b and i.c = t.c)
begin
raiserror('Duplicate(s) found', 16, 1)
rollback transaction
end
end