Sending Multiple DB mail using curser - sql

I have a Table which have ID, Subject, Result and Email columns. I want to send Subject and result to user.
I try to use bellow code to do this
Declare #ID INT
Declare Agent Cursor for
SELECT ID FROM myTable
GROUP BY ID
OPEN Agent
FETCH NEXT FROM Agent INTO #ID
While (##Fetch_Status = 0)
Begin
DECLARE #email NVARCHAR(MAX)
SET #email = (SELECT email FROM myTable
WHERE ID = #ID)
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT ID, Subject, Result FROM myTable WHERE ID = ''#ID'''
--print EXEC #query
EXEC msdb.dbo.sp_send_dbmail
#profile_name='Reports',
#recipients='my#email.com',
#subject = 'Results',
#body = ' ',
#body_format = 'HTML',
#query = #query,
#query_result_header = 0,
#exclude_query_output = 1,
#append_query_error = 1,
#attach_query_result_as_file = 1,
#query_attachment_filename = 'qry.txt',
#query_result_no_padding = 1
FETCH NEXT FROM Agent INTO #ID
end
CLOSE Agent
DEALLOCATE Agent
However when I execute this i do not get any error. Only get a message telling
Command(s) completed successfully
I couldn't get the
Mail (Id: 16) queued.
message which should normally come with this kind of executions.
Where is the bug in this script?

You're treating #ID as both numeric and a string (I have no idea what it actually IS in "myTable") - and there's a possibility of a data conversion error on the first execution of sp_send_dbmail.
SET #email = (SELECT email FROM myTable
WHERE ID = #ID)
SET #query = 'SELECT ID, Subject, Result FROM myTable WHERE ID = ''#ID'''
Try changing the above to treat #ID as a numeric.
SET #query = 'SELECT ID, Subject, Result FROM myTable WHERE ID = #ID'

Presumably, you want the query to contain the id. I think you want:
SET #query = REPLACE('SELECT ID, Subject, Result FROM myTable WHERE ID = #ID', '#ID', #ID);
As explained in the documentation:
Note that the query is executed in a separate session, so local variables in the script calling sp_send_dbmail are not available to the query.

Related

Restore Verify Only Loop with Update

I'm running a Restore Verify Only loop through a table, but I need it to UPDATE a field in that table if it's successful. Here is my code:
DECLARE #Path NVARCHAR(max)
DECLARE #DatabaseName NVARCHAR(100)
DECLARE #NSql NVARCHAR(1000)
DECLARE #Update NVARCHAR(200)
DECLARE #DB_Text NVARCHAR(50)= 'Backup has been confirmed for'
BEGIN
DECLARE
#DatabaseId INT = 1,
#NumberOfDBs INT
SELECT #NumberOfDBs= COUNT(*) FROM dbo.RestoreVerifyDatabases
WHILE #DatabaseId<= #NumberOfDBs
BEGIN
SELECT * FROM dbo.RestoreVerifyDatabases WHERE DatabaseId= #DatabaseId
SET #DatabaseId = #DatabaseId + 1
END
SET #Path = (SELECT LastBackupFileName FROM RestoreVerifyDatabases WHERE DatabaseId =
#DatabaseId)
SET #DatabaseName = (SELECT DatabaseName FROM RestoreVerifyDatabases WHERE DatabaseId =
#DatabaseId)
SET #NSql = N'SELECT LastBackupFileName
FROM RestoreVerifyDatabases
WHERE DatabaseName = #DatabaseName
AND DatabaseId = #DatabaseId'
EXEC sp_executesql #NSql
IF #DatabaseId IS NULL
BEGIN
RAISERROR(N'Verify failed. Backup information for database N''#DatabaseName'' not
found.', 16, 1)
END
RESTORE VERIFYONLY
FROM #Path
WITH FILE = #DatabaseId, checksum
SET #Update= N'UPDATE RestoreVerifyDatabases
SET Confirmed = #DB_Text + #DatabaseName
WHERE DatabaseID = #DatabaseId'
EXEC sp_executesql #Update
END
The looping and calling of Restore Verify Only works fine but the UPDATE is not getting called. Please help.
There's a few things wrong with your code as far as I can see. As my comment indicated, you run a loop over the #databaseID, and then immediately increase the id with 1 within the loop.
This means your #databaseId simply will increase until it reaches the maximum value of the id, and then exits the loop. The rest of your code will then execute once (only for that ID).
That happens here in your code:
SELECT #NumberOfDBs= COUNT(*) FROM dbo.RestoreVerifyDatabases
WHILE #DatabaseId<= #NumberOfDBs
BEGIN
SELECT * FROM dbo.RestoreVerifyDatabases WHERE DatabaseId= #DatabaseId
SET #DatabaseId = #DatabaseId + 1
END
-- Bunch of other code
So #databaseID goes from 1 to 50 (just a random number) within that loop, then exists with a value of 50. None of the underlying code ever gets to see any value other than 50.
To fix that, the code should look like this:
SELECT #NumberOfDBs= COUNT(*) FROM dbo.RestoreVerifyDatabases
WHILE #DatabaseId<= #NumberOfDBs
BEGIN
SELECT * FROM dbo.RestoreVerifyDatabases WHERE DatabaseId= #DatabaseId
-- Bunch of other code
SET #DatabaseId = #DatabaseId + 1
END
This would ensure the "bunch of other code" processes #databaseid = 1, then processes #databaseid = 2, etc. until #databaseid = 50.
You can also remove this line:
SELECT * FROM dbo.RestoreVerifyDatabases WHERE DatabaseId= #DatabaseId
I suspect you have it for debugging purposes, but it doesn't really do anything.
These lines can be simplified:
SET #Path = (SELECT LastBackupFileName FROM RestoreVerifyDatabases WHERE DatabaseId =
#DatabaseId)
SET #DatabaseName = (SELECT DatabaseName FROM RestoreVerifyDatabases WHERE DatabaseId =
#DatabaseId)
as:
SELECT #Path = LastBackupFileName, #DatabaseName = DatabaseName FROM RestoreVerifyDatabases WHERE DatabaseId = #DatabaseId
You also have a RAISEERROR. I suspect that is because you do a count(*), rather than selecting actually existing #databaseids. This works in an ideal scenario, where databases never get deleted. In the reasl world, though, you'd go from databaseid 1 to 5, because 2, 3, and 4 were removed. Your count will still be 50 databases, but you'd miss all databases with ids above the count due to these gaps. You'd be trying to process the databaseid that no longer exists, and miss the ones with an id > 50.
You could instead write your loop to do something like the following:
SELECT #DatabaseID = MIN(DatabaseID) FROM dbo.RestoreVerifyDatabases
WHILE #datbaseID IS NOT NULL
BEGIN
-- Do stuff
SELECT #DatabaseID = MIN(DatabaseID) FROM dbo.RestoreVerifyDatabases WHERE databaseID > #databaseID
END
Finally, your dynamic code isn't working. You are adding literal text rather than the value of the parameters. If I print the value of #Update at the end, it shows as:
UPDATE RestoreVerifyDatabases
SET Confirmed = #DB_Text + #DatabaseName
WHERE DatabaseID = #DatabaseId
Your code should be something like:
SET #Update= CONCAT('UPDATE RestoreVerifyDatabases SET Confirmed = ''', #DB_Text, ' ', #DatabaseName, ''' WHERE DatabaseID = ', #DatabaseId)
Which outputs:
UPDATE RestoreVerifyDatabases SET Confirmed = 'Backup has been confirmed for Last' WHERE DatabaseID = 2

How to send an email only to users who appear in query results?

I am creating a query to return tickets that haven't been updated in the required time and send via email. I have everything working except I want to email only the users whose names appear in the query results.
I have tried setting the # user name = to user id and adding to the email as below. Not sure if this is even close.
Declare #username VARCHAR(MAX)
--("query")--
Set #username =[userid]--(in query results)--
exec msdb.dbo.sp_send_dbmail
#participants ='#username+#mydomain.com'
Expect to send emails to users whose user name appears in query results.
You can use STUFF with FOR XML PATH to create a ; delimited list for the #recipients parameter:
DECLARE #EmailList varchar(max)
SELECT #EmailList = STUFF((
SELECT ';' + Username + '#gmail.com'
FROM yourTable
FOR XML PATH('')
), 1, 1, '')
Then:
EXEC msdb.dbo.sp_send_dbmail
#recipients = #EmailList,
#subject = 'foo',
#body = 'bar',
etc
You can use Coalesce to create a list separated with semicolons which is what dbmail wants.
Declare #username varchar(max)
SELECT
#username = COALESCE(#username + '; ','') + userid + '#mydomain'
FROM
Yourtable
WHERE
Yourquery
EXEC msdb.dbo.sp_send_dbmail
#recipients = #username
#subject = 'BARK BARK'
#Body = 'BARK BARK BARK'
You can use a CURSOR to fetch your email address list and send the email to that list:
DECLARE #username AS VARCHAR(MAX) = ''
DECLARE #participants AS VARCHAR(MAX) = ''
DECLARE cursor_name CURSOR
FOR
SELECT userid
FROM YourTable
OPEN cursor_name;
FETCH NEXT
FROM cursor_name
INTO #username;
WHILE ##FETCH_STATUS = 0
BEGIN
#participants += #username + '#mydomain.com;'
FETCH NEXT FROM cursor_name
INTO #username;
END
CLOSE cursor_name;
DEALLOCATE cursor_name;
EXEC msdb.dbo.sp_send_dbmail #recipients = #participants
etc

