SQL trigger trouble with TRY--CATCH block for INSERT command - sql

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

Related

Incorrect Syntax near in After trigger

I have written a Create trigger and it shows me some error on Incorrect Syntax near
My Code
CREATE TRIGGER [dbo].[News_data] ON [dbo].[News]
AFTER INSERT
AS
declare #NID uniqueidentifier;
select #NID = NEWID();
BEGIN
SET NOCOUNT ON;
INSERT INTO [ABC].[News](
#NID,
[Name], //<-- Incorrect Syntax near [Name]
[GeoLoc])
SELECT
[Name],
[GeoLoc]
FROM inserted
END
Your variable declaration is wrong with column list, if news table has primary column for which you are referring to #NID then use that column name instead :
INSERT INTO [dbo].[News] (PK, [Name], [GeoLoc])
SELECT #NID, [Name], [GeoLoc]
FROM inserted
Was about to post a sample code, but Yogesh already provided the answer
CREATE TRIGGER [dbo].[Customers_data] ON Customers
INSTEAD OF INSERT AS
DECLARE #NID INT = NEWID()
BEGIN
SET NOCOUNT ON;
INSERT INTO Customers(
CustomerID, -- <-- ColumnName instead of its value
CustomerName, -- <-- Incorrect Syntax near [Name]
ContactName)
SELECT
#NID
CustomerName,
ContactName
FROM inserted
END
Something else you might take a look at is the ID. If in one transaction multiple records are inserted, then the inserted will have multiple records with the same 'NewId'. Not sure if that is what you want in your case, but as I suspect that the #NID is the Primary Key, you might want to take a look at that.
Edit: To make sure the trigger still works with a batch-insert. Meaning that multiple records are inserted in one transaction, thus SELECT * FROM inserted will contain multiple records, while you do not wish them to use the same #NID value.
CREATE TRIGGER [dbo].[Customers_data] ON Customers
INSTEAD OF INSERT AS
BEGIN
SET NOCOUNT ON;
INSERT INTO Customers(
CustomerID, -- <-- ColumnName instead of its value
CustomerName,
ContactName)
SELECT
NEWID(), -- <-- If the 'FROM inserted' contain multiple columns
CustomerName,
ContactName
FROM inserted
END

AUDIT TRAIL OF TABLES

