I created a update trigger in Microsoft SQL Server that would email me if a date changed in a row.
Similar to this:
IF UPDATE(ColumnName)
BEGIN
DECLARE #columnVal AS DATETIME
SELECT
#columnVal = i.columnName
FROM
inserted i JOIN deleted d on i.RowId= d.RowId;
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'me',
#recipients = 'me#company.com',
#body = 'blah blah datechange',
#body_format = 'HTML',
#subject = 'subject';
END
it worked fine for a time.
Then I switched over to batch updates and only the first row email is sent out if the date changes on multiple rows. I tried to set up a cursor to roll through the changes but I cannot get it to work, Similar to below:
DECLARE #columnVal AS DATETIME
DECLARE cur CURSOR LOCAL READ_ONLY FAST_FORWARD FOR
SELECT
i.ColumnName
FROM
inserted i JOIN deleted d on i.RowId= d.RowId;
OPEN cur
FETCH NEXT FROM cur INTO #columnVal
WHILE ##FETCH_STATUS = 0
BEGIN
IF UPDATE(ColumnName)
BEGIN
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'me',
#recipients = 'me#company.com',
#body = 'blah blah datechange',
#body_format = 'HTML',
#subject = 'subject';
END
FETCH NEXT FROM cur INTO #columnVal
END
CLOSE cur
DEALLOCATE cur
Any ideas on how to accomplish this task? Would the Update(ColumnName) function even work properly nested inside a cursor (would it actually tell me if that column was updated for that row?)
A cursor inside a trigger is a very very bad idea.
A trigger should be very lean - it shouldn't do a lot of work! I would recommend to only "take a note" of who you have to send an e-mail to - but leave the actual sending of the e-mail to a separate e.g. SQL Server Agent Job which is not part of the trigger.
Triggers are fired often and often unexpectedly - don't put a log of processing burden into them! And especially not a performance killer like a cursor!
To find those rows that you're interested in, you can use a WHERE clause something like:
WHERE inserted.ColumnName <> deleted.ColumnName
In the context of an UPDATE trigger, this means the new value of ColumnName is different from the old value --> this column has been updated.
Related
Can someone please give an example of when we need to use a cursor in SQL that cannot be solved with a set based approaches.
Thanks
A cursor is often used when you need to do an action on a per row basis. Which is something we'd often relegate to other tools outside of the DBMS. In general the strength of the DBMS lies in set based approaches for data. However.. to give an example.
Say you have a table where some other process writes mail messages to be stored and sent at a later date. Perhaps multiple SQL jobs running and each writes their own status, then when next morning comes or the server load is very low, the DBMS is expected to send these on its own.
Setting up an example table with some data:
CREATE TABLE outgoingMessages
(
recipient VARCHAR(MAX),
subject NVARCHAR(255),
message NVARCHAR(MAX)
)
INSERT INTO dbo.outgoingMessages (recipient,subject,message)
VALUES
('foo#bar.com', N'An email', N'Procedure dbo.Foo ran with statuscode X'),
('foo#bar.com', N'An email', N'Procedure dbo.Bar ran with statuscode Y'),
('manager#bar.com', N'An email', N'Data synchronisation had problems, ask foo')
Then as a theoretical end step/end job, we have a process that goes over the table and handles all built up messages.
/* Scheduled job */
DECLARE mailCursor CURSOR FOR
SELECT * FROM dbo.outgoingMessages;
DECLARE #mailRecipient VARCHAR(MAX);
DECLARE #mailSubject NVARCHAR(255);
DECLARE #mailMessage NVARCHAR(MAX);
OPEN mailCursor;
FETCH NEXT FROM mailCursor INTO
#mailRecipient, #mailSubject, #mailMessage
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #mailRecipient + ' ' + #mailSubject + ' ' + #mailMessage
EXEC msdb.dbo.sp_send_dbmail
#profile_name = N'defaultMailprofile',
#recipients = #mailRecipient,
#subject = #mailSubject,
#body = #mailMessage
FETCH NEXT FROM mailCursor INTO
#mailRecipient, #mailSubject, #mailMessage
END
CLOSE mailCursor;
DEALLOCATE mailCursor;
This makes the database print out all lines individually and send a mail to the specified variables (calls another stored procedure for it) for each line in the table. I would say this line of operation, taking data for each row and manipulating it further or using it as variables for another procedure is a more common usecase.
/* Print results */
foo#bar.com An email Procedure dbo.Foo ran with statuscode X
foo#bar.com An email Procedure dbo.Bar ran with statuscode Y
manager#bar.com An email Data synchronisation had problems, ask foo
You can think of a table with perhaps built up API calls by other automated processes, then to be executed at a later date.
Are cursors common? No. You should always consider their usecases and ideally use a different approach. But if you need to do something for each line, and potentially jump backwards based on conditions. Cursors allow you to do so inside the DBMS and they're a powerful tool.
I have created a batch script which prints information on the table into a label and saves it into pdf. At the moment, I am giving the script a number, which is the ItemCode and it prints out the rest of the information in the table.
Well now I'm going much further, my goal is to run the script each time the table is modified, or a new row is added or even if a single field is modified. When this happens it would check which row has been modified and It would run the script with the ItemCode which has been modified.
Been looking for something similar to this but couldn't find anything precise enough, so any help would be nice!
The code below is part of a trigger I am using. It writes old values and new values into a special table to track all important changes. The updated_idx is send with the SQL command to tell, what user is doing it.:
USE [Demo] -- database
GO
/****** Object: Trigger [dbo].[update_Address] Script Date: 26.07.2018 11:16:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[update_Address] ON [dbo].[Address] for UPDATE AS
DECLARE #fieldname varchar(128) = '- empty -'
DECLARE #newValue varchar(2048) = '- empty -'
DECLARE #oldValue varchar(2048)= '- empty -'
DECLARE #Updated int = datediff(s,'01/01/1970',SYSUTCDATETIME())
DECLARE #Updated_IDX int = -1
DECLARE #ID int = -1
DECLARE db_cursor CURSOR FOR SELECT Address_id, Updated_IDX FROM inserted
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #ID, #Updated_IDX -- this takes the current id
WHILE ##FETCH_STATUS = 0
BEGIN
SET NOCOUNT ON
If UPDATE([ZipCode])
BEGIN
SELECT #OldValue=b.ZipCode, #NewValue=a.ZipCode FROM inserted a, deleted b
IF #NewValue <> #OldValue
BEGIN
INSERT INTO TransactionLog ([ID],[TableName],[Type],[FieldName],[newValue],[oldValue],[Updated],[Updated_IDX]) values (#ID,'Address','U','ZipCode',#newValue,#oldValue,#Updated,#Updated_IDX);
END
END
If UPDATE([City])
BEGIN
SELECT #OldValue=b.City, #NewValue=a.City FROM inserted a, deleted b
IF #NewValue <> #OldValue
BEGIN
INSERT INTO TransactionLog ([ID],[TableName],[Type],[FieldName],[newValue],[oldValue],[Updated],[Updated_IDX]) values (#ID,'Address','U','City',#newValue,#oldValue,#Updated,#Updated_IDX);
END
END
FETCH NEXT FROM db_cursor INTO #ID, #Updated_IDX
End
CLOSE db_cursor
DEALLOCATE db_cursor
I have an IF statement part of a stored procedure as follows :
BEGIN TRY
IF EXISTS (SELECT which I have confirmed returns no results, has a variable called from another table in the where clause)
BEGIN
EXEC msdb.dbo.sp_send_dbmail
#profile_name = #dynamic_profile,
#recipients = #dynamic_recipient,
#subject = #subjectline,
#body = #mailHTML,
#body_format = 'HTML';
END
END TRY
This is tied to a job that runs every 15mins.
Why is it keep sending me blank emails when the IF statement asks to skip the mail sending if the select returns no results?
My NOCOUNT is set to ON
Try - instead:
IF (SELECT COUNT(*) FROM whatever) > 0
BEGIN
--MAIL
END
I used to have triggers in my database that used cursors / sp_send_dbmail to email when certain columns were updated. I was told this was not best practice so I created a new table called EmailNotify that contains columns like recepient, subject, body etc. So instead the triggers now insert into this table the email I want to send.
I want to create a Job that runs every few minutes that checks this table and emails. The item below is what I came up with but is it okay to use cursors in this case? Should the table include a sent field so I know which rows I sent? Can I change that inside the cursor? Or would it be recommended to truncate the table afterwards?
DECLARE #emailSubject AS NVARCHAR(100);
DECLARE #emailRecipients AS NVARCHAR(100);
DECLARE #emailBody AS NVARCHAR(max);
DECLARE cur CURSOR LOCAL READ_ONLY FAST_FORWARD FOR
SELECT
recipients,
subject,
body
FROM
EmailNotify;
OPEN cur
FETCH NEXT FROM cur INTO
#emailRecipients,
#emailSubject,
#emailBody
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'name',
#recipients = #emailRecipients,
#body = #emailBody,
#body_format = 'HTML',
#subject = #emailSubject;
FETCH NEXT FROM cur INTO
#emailRecipients,
#emailSubject,
#emailBody
END
CLOSE cur
DEALLOCATE cur
I would
Add an EmailSent(DATETIME) = NULL column to your table
Create an EmailSent variable at the top of your proc and set it to GETDATE()
UPDATE <YOURTABLE> SET EmailSent = #EmailSent WHERE EmailSent IS NULL
Add a WHERE EmailSent = #EmailSent to your query for the email
You can accomplish this with a SSRS report with a subscription or data-bound subscription instead of this job, but it's really just a matter of preference. Formatting is easier in SSRS, otherwise you're messing with dynamic HTML in your message body. Been there, not pleasant.
We have a stored procedure that is supposed to check db and select all records where sentFlag is No.
Once record(s) found, the stored proc invokes sp_send_dbmail with passed parameters and then sends an email to affected individuals.
This appears to work.
The issue we are having so far though is that each indivual is receiving duplicate emails.
Any ideas what part of this code could be causing this?
OPEN MAIL_CURSOR
FETCH MAIL_CURSOR into #mail1, #sender,#content1
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #email = #email+';'+Email
FROM GRVRIEVANCES
WHERE sentFlag = 'No'
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'Grievances',
#recipients = #email,
#subject = 'Account Details',
#body = #content1;
FETCH MAIL_CURSOR INTO #mail1, #sender, #content1
END
CLOSE MAIL_CURSOR
DEALLOCATE MAIL_CURSOR
END
If you set email to an initial value within the loop, does the issue go away? Also, make sure you setting the sentflag to 'yes'.
WHILE ##FETCH_STATUS = 0
BEGIN
SET #email=''
SELECT #email = #email+';'+Email
Back to basics to solve this one.
Start with some debugging:
Comment out your EXEC part
Add PRINT #email in the same spot
Run the cursor and see the reults, they should be quite enlightening!
Essentially what you're doing is on every cursor execution you are building up this big ol' string of email addresses for all GRVRIEVANCES WHERE sentFlag = 'No'.