Saving the result of a SQL query into a local SQL variable

I have the below code that is not working as expected:
DECLARE #MySelect varchar(max), #MyRecipients varchar(max), #MyId
char(4),#MyResult varchar(max);
SET #MyId = '1';
SET #MySelect = 'SELECT SQL_Script FROM AutoSendMail.dbo.A01_St WHERE
ID =' + #MyId;
USE msdb
EXEC sp_send_dbmail
#profile_name='Operator',
#recipients='Mail#test.com',
#subject='Mail test',
#body= 'This is a test-mail',
#mailitem_id='1',
#query= #MyResult,
#attach_query_result_as_file = 1
I need to save the result of the select query: #MySelect into the variable #MyResult, because I need to send the result of the SELECT via Mail. But I can't find a way to do just that. The query should return a single string value.
You can do this without dynamic SQL, like so:
DECLARE #MyRecipients VARCHAR(MAX) ,
#MyId CHAR(4) = '1' ,
#MyResult VARCHAR(MAX);
SET #MyId = '1';
SELECT TOP 1
#MyResult = SQL_Script
FROM AutoSendMail.dbo.A01_St
WHERE ID = #MyId
USE msdb
EXEC sp_send_dbmail #profile_name = 'Operator', #recipients = 'Mail#test.com',
#subject = 'Mail test', #body = 'This is a test-mail', #mailitem_id = '1',
#query = #MyResult, #attach_query_result_as_file = 1
Not that you need dynamic SQL, but please note, that doing this:
SET #MySelect = 'SELECT SQL_Script FROM AutoSendMail.dbo.A01_St WHERE ID =' + #MyId;
Does not execute any code, it simply sets the value of #MySelect. You would need to call an execute command to run the SQL:
EXEC SP_EXECUTESQL #MySelect
You can declare table variable, insert into it the results of executing your query and then select into your result variable:
DECLARE #t TABLE(SQL_Script varchar(max))
INSERT INTO #t EXEC(#MySelect)
SELECT TOP 1 #MyResult = SQL_Script FROM #t
You can also use sp_executesql procedure with output parameters like:
SET #MyId = '1';
SET #MySelect = 'SELECT #MyResult = SQL_Script FROM AutoSendMail.dbo.A01_St WHERE
ID =' + #MyId;
EXEC sp_executesql #MySelect, '#MyResult varchar(max) OUTPUT', #MyResult OUTPUT
How about this?
select #MyResult = stuff((
select ',' + convert(nvarchar(20), SQL_Script)
from AutoSendMail.dbo.A01_St
where ID = #MyId
for
xml path('')
), 1, 1, '')
This outputs a comma delimited string though

