Trigger to handle multiple row inserts and updates - sql-server

I am using the following trigger to track inserts and updates on multiple tables and log it in a log table.
CREATE TRIGGER tr_TestTable1]
ON [TestTable_1]
AFTER INSERT, UPDATE
AS
DECLARE #keyid int, #tn nvarchar(50), #recEditMode nvarchar(50), #trstat nvarchar(50)
BEGIN
SET NOCOUNT ON;
SET #tn = 'TestTable_1'
IF EXISTS(SELECT 1 FROM INSERTED)
BEGIN
SET #recEditMode = (Select REC_EDIT_MODE FROM inserted)
SET #trstat = 'PENDING'
SET #keyid = (Select prkeyId FROM inserted)
IF (#recEditMode = 'MANUAL')
BEGIN
IF NOT EXISTS (SELECT * FROM [logTable_1] WHERE SourceKeyId = #keyid AND TrStatus = 'PENDING' AND SourceTableName = #tn)
BEGIN
INSERT INTO [logTable_1](SourceKeyId,SourceTableName,TrStatus)
VALUES (#keyid, #tn, #trstat)
END
END
END
END
This works fine on single row insert and single row update. I am unable to optimize this code to handle multi row inserts and updates. Looking for some help in handling this.
Thanks.

I modified the trigger as below and it seems to be working fine now...
CREATE TRIGGER tr_TestTable1]
ON [TestTable_1]
AFTER INSERT, UPDATE
AS
DECLARE #keyid int, #tn nvarchar(50), #trstat nvarchar(50)
BEGIN
IF ##ROWCOUNT = 0
RETURN
SET NOCOUNT ON;
IF EXISTS(SELECT * FROM INSERTED)
BEGIN
SET #tn = 'TestTable_1'
SET #trstat = 'PENDING'
BEGIN
INSERT INTO LogTable_1 (SourceKeyId, SourceTableName, TrStatus)
SELECT I.prKeyId, #tn, #trStat FROM INSERTED AS I
WHERE (I.REC_EDIT_MODE = 'MANUAL' AND NOT EXISTS(SELECT * FROM LogTable_1 WHERE SourceKeyId = I.prKeyId AND SourceTableName = #tn AND TrStatus = 'PENDING'))
END
END
END

Related

SQL Trigger to send an email

I need to créate a trigger in a SQL table to send an email if the inserted record meets certain conditions.
That is, I create the trigger in Table1 to send an email to X if in the inserted record the field IdCircuito= 53, IdTipoDoc = 45 and Gestor = 'Gest1'. Also, in the body of email message I want the value of a certain field of that inserted record to appear. I have done something like this but trigger always executes regardless of the inserted record:
CREATE TRIGGER dbo.SendEmail
ON dbo.TitulosDoc
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 FROM TitulosDoc WHERE IdCircuito = 53 AND IdTipoDoc = 45 AND Gestor = 'Gest1')
BEGIN
EXEC msdb.dbo.sp_send_dbmail
#recipients = 'rsg#gmail.com',
#subject = 'New requeriment',
#body = 'It's a new requeriment: ';
END
END
GO
In body is where I want show a literal text with the value of the field of inserted record:
#body = 'It's a new requeriment: ' + TitulosDoc.NombreDocumento;
Can somebody help me? Thank you
To access the inserted row you need to select from INSERTED.
Try this:
CREATE TRIGGER dbo.SendEmail
ON dbo.TitulosDoc
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #NombreDocumento VARCHAR(MAX) = (SELECT INSERTED.NombreDocumento
FROM INSERTED
WHERE INSERTED.IdCircuito = 53
AND INSERTED.IdTipoDoc = 45
AND INSERTED.Gestor = 'Gest1')
IF #NombreDocumento IS NOT NULL
BEGIN
EXEC msdb.dbo.sp_send_dbmail
#recipients = 'rsg#gmail.com',
#subject = 'New requeriment',
#body = 'It''s a new requeriment: ' + #NombreDocumento;
END
END
GO
I would do what Sean Lange said... create a physical table called TempTitulosDoc, then insert your records to it that need to be sent through email. Do this in your trigger.
CREATE TRIGGER dbo.SendEmail
ON dbo.TitulosDoc
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
BEGIN
INSERT INTO TempTiulosDoc(field1, field2, EmailStatus)
SELECT field1, field2, 0 AS EmailStatus --Email NOT sent
FROM TitulosDoc
WHERE IdCircuito = 53 AND IdTipoDoc = 45 AND Gestor = 'Gest1'
END
END
GO
Create a stored procedure like this that loops through those records and sends an email. When done, update the TempTitlosDoc with a status of 1 signifying a sent email.
DECLARE #loopCount INT
DECLARE #field1 VARCHAR(10)
DECLARE #field2 VARCHAR(10)
DECLARE #EmailStatus int
--Create Temp Table
CREATE TABLE #Temp
(
id int not null identity,
field1 VARCHAR(10),
field2 VARCHAR(10),
EmailStatus int
)
--Insert Tasks to temp table
INSERT INTO #Temp (field1, field2, EmailStatus)
SELECT field1, field2, EmailStatus
FROM dbo.TempTiulosDoc
WHERE Status = 0
--Set a loopCount for while loop
SET #loopCount = 1
--Use the while loop to check if we have any Tasks left to send
while ( exists(SELECT id FROM #Temp WHERE id = #loopCount) )
BEGIN
--Get current record in temp table
SELECT #field1 = field1,
#field2 = field2,
#EmailStatus = EmailStatus
FROM #Temp
WHERE id = #loopCount
EXEC msdb.dbo.sp_send_dbmail
#recipients = 'rsg#gmail.com',
#subject = 'New requeriment',
#body = 'It''s a new requeriment: ' + #NombreDocumento;
--Update your work table with the status of 1 so it's not picked up again
UPDATE teq
SET teq.#EmailStatus = 1
FROM dbo.TempTiulosDoc teq
WHERE teq.id = #field1
SET #loopCount = #loopCount + 1
END

SQL Trigger after insert update

What is wrong with this code.
If i type like this
Declare tmp as CURSOR FOR
Select i.ID from Inserted
OPEN tmp
Or if i type
Declare tmp as CURSOR FOR
Select i.ID from Deleted
OPEN tmp
Works like a charm but
Is there any way i can write something like this
if #operation <> 3
set #tmp = 'SELECT i.id FROM inserted i '
else
set #tmp =' SELECT i.id FROM deleted i '
DECLARE tmpUpit CURSOR FOR
#tmp
OPEN tmpUpit
Edit :
CREATE TRIGGER [dbo].[ax_Triger] ON [dbo].[extraTable]
FOR INSERT, UPDATE, DELETE AS
SET NOCOUNT ON;
--Capture the Operation (Inserted, Deleted Or Updated)
DECLARE #operation int
DECLARE #Count AS INT
SET #operation = 1
SELECT #Count = COUNT(*)
FROM DELETED
IF #Count > 0
BEGIN
SET #operation = 3
SELECT #Count = COUNT(*)
FROM INSERTED
IF #Count > 0
SET #operation = 2
END
DECLARE tmpUpit CURSOR FOR
SELECT i.id FROM inserted i
OPEN tmpUpit
FETCH NEXT FROM tmpUpit
into #ID WHILE ##fetch_status = 0
begin If Not exists
(SELECT * FROM mytable
where (Operation=3 OR (Operation=1 AND ID=#ID)) AND Status = 0 AND Tablename ='extraTable')
begin INSERT INTO
mytable ([Field1], [Field2],
[ID], [Tablename], [Operation], [Time12], [Status])
VALUES (#Field1, #Field2, #ID, 'extraTable', #operation,GETDATE(),0)
DELETE FROM mytable
WHERE [Field1]=#Field1 And [Field2]=#Field2 And [ID]=#ID And [Tablename]='extraTable'
AND [Operation] = 4 AND [Status] = 0
End
FETCH NEXT FROM tmpUpit into #ID
End CLOSE tmpUpit DEALLOCATE tmpUpit end
i need to insert the value from one table to another depending about the status Inserted/updated/deleted
This is completely untested since the table structures posted did not match the posted trigger code. This should at least demonstrate how you can rethink about this as set based logic instead of row by agonizing row.
CREATE TRIGGER [dbo].[Trigger212] ON [dbo].[Towns]
FOR INSERT, UPDATE, DELETE AS
BEGIN
SET NOCOUNT ON;
DECLARE #operation int
DECLARE #Variable1 nvarchar(8) = 'Woof'
DECLARE #Variable2 nvarchar(4) = 'Foof'
--Capture the Operation (Inserted, Deleted Or Updated)
IF EXISTS(SELECT * FROM inserted) AND NOT EXISTS (SELECT * FROM deleted)
SET #operation = 1 --Insert
ELSE
IF EXISTS(SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted)
SET #operation = 2 --update
ELSE
SET #operation = 3 -- DELETE
INSERT Requests
(
Field1
, Field2
, ID
, TableName
, Operation
, TimeU
, Status
)
SELECT 'Woof'
, 'Foof'
, i.ID
, 'Towns'
, #operation
, GETDATE()
, 0
FROM inserted i
LEFT JOIN Requests r on r.ID = i.ID
AND r.Operation = 3
OR (r.Operation = 1 and r.ID = i.ID)
AND r.Status = 0
AND r.TableName = 'Towns'
WHERE r.ID IS NULL
DELETE r
FROM Requests r
JOIN inserted i on i.Field1 = r.Field1
AND i.Field2 = r.Field2
AND i.ID = r.ID
AND i.Operation <> #operation
WHERE r.TableName = 'Towns'
AND r.Status = 0
END
In general I think triggers should be avoided but they have their place. When triggers are appropriate I am generally not a big fan of doing all three operations. Over time it gets really messy because invariably you need to do different logic for the different operations. Splitting them into three triggers makes this eventuality a lot less painful for maintenance.

Is there a way to dynamically control a table hint without using a dynamic sql query

For example:
CREATE PROCEDURE [dbo].[procGetTable]
(
#SetUPDLOCK BIT,
#RecordId BigInt
)
AS
SELECT *
FROM MYTABLE WITH (CASE WHEN #SetUPDLOCK = 1 THEN 'UPDLOCK' ELSE '' END)
WHERE MYTABLE.RecordId = #RecordId
/* P.S. I Know the above does not work it conceptual only */
CREATE PROCEDURE [dbo].[procGetTable]
#SetUPDLOCK BIT,
#RecordId BigInt
AS
BEGIN
SET NOCOUNT ON;
IF (#SetUPDLOCK = 1 )
BEGIN
SELECT *
FROM MYTABLE WITH (UPDLOCK)
WHERE MYTABLE.RecordId = #RecordId
END
ELSE
BEGIN
SELECT *
FROM MYTABLE
WHERE MYTABLE.RecordId = #RecordId
END
END

Insert update delete trigger

Dear all I´m having trouble with my trigger.
Am I doing this at all right, right now it only works for Insert. I think I´m pretty close tho please help me if you have the time. I´m trying to store all the inserts, updates and deletes into the table customers_changelog via trigger. There is something wrong with the code I cant delete or update customers I can only insert new ones. Please help my I have been spending plenty of hours on this and just cant get this to work! :)
create table customers (
customerid int identity primary key,
name varchar(100) not null,
address varchar(100)
)
go
create table customers_changelog (
customerid int,
name varchar(100) not null,
address varchar(100),
change_user varchar(32),
change_time datetime,
change_action char(1) default 'I',
check (change_action = 'I' or change_action = 'D')
)
go
CREATE TRIGGER log_changes
ON customers
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
--
-- Check if this is an INSERT, UPDATE or DELETE Action.
--
DECLARE #customerid1 as int;
DEClARE #name1 as varchar(32);
DECLARE #address1 as varchar(100);
DECLARE #change_action1 as char(1);
DECLARE #change_time1 as datetime;
DECLARE #change_user1 as varchar(32);
select #customerid1 = c.customerid, #name1 = c.name, #address1 = c.address
from customers c, inserted i
where c.customerid = i.customerid
SET #change_time1 = CURRENT_TIMESTAMP;
SET #change_user1 = CURRENT_USER;
INSERT INTO customers_changelog(customerid,name,address,change_action,change_time,change_user)
VALUES(#customerid1,#name1,#address1,'I',#change_time1,#change_user1)
IF EXISTS(SELECT * FROM DELETED)
BEGIN
IF EXISTS(SELECT * FROM INSERTED)
INSERT INTO customers_changelog VALUES(#customerid1,#name1,#address1,'U',#change_time1,#change_user1)
ELSE
INSERT INTO customers_changelog VALUES(#customerid1,#name1,#address1,'D',#change_time1,#change_user1)
END
ELSE
IF NOT EXISTS(SELECT * FROM INSERTED) RETURN;
END
Assuming MS-SQL from syntax - So couple issues here:
1. Need to specify column lists in the "update" and "delete" inserts because the column order in the table doesn't match your inserts.
2. Can't use "inserted" data for delete insert
ALTER TRIGGER [dbo].[log_changes] ON [dbo].[customers] AFTER INSERT, UPDATE, DELETE AS
BEGIN
SET NOCOUNT ON;
DECLARE #customerid1 as int;
DEClARE #name1 as varchar(32);
DECLARE #address1 as varchar(100);
DECLARE #change_action1 as char(1);
DECLARE #change_time1 as datetime;
DECLARE #change_user1 as varchar(32);
select #customerid1 = c.customerid, #name1 = c.name, #address1 = c.address
from customers c, inserted i
where c.customerid = i.customerid
SET #change_time1 = CURRENT_TIMESTAMP;
SET #change_user1 = CURRENT_USER;
IF EXISTS(SELECT * FROM DELETED)
BEGIN
IF EXISTS(SELECT * FROM INSERTED)
INSERT INTO customers_changelog(customerid,name,address,change_action,change_time,change_user)
VALUES(#customerid1,#name1,#address1,'U',#change_time1,#change_user1)
ELSE
BEGIN
select #customerid1 = d.customerid, #name1 = d.name, #address1 = d.address
from deleted d
INSERT INTO customers_changelog(customerid,name,address,change_action,change_time,change_user)
VALUES(#customerid1,#name1,#address1,'D',#change_time1,#change_user1)
END
END
ELSE
BEGIN
IF NOT EXISTS(SELECT * FROM INSERTED) RETURN;
INSERT INTO customers_changelog(customerid,name,address,change_action,change_time,change_user)
VALUES(#customerid1,#name1,#address1,'I',#change_time1,#change_user1)
END
END

SQL Server 2005 - Trigger not firing

I have two tables User & User Log . User log table basically logs all the changes(insert/update/delete) made to user table.
I have a trigger on User table which is as below:
ALTER TRIGGER [dbo].[TRG_UserLog]
ON [dbo].[Users]
FOR INSERT,UPDATE,DELETE
AS
-- Declare variables here
DECLARE #UserName VARCHAR(50)
DECLARE #FirstName VARCHAR(50)
DECLARE #LastName VARCHAR(50)
DECLARE #Email VARCHAR(50)
DECLARE #RoleID INT
DECLARE #UpdatedBy VARCHAR(50)
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
IF ##ROWCOUNT = 0
BEGIN
RETURN
END
IF EXISTS(SELECT * FROM INSERTED)
--INSERTED / UPDATED
BEGIN
SET #UserName = ( SELECT UserName FROM INSERTED)
SET #FirstName = ( SELECT FirstName FROM INSERTED)
SET #LastName = ( SELECT LastName FROM INSERTED)
SET #Email = ( SELECT Email FROM INSERTED)
SET #RoleID = ( SELECT RoleID FROM INSERTED)
SET #UpdatedBy = ( SELECT ModifiedBy FROM INSERTED)
INSERT INTO UserLog(UserName,FirstName,LastName,Email,RoleID,[DateTime],UpdatedBy)
VALUES (#UserName,#FirstName,#LastName,#Email,#RoleID,GETDATE(),#UpdatedBy)
END
ELSE
-- DELETED
BEGIN
SET #UserName = ( SELECT UserName FROM DELETED)
SET #FirstName = ( SELECT FirstName FROM DELETED)
SET #LastName = ( SELECT LastName FROM DELETED)
SET #Email = ( SELECT Email FROM DELETED)
SET #RoleID = ( SELECT RoleID FROM DELETED)
SET #UpdatedBy = ( SELECT ModifiedBy FROM DELETED)
INSERT INTO UserLog(UserName,FirstName,LastName,Email,RoleID,[DateTime],UpdatedBy)
VALUES (#UserName,#FirstName,#LastName,#Email,#RoleID,GETDATE(),#UpdatedBy)
END
END
The above triigger is not inserting data in userlog table when insert/update or delete is made to user table. Is there anything wrong with the code?
You should not be setting variables from INSERTED and DELETED. These "tables" could potentially hold more than 1 row in them and using set is going to lose data. Do this instead (on both the INSERT and DELETE blocks):
INSERT INTO UserLog(UserName, FirstName, LastName, Email, RoleID, [DateTime], UpdatedBy)
SELECT UserName, FirstName, LastName, Email, RoleID, getdate(), ModifiedBy
FROM INSERTED
If you make that change, you don't even need the IF/ELSE blocks, since it'll only INSERT/DELETE based on whether or not there are rows in those tables.
Also, the check on ##ROWCOUNT is unnecessary. You should be able to remove that completely.
This will always be true:
SET NOCOUNT ON; -- this sets ##ROWCOUNT to 0
IF ##ROWCOUNT = 0
BEGIN
RETURN
END
because calling SET NOCOUNT ON; affects ##ROWCOUNT, and since no rows are affected by that statement, it's set to 0. So the rest of the code never runs. At the very least, you would run that code before SET NOCOUNT ON; (or store ##ROWCOUNT in a variable for later use), but as indicated by the other answer, it's completely unnecessary anyway.

Resources