I want to do audit trail on specific table like
what inserted,updated,deleted in table and all this logs are save in one table
I am using sql server 2012 .
Can any one please help me with how to achieve this?
Please note - Use of cursor is restricted
create an after trigger on that table and insert the records into the log table .
create trigger <trigger_name> after insert/update/delete/ on
table <orig table>
begin
insert into the log tables ('all the fields that you require');
end
Try using CDC (Change Data Capture). A very helpful tool which will help to manage the Audit Trail
Read the article from MSDN
This can be achieve using Triggers. Trigger will make your DML operations slower, if large Insert, Delete and Update operations are happening on your table. If it's small table you can create TRIGGER like below, to log the rows to another table based on the action occurred.
You can make use of Inserted and Deleted magic tables which hold the rows which are being Inserted and Deleted inside a trigger.
There is another alternate if you need more control over auditing using CDC (Change Data Capture).
CREATE TABLE TrailTable
(
Id INT,
Name VARCHAR(100)
);
CREATE TABLE TrailTableLog
(
Id INT,
Name VARCHAR(100),
Action CHAR(3)
);
Insert Into TrailTable VALUES (1,'Vi');
Insert Into TrailTable VALUES (2,'Vr');
Insert Into TrailTable VALUES (3,'An');
Insert Into TrailTable VALUES (4,'Ma');
CREATE TRIGGER dbo.TRG_IDU_TrailTable
ON dbo.TrailTable
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Action as char(1);
SET #Action = (CASE WHEN EXISTS(SELECT * FROM INSERTED)
AND EXISTS(SELECT * FROM DELETED)
THEN 'U' -- Set Action to Updated.
WHEN EXISTS(SELECT * FROM INSERTED)
THEN 'I' -- Set Action to Insert.
WHEN EXISTS(SELECT * FROM DELETED)
THEN 'D' -- Set Action to Deleted.
ELSE NULL -- Skip. It may have been a "failed delete".
END)
IF(#Action = 'I')
BEGIN
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'I' FROM INSERTED;
END
IF(#Action = 'D')
BEGIN
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'D' FROM DELETED;
END
IF(#Action = 'U')
BEGIN
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'U-D' FROM INSERTED; -- Records Deleted to Update
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'U-I' FROM INSERTED; --Records Inserted to Update
END
END

Stored procedures - writing errors out to another table

I have two tables and a simple stored procedure. I want to use the stored procedure to insert into table1 with one of the parms being the primary key.
Here is where I am having trouble:
I want to have it so that if the parm/primary key the user has entered into the stored procedure and run is already on the table then it writes out the fields to a second table as well as the error description.
How do you capture / output this error information?
Stored procedure (sans the error logging):
CREATE PROCEDURE procedure1
#Primary INT,
#Info NVARCHAR
AS
BEGIN
SET NOCOUNT ON
INSERT INTO Table1
(
Primary ,
Info
)
VALUES
(
#Primary ,
#Info
)
END
Thanks
CREATE PROCEDURE dbo.InsertSomething
(
#PrimaryKey INT
, #Info NVARCHAR(MAX)
)
AS
BEGIN
IF EXISTS (SELECT 1 FROM dbo.Table1 WHERE ID = #PrimaryKey)
BEGIN
INSERT INTO dbo.Table2 (ID, Info)
VALUES (#PrimaryKey, #Info);
RAISERROR (15600,-1,-1, 'Your custom message');
END
ELSE
BEGIN
INSERT INTO dbo.Table1 (ID, Info)
VALUES (#PrimaryKey, #Info);
END
END
All you have to do is a simple check if record already exists in first table, and if it does just insert into it and throw error.

T-SQL - anti-duplicate trigger

I need to write a trigger which prevents from inserting more than one record at the same time and also checks if the place is already in the database. Code compiles but it doesn't work as it should - it displays error message even if I try to add a non-existing address
Here's my code:
CREATE TRIGGER address_duplicate ON place
AFTER INSERT
AS
BEGIN
DECLARE #counter INT
SELECT #counter=COUNT(*) FROM place WHERE street IN (SELECT street FROM inserted) AND number IN(SELECT number FROM inserted)
AND city IN(SELECT city FROM inserted) AND postcode IN(SELECT postcode FROM inserted)
IF #counter>0
BEGIN
RAISERROR('This record is already in the database',1,1)
ROLLBACK
END
IF ##ROWCOUNT>1
BEGIN
RAISERROR('You can add only one record at the same time',1,2)
ROLLBACK
END
END
GO
Your logic of identifying duplicate place is not correct.
Try something like this:
select #counter= count(*) from place p join inserted n
where p.address=n.address and p.city=n.city and p.postcode=n.postcode
and p.number=n.number;
Also want you to know, using triggers to avoid duplicate can be very expensive.
Personally, I'd use a unique constraint and probably use TRY-CATCH when inserting, but if you really want to do it in a trigger, try this out:
CREATE TRIGGER address_duplicate ON place
INSTEAD OF INSERT
AS
BEGIN
DECLARE #newValue TABLE (ID INT);
IF ##ROWCOUNT > 1
BEGIN
RAISERROR('Insert cancelled. You can add only one record at the same time.',1,2);
END
ELSE
BEGIN
INSERT INTO place
OUTPUT inserted.ID INTO #newValue
SELECT *
FROM inserted
EXCEPT
SELECT *
FROM place
IF (SELECT 1 FROM #newValue) IS NULL
RAISERROR('Insert cancelled. This record is already in the database',1,1)
END
END

Insert data into table when i am using trigger?

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