Stored procedure sends email even when condition is false. Any ideas?

When a user uses our site to process certain requests, upon exiting the site, an email with a link goes out to that user asking him/her to click the link to take a survey and share his/her user experiences with us.
Below is the stored procedure that I have written that does as described above.
ALTER proc [dbo].[SendSurvey]
AS
BEGIN
Declare #sender nvarchar(200)
declare #dept nvarchar(200) = ''
declare #loc nvarchar(200) = ''
declare #dteCreated nvarchar
declare #RequestID nvarchar(50) = ''
declare #authorizedname nvarchar(200) = ''
declare #email nvarchar(200) = ''
declare #message nvarchar(1000) = ''
declare #mailid int = 0
SET QUOTED_IDENTIFIER OFF;
SELECT
#email = email, #mailid=ID, #message = #message,
#RequestID = RequestID,
#authorizedname = SUBSTRING(submittedBy, CHARINDEX(',', submittedBy) + 1, LEN(submittedBy) - CHARINDEX(',', submittedBy) + 1)
+ ' ' + SUBSTRING(submittedBy, 1, CHARINDEX(',', submittedBy) - 1),
#loc = Bldg, #dtecreated = DateCreated, #dept = Department
FROM
Survey
WHERE
email = #email
AND Email IS NOT NULL OR Email != ''
AND (orderStatus != 1)
SELECT #message = 'This is a computer generated email message.
Please DO NOT use the REPLY button above to respond to this email.
Dear '+ #authorizedname +':
Thank you for using the order processing system.
Please click the link below to complete a survey
http://feedbacksurvey.php?rID=' +#RequestID+'&loc='+Replace(#loc,' ', '%20')+'&dept='+Replace(#dept,' ', '%20')+'
Regards,
web admin.'
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'Customer Feedback Survey',
#recipients = #Email, -- your email
#subject = 'Customer Feedback Survey',
#body = #message;
UPDATE Survey
SET orderStatus = 1
WHERE orderStatus != 1 AND ID = #mailid
END
There are two problems with the stored procedure.
There is a column orderStatus which is a BIT data type with True (1) of false(0) value.
If the orderstatus is false, then send emails with records associated with it.
After sending the email, update orderstatus to true so the email doesn't get sent a second time.
This is not working. When I execute the stored procedure where all records on the table have orderstatus set to True, email still goes out.
the second problem that I am having is that the code is not sending out all records where orderStatus is True. It just sends email one at a time.
We would like emails to be send out for ALL records where orderstatus = 1 (True).
Any ideas what I am doing wrong?
You mixed AND with OR in your WHERE clause. The results will include all rows where Email != '', regardless of the other conditions.
Use parens to make this work:
WHERE email=#email
AND (Email IS NOT NULL or Email != '')
AND (orderStatus != 1)
As for why it's sending one email at a time, you are using your query to populate scalar variables.
SELECT #email = email...
Will result in #email being populated with one value, no matter how many rows the query returns.
the second problem that I am having is that the code is not sending out all records where orderStatus is True. It just sends email one at a time.
Yeah - that's how sp_send_dbmail works. You'll need a cursor to send > 1 email.
DECLARE c CURSOR LOCAL FAST_FORWARD READ_ONLY FOR
SELECT ....
FETCH NEXT FROM c INTO ....
WHILE ##FETCHSTATUS = 0 BEGIN
EXEC sp_send_dbmail ...
FETCH NEXT FROM c INTO ....
END
CLOSE c
DELLOCATE c

