How to fire a trigger after the SPROC has completed execution? - sql

I have written a trigger that sends email once a row INSERT is performed.
ALTER TRIGGER TR_SendMailOnDataRequest
ON DataRequest
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#DR_Id INT,
#DR_FullName VARCHAR(200),
#DR_Email VARCHAR(200),
#DR_Phone VARCHAR(20),
#UT_Name VARCHAR(50),
#DR_UserTypeOther VARCHAR(50) = NULL,
#D_Name VARCHAR(200),
#DR_RequestDate DATETIME,
#UF_LinkedFiles VARCHAR(MAX),
#DRN_Names VARCHAR(200),
#DR_Description VARCHAR(1200),
#DR_CreatedOn DATETIME,
#analystMailList VARCHAR(MAX),
#tableHtml NVARCHAR(MAX),
#downloadLink VARCHAR(MAX) = N'NONE'
SELECT #DR_Id = MAX(DR_Id) FROM dbo.DataRequest
SELECT
#DR_FullName = DR_FullName,
#DR_Email = DR_Email,
#DR_Phone = DR_Phone,
#UT_Name = UT_Name,
#DR_UserTypeOther = DR_UserTypeOther,
#D_Name = D_Name,
#DR_RequestDate = DR_RequestDate,
#UF_LinkedFiles = UF_LinkedFiles,
#DRN_Names = DRN_Names,
#DR_Description = DR_Description,
#DR_CreatedOn = DR_CreatedOn
FROM
dbo.FN_GetDataRequest(#DR_Id)
SELECT #analystMailList = dbo.FN_GetAnalystsMailList()
IF (LEN(#UF_LinkedFiles) > 0)
BEGIN
SET #downloadLink = N'Downloads'
END
SET #tableHTML =
N'<H1>Data Request</H1>' +
N'<UL>' +
N'<LI>Full Name: ' + #UF_LinkedFiles + N'</LI>' +
N'<LI>Email: ' + #DR_Email + N'</LI>' +
N'<LI>Phone: ' + CAST(#DR_Phone AS VARCHAR(20)) + N'</LI>' +
N'<LI>User Type: ' + #UT_Name + N'</LI>' +
N'<LI>User Type Other: ' + COALESCE(#DR_UserTypeOther, N'NONE') + N'</LI>' +
N'<LI>Reuest Date: ' + CONVERT(VARCHAR(20), #DR_RequestDate, 107) + N'</LI>' +
N'<LI>Downloads: ' + #downloadLink + N'</LI>' +
N'</UL>';
BEGIN
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'Example',
#recipients = 'John Doe<jdoe#example>',
--#recipients = #analystMailList,
#reply_to = #DR_Email,
#subject = 'Email Test',
#body_format = 'HTML',
#body = #tableHtml
END
END
GO
The above trigger is fired when there is a ROW INSERT operation on table DataRequest. After the row insert operation, I take the IDENTITY element generated after the INSERT operation and use that as the foreign key, and INSERT other values in a different table. Finally, I use the values from both the tables and create an email to be sent.
I wasn't getting the values from the other tables (e.g. #UF_LinkedFiles), so I realized that the TRIGGER is being fired just after the INSERT in FIRST table but before the INSERT in the SECOND table, thus no values available when SENDING EMAIL.
So how do I make sure that TRIGGER is fired only after the SPROC that does all the INSERT activities in multiple tables has completed the transaction.
Here is the table diagram -

Instead of using a trigger, I have included the EMAIL SENDING code in the SPROC where rows are being inserted.

Not sure if that is your case because you don't explain how is the behavior between the tables. But i had an scenario where i try to execute a SELECT during a series of insert and i couldn't find the row because the transaction wasn't finish yet.
What i did was create an additional table
tblProgress
id integer,
fieldA integer,
fieldB integer,
fieldC integer
So if you have 3 tables TableA, TableB and TableC each table will have one INSERT trigger and will do some job then access tblProgress.
TableA create a row
TableB and TableC update.
Then tblProgress will also have an AFTER UPDATE trigger, where you validate all 3 field have NOT NULL value
When you have all 3 values you can send the email.

Related

Stored procedure not inserting data into table

I'm trying to implement a stored procedure like this where it's going to compare UserID from another table. If that user id already exists in dbo.user info table, it will insert data into [dbo].[BulkUploadTagDetail] table.
Every time I execute this stored procedure running into an issue that the data doesn't get inserted into my database table.
CREATE PROCEDURE [dbo].[InsertBulkUploadTagDetail]
(#BulkTagID INT,
#PortalUserID UNIQUEIDENTIFIER,
#EmailAddress VARCHAR(256),
#BulkUploadFileName VARCHAR(256),
#CreatedOn DATETIME2(7),
#IsCompleted BIT = 0)
AS
BEGIN
SET NOCOUNT ON
IF EXISTS (SELECT UserID
FROM dbo.UserInfo
WHERE UserID = #PortalUserID)
BEGIN
UPDATE [unp]
SET [unp].[BulkTagID] = #BulkTagID,
[unp].[UserID] = #PortalUserID,
[unp].[EmailAddress] = #EmailAddress,
[unp].[BulkUploadFileName] = #BulkUploadFileName,
[unp].[CreatedOn] = GETUTCDATE(),
[unp].[IsCompleted] = #IsCompleted
FROM
[dbo].[InsertBulkUploadTagDetail] unp
INNER JOIN
[dbo].[UserInfo] uinfo ON [unp].[UserID] = [uinfo].[UserID]
WHERE
[unp].[UserID] = #PortalUserID
END
ELSE
BEGIN
INSERT INTO [dbo].[BulkUploadTagDetail]
([BulkTagID], [PortalUserID], [EmailAddress],
[BulkUploadFileName], [CreatedOn], [IsCompleted])
VALUES (#BulkTagID, #PortalUserID, #EmailAddress,
#BulkUploadFileName, #CreatedOn, #IsCompleted)
END
END

After Trigger with JOIN Tables

TASK: Create an AFTER TRIGGER to accomplish a condition from a JOIN. The Trigger would be in table_1 when some record is created. Meanwhile, table_2 has a common column with some parameters that the condition needs to have.
Every time that the Result <> 1 AND Status <> 3 in table_2 and ALERT should be sent
-- QUERY WITH JOIN TABLE_1 ON TABLE_2
-- MOCK TABLE
-- Table_1 as A | Table_2 as B
A.LotCode | A.LineNumber | B.Result | B.Status
00000 | xxxx | 1 | 3
00001 | xxxx | 2 | 4
-- The LotCode 00001 should send it through email because satisfy the condition
CREATE TRIGGER FullfillOrderQCResult
ON Table_1
AFTER INSERT
AS
BEGIN
-----DECLARE VARIABLES-----
DECLARE #LOTNUMBER VARCHAR(50)
DECLARE #ACTIONPEFORMED VARCHAR(MAX)
DECLARE #ITEM INT
DECLARE #RESULT TINYINT
DECLARE #STATUS TINYINT
SELECT #LOTNUMBER = A.LotCode, #ITEM = A.LineNumber, #RESULT = B.Result, #STATUS = B.Status
FROM inserted AS A
JOIN Table_2 AS B
ON A.LotCode = B.DocumentID2
-----CONDITION WHEN I INSERT A VALUE-----
IF (#RESULT <> 1 AND #STATUS <> 3)
BEGIN
SET #ACTIONPEFORMED =
N'Hello, ' + '<br>' + '<br>'
+ N' The following LOT NUMBER: ' + #LOTNUMBER + ' has not been approved for this Item: '
EXEC MSDB.DBO.SP_SEND_DBMAIL
#PROFILE_NAME = 'SQLMail',
#RECIPIENTS = 'TEST#gmail.com',
#SUBJECT = 'LOT NON-Approved',
#BODY = #ACTIONPEFORMED,
#IMPORTANCE = 'HIGH',
#BODY_FORMAT = 'HTML'
END
ELSE
PRINT 'ALL GOOD MY FRIEND'
END
TESTING THE TRIGGER
--------INSERT VALUES------------------
INSERT INTO Table_1 (LotCode,LineNumber)
values ('00000','xxxx')
-----EXISTING VALUES-----
INSERT INTO Table_2 (CreationUser,DocumentID1,DocumentID2,DocumentID3,Result,Status)
values ('JL','00000','00000','00000',2,3)
The following shows you how to handle the fact that Inserted might have multiple rows. This is really not ideal behaviour for a trigger, because you have to process the results RBAR (row by agonising row), which is slow by itself, let alone the fact that you are sending an email.
CREATE TRIGGER FullfillOrderQCResult
ON Table_1
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
-----DECLARE VARIABLES-----
DECLARE #ACTIONPEFORMED varchar(max), #Id int;
SELECT A.LotCode, A.LineNumber, CONVERT(bit, 0) Done, IDENTITY(int) id -- Use your own id if you have one, just need to uniquely identify each row.
INTO #FullfillOrderQCResult_temp
FROM Inserted AS A
INNER JOIN Table_2 AS B ON A.LotCode = B.DocumentID2
WHERE B.Result <> 1 and B.[Status] <> 3;
WHILE EXISTS (SELECT 1 FROM #FullfillOrderQCResult_temp WHERE Done = 0) BEGIN
SELECT TOP 1 #Id = id, #ACTIONPEFORMED =
N'Hello, ' + '<br>' + '<br>'
+ N'The following LOT NUMBER: ' + LotCode + ' has not been approved for this Item: ' + LineNumber
FROM #FullfillOrderQCResult_temp
WHERE Done = 0;
EXEC MSDB.DBO.SP_SEND_DBMAIL
#PROFILE_NAME = 'SQLMail',
#RECIPIENTS = 'TEST#gmail.com',
#SUBJECT = 'LOT NON-Approved',
#BODY = #ACTIONPEFORMED,
#IMPORTANCE = 'HIGH',
#BODY_FORMAT = 'HTML';
UPDATE #FullfillOrderQCResult_temp SET Done = 1 WHERE id = #Id;
END;
END;
I don't know whether you would still want the concept of 'ALL GOOD MY FRIEND' because you could have none, some or all rows with issues. Anyway I assume print is only for debugging.
That said you would be much better off pushing an event into a queue and having a service process said event because triggers really should be as fast as possible. And adding an event to a queue could be handled in a set based manner e.g.
CREATE TRIGGER FullfillOrderQCResult
ON Table_1
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO MyEventQueue (A.LotCode, A.LineNumber) -- Any other information required to identify the records etc
SELECT A.LotCode, A.LineNumber
FROM Inserted AS A
INNER JOIN Table_2 AS B ON A.LotCode = B.DocumentID2
WHERE B.Result <> 1 and B.[Status] <> 3;
END;

Obtain a repeated data and send SQL mail

Good day I have a table called Ticket which has several tickets registered, each one has a status:
1 = Accepted,2 = Assigned,3 = At ​​attention,4 = Attended,5 = Agree.
I want to perform a stored procedure in which I only send mail to the tickets that are in state 4, that is, my ticket has status 4, it is activated exec sp_sendmail .
Then I will use it as a Job every 30 minutes, check to see if it is still in that state and if it is in state 4 it sends again mail, once it changes state 4 to 5 it will not send anything and it will be closed.
Something like this, that loops through the list of tickets and sends an email. Couple notes, though: first, if you try to send too many at once, your email provider may start dropping them, so maybe put in a {pre}WAITFOR DELAY '00:00:02'{pre} delay between messages. Also, instead of sending one email per ticket, you can look into the query options in sp_send_dbmail: you can email a single list of all currently-4 tickets. It just depends on your needs.
CREATE PROCEDURE dbo.SendTicketAttendedEmails
AS
BEGIN
DECLARE #MailList TABLE(TicketID INT, SendTo VARCHAR(255))
DECLARE #ThisTicketID INT
, #MailMessage NVARCHAR(2000)
, #MailSubject NVARCHAR(255)
, #SendTo VARCHAR(255)
INSERT INTO #MailList
([TicketID], [SendTo])
SELECT t.[ID], u.[UserEmail]
FROM dbo.YourTicketTable t
JOIN dbo.YourUserTable u
ON t.UserCreated = u.ID
WHERE [StatusID] = 4
WHILE EXISTS(SELECT 1 FROM #MailList)
BEGIN
SELECT TOP(1) #ThisTicketID = [TicketID]
, #MailSubject = 'Ticket ' + CAST([TicketID] AS VARCHAR(10)) + ' is in status 4.'
, #MailMessage = 'Please review, or whatever, ticket ' + CAST([TicketID] AS VARCHAR(10)) + '.'
, #SendTo = COALESCE([SendTo], 'yourEmailAddress#InCase.Missing')
FROM #MailList
ORDER BY [TicketID];
DECLARE #mailitem_id INT ;
EXEC [msdb].dbo.[sp_send_dbmail]
#profile_name = 'SomeDBMailProfileName' -- sysname
, #recipients = #SendTo -- varchar(max)
, #subject = #MailSubject -- nvarchar(255)
, #body = #MailMessage -- nvarchar(max)
, #mailitem_id = #mailitem_id OUTPUT -- int
, #from_address = 'you#you.com' -- varchar(max)
, #reply_to = 'you#you.com' -- varchar(max)
DELETE #MailList
WHERE [TicketID] = #ThisTicketID
END
END
Basically you will use something like this for your job.
exec sp_send_dbmail
#profile_name = 'your_mail_profile'
,#recipients = 'you#email.com'
,#subject = 'Attended'
,#query = 'select * from yourTable where [status] = 4'
--,#attach_query_result_as_file = 1
--,#query_attachment_filename = 'somefile.csv'
See other options in the docs... and adjust accordingly.

SQL update trigger unable to handle multiple entries

I have a SQL MERGE script that updates a table where an update trigger exists.
When the MERGE scripts comes with only one update to the table the trigger works fine. When the MERGE command comes with multiple updates to the table the trigger returns an error.
Here is the trigger:
ALTER TRIGGER [dbo].[userupd]
ON [dbo].[users]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #navn varchar(255), #fornavn varchar(255), #efternavn varchar(255),#initialer varchar(255), #areagroups varchar(255)
SET #fornavn = (SELECT Fornavn FROM DELETED)
SET #efternavn = (SELECT Efternavn FROM DELETED)
SET #initialer = (SELECT Initialer FROM DELETED)
IF #initialer IS NULL SET #initialer = 'Extern'
SET #navn = #fornavn + ' ' + #efternavn + ' (' + #initialer + ')'
SET #areagroups = (SELECT AddedAreaGroups FROM NOX.dbo.simscodesusers WHERE Username = #navn)
SELECT #areagroups OriginalString, RTRIM(LTRIM(#areagroups)) TrimmedValue
SET #areagroups = ' ' + #areagroups
INSERT INTO NOX.dbo.SIMScodesAutoUpdate
( Action ,
Username
)
SELECT 'DELETE' ,
D.Fornavn + ' ' + D.Efternavn + ' (' + D.Initialer + ')'
FROM DELETED D;
INSERT INTO NOX.dbo.SIMScodesAutoUpdate
( Action ,
Username ,
NoxAutoCode ,
NoxAutoCodePIN ,
UserGroup ,
Startdate ,
EndDate ,
AddedAreaGroups
)
SELECT 'ADD' ,
I.Fornavn + ' ' + I.Efternavn + ' (' + I.Initialer + ')' ,
I.Kortnummer ,
I.PINkode ,
I.Brugerniveau ,
I.Startdato ,
I.Slutdato,
#areagroups
FROM INSERTED I
END
This is the error returned from the SQL job that contains the MERGE script:
Executed as user: CPCORP\SQDKRTV96service. Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression. [SQLSTATE 21000] (Error 512) The statement has been terminated. [SQLSTATE 01000] (Error 3621). The step failed.
Can the trigger be edited to handle multiple values?
Thanks in advance.
In the second case ("When the MERGE command comes with multiple updates...") the DELETED table contains MANY rows. So you can't assign MULTI rows table to the ONE SCALAR variable. Here is the source of the error 'Subquery returned more than 1 value....':
SET #fornavn = (SELECT Fornavn FROM DELETED)
Microsoft: Create DML Triggers to Handle Multiple Rows of Data

Trigger to update sum of columns?

I know this is not a way to do it but it's a interview question
to update total = marks1 + marks2 + marks3 using a trigger.
I wrote something like this but it's not updating after an insert statement.
CREATE table marks
(
marks1 int,
marks2 int,
marks3 int,
total int
)
SELECT * from marks m
insert into marks values(10,10,20,0)
drop TRIGGER total_marks
create TRIGGER total_marks ON marks
AFTER INSERT
AS
begin
SET NOCOUNT ON
DECLARE #marks1 as int
select #marks1 = inserted.marks1 FROM inserted
DECLARE #marks2 as int
select #marks1 = inserted.marks2 FROM inserted
DECLARE #marks3 as int
select #marks1 = inserted.marks3 FROM inserted
DECLARE #result as int
set #result = #marks1 + #marks2 + #marks3
update marks
set total = #result
SET NOCOUNT OFF
end
Your trigger doesn't handle multiple row inserts, updates all rows to the same value (rather than just the row(s) inserted), and is far more complex than necessary anyway. Where is your key?
CREATE TRIGGER dbo.total_marks
ON dbo.marks
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE m
SET total = i.marks1 + i.marks2 + i.marks3
FROM dbo.marks AS m
INNER JOIN inserted AS i
ON m.key = i.key;
END
GO
If your table really doesn't have a key (it doesn't make a whole lot of sense to me), then you can say this, but it may update rows that were already updated:
ON m.marks1 = i.marks1 AND m.marks2 = i.marks2 AND m.marks3 = i.marks3
WHERE m.total = 0;