Local variables in sp_send_dbmail?

I'm working on an SQL stored procedure that is supposed to send an attachment with the results of a query.
I am using sp_send_dbmail to send the email.
Within the query I'd like to send, I join the to a table variable. When I executed the stored procedure, I got an error message which said that the variable didn't exist.
My code:
DECLARE #t TABLE (
id INT IDENTITY(1,1),
some fields
)
DECLARE #query VARCHAR(MAX)
SET #query = 'SELECT
some values
FROM #t t
INNER JOIN dbo.Table d ON t.field = d.field
EXEC msdb.dbo.sp_send_dbmail #recipients=#recipients_list,
#subject = #subject,
#query = #query,
#attach_query_result_as_file = 1,
#query_result_width = 4000,
#query_attachment_filename = 'Details.txt'
Is there any way for me to refer to the local variable within this stored procedure? If not, why not?
TIA!
(I am using SQL Server 2005)
The query runs in a different context than your original code body, so it is not aware of any local variables. Try using a global temp table instead.
CREATE TABLE ##t (
id INT IDENTITY(1,1),
some fields
)
DECLARE #query VARCHAR(MAX)
SET #query = 'SELECT
some values
FROM ##t t
INNER JOIN dbo.Table d ON t.field = d.field'
EXEC msdb.dbo.sp_send_dbmail #recipients=#recipients_list,
#subject = #subject,
#query = #query,
#attach_query_result_as_file = 1,
#query_result_width = 4000,
#query_attachment_filename = 'Details.txt'
DROP TABLE ##t