I have a SQL Statement that works as I want.
select COUNT(*), MIN(emailed_to)from email.email_archive
group by emailed_to
order by COUNT(*) desc
The output looks like this.
13 deadlockIE12388nnhy32#hepmeplease.com;
8 deadlockIE1277yhygt#hepmeplease.com;
4 deadlockFF17uyt9xx967#hepmeplease.com;
...
...
...
1 deadlockFF17uytsdfa7#hepmeplease.com;
This is simple enough, but then I would have to remember to run the select every day and make sure things are ok. I want to have the stored procedure email me once in a while and I can decide if I have an issue. So I have hacked together the following from many resources:
use MYDB;
go
IF SCHEMA_ID('monitors') IS NULL EXECUTE('CREATE SCHEMA monitors AUTHORIZATION dbo')
GO
if object_id('monitors.email_abuse') is null
exec('create procedure monitors.email_abuse as print ''stub'' return');
GO
alter procedure monitors.email_abuse
(#to varchar(max) = 'itops#hepmeplease.com',
#sendemail tinyint = 1)
as
set nocount on ;
set transaction isolation level read uncommitted ;
begin try
declare #errmsg varchar(max) = '',
#subject nvarchar(255);
select #subject = 'Run Away Email Monitor';
select #errmsg = REPLICATE(char(10),1)+
'# of Emails'+
REPLICATE(char(9),1)+
'Email Address'+
REPLICATE(CHAR(10),1);
select #errmsg = #errmsg +REPLICATE(char(9),1)+
CAST(COUNT(*) as CHAR(10))+
REPLICATE(char(9),1)+
CAST(MIN(emailed_to) as CHAR(45))
from
email.email_archive
group by
emailed_to
order by
COUNT(*) desc;
print #errmsg;
if #sendemail = 1
begin
exec master.dbo.sp_email
#to = #to,
#subject = #subject,
#body = #errmsg;
end
end try
begin catch
-- unexpected errors
exec sp_raise_error #rethrow = 1, #textdata = N'Error in monitors.email_abuse', #emailTo = N'itops#hepmeplease.com'
end catch
go
But it then emails me the following output that is just one line. I know that there are many lines but for some reason when I put the COUNT(*), MIN(emailed_to) into the CAST statement this no longer works. I get a email that has the header and one line. If I just print the output of #errmsg I exactly what I get in the email, the header and the one line. just like below.
# of Emails Email Address
1 y#y.com;
I am not sure what I am doing wrong with my cast statement.
Explanation:
My guess is that the code you are actually using is slightly different from the code you have posted here because when I take your code and the following data in a test database, things work out fine.
create table email_archive
(
id int,
emailed_to nvarchar(255)
)
insert into email_archive values
( 1, 'one#helpme.com'), ( 2, 'two#helpme.com'), ( 3, 'three#helpme.com'),
( 4, 'four#helpme.com'), ( 5, 'one#helpme.com'), ( 6, 'two#helpme.com'),
( 7, 'three#helpme.com'), ( 8, 'four#helpme.com'), ( 9, 'one#helpme.com'),
(10, 'two#helpme.com'), (11, 'three#helpme.com'), (12, 'four#helpme.com'),
(13, 'one#helpme.com'), (14, 'two#helpme.com'), (15, 'three#helpme.com'),
(16, 'four#helpme.com'), (17, 'one#helpme.com'), (18, 'one#helpme.com'),
(19, 'one#helpme.com'), (20, 'three#helpme.com'), (21, 'three#helpme.com')
I am thinking you may have hit upon an issue discussed here: http://bit.ly/cMlnjt
Since I can't be sure I offer you two alternative solutions that will definitely get the job done, even though as others have mentioned this aggregate concatenation should work without an issue.
Alternatives:
To get what you are looking for, I prefer one of the following two options
1) Just make sp_send_dbmail do the work for you.
2) Go with a cursor solution
Option 1:
EXEC msdb..sp_send_dbmail #profile_name = 'MyMailProfile',
#recipients = 'my_email#domain.com',
#subject = 'Runaway Email Monitor',
#body = 'Runaway emails found',
#query = 'SELECT COUNT(*), emailed_to FROM mydb.dbo.email_archive GROUP BY emailed_to HAVING COUNT(*) > 5 ORDER BY COUNT(*) DESC'
Note: The having clause makes this only display rows where the count is greater than 5.
Option 2:
USE test
IF EXISTS ( SELECT name FROM test.sys.sysobjects WHERE type = 'P' AND name = 'usp_MonitorEmails' )
BEGIN
DROP PROCEDURE dbo.usp_MonitorEmails
END
GO
CREATE PROCEDURE usp_MonitorEmails
#Subject nvarchar(255) = '',
#Importance varchar(6) = 'NORMAL',
#Sensitivity varchar(12) = 'NORMAL',
#Recipients varchar(MAX) = NULL,
#MinimumCount int = 0
AS
BEGIN
SET NOCOUNT ON
IF UPPER(#Importance) NOT IN ('LOW', 'NORMAL', 'HIGH') SET #Importance = 'NORMAL'
IF UPPER(#Sensitivity) NOT IN ('NORMAL', 'PERSONAL', 'PRIVATE', 'CONFIDENTIAL') SET #Sensitivity = 'NORMAL'
DECLARE #run bit,
#message nvarchar(MAX)
SELECT #run = 0,
#subject = 'Run Away Email Monitor',
#message = 'Run away emails found' + CHAR(13)+CHAR(10) +
'Count Email Address' + CHAR(13)+CHAR(10) +
'----------- ------------------------------------------------------------------------------' + CHAR(13)+CHAR(10)
DECLARE #count int,
#email nvarchar(255)
DECLARE BodyCursor CURSOR STATIC FOR
SELECT COUNT(*), emailed_to FROM email_archive GROUP BY emailed_to HAVING COUNT(*) > #MinimumCount ORDER BY COUNT(*) DESC
OPEN BodyCursor
FETCH NEXT FROM BodyCursor
INTO #count, #email
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #message = #message + REPLICATE(N' ', 11-LEN(CAST(#count AS nvarchar(22)))) + CAST(#count AS nvarchar(22)) + ' ' + #email + CHAR(13)+CHAR(10), #run = 1
FETCH NEXT FROM BodyCursor
INTO #count, #email
END
CLOSE BodyCursor
DEALLOCATE BodyCursor
IF #run = 1 AND LEN(#Recipients) > 0
BEGIN
EXEC msdb..sp_send_dbmail #profile_name = 'MyMailProfile',
#recipients = #Recipients,
#subject = #Subject,
#body = #Message,
#body_format = 'TEXT',
#importance = #Importance,
#sensitivity = #Sensitivity
END
END
GO
Note: I prefer this method because of the flexibility I have in the way the messages are formatted. This will also only send the email if there are rows returned where the minimum count is reached.
You will only get one value because you have no loop through the recordset containing the emails. You will need to use a CURSOR to traverse the set and build up your #errmsg variable with the contents of each record.
Nimble SQL jockeys will probably point out you could avoid the CURSOR with the correct kind of join in the query but I always find that harder than this method and if it doesn't have to be high performance it does the job.
Here's some example code;
DECLARE #myGlobalVar nvarchar(255);
DECLARE #myVarCol1 nvarchar(10);
DECLARE #myVarCol2 nvarchar(10);
DECLARE myCursor CURSOR FOR
SELECT col1, col2
FROM table1
WHERE wanted = 1
ORDER BY col1;
OPEN myCursor;
FETCH NEXT FROM myCursor
INTO #myVarCol1, #myVarCol2;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #myGlobalVar = #myGlobalVar + ' ' + #myVarCol1 + ' ' + #myVarCol2;
FETCH NEXT FROM myCursor
INTO #myVarCol1, #myVarCol2;
END
CLOSE vendor_cursor;
DEALLOCATE vendor_cursor;
Always remember to FETCH NEXT inside the loop unless you want to have to kill the process! You should always CLOSE and DEALLOCATE the cursor too or you'll run out of memory eventually.
I've had similar issues when trying to use this approach to concatenation in the past. Often some messing around with the SELECT resolves it. The FOR XML approach to concatenation is more robust.
I tried to reproduce the issue here though and couldn't (it worked correctly for me and output the 2 different email addresses in the concatenation).
Does this work for you? If so maybe compare the execution plans and see what's different.
set nocount on
create table #email_archive
(
emailed_to CHAR(45)
)
insert into #email_archive
select 'deadlockIE12388nnhy32#hepmeplease.com;' union all
select 'deadlockIE12388nnhy32#hepmeplease.com;' union all
select 'deadlockIE12388nnhy32#hepmeplease.com;' union all
select 'deadlockIE12388nnhy32#hepmeplease.com;' union all
select 'deadlockIE12388nnhy32#hepmeplease.com;' union all
select 'deadlockIE12388nnhy32#hepmeplease.com;' union all
select 'deadlockIE12388nnhy32#hepmeplease.com;' union all
select 'deadlockIE12388nnhy32#hepmeplease.com;' union all
select 'deadlockIE1277yhygt#hepmeplease.com;'
declare #errmsg varchar(max) = '',
#subject nvarchar(255) = 'Run Away Email Monitor';
select #errmsg = REPLICATE(char(10),1)+
'# of Emails'+
REPLICATE(char(9),1)+
'Email Address'+
REPLICATE(CHAR(10),1);
select #errmsg = #errmsg +REPLICATE(char(9),1)+
CAST(COUNT(*) as CHAR(10))+
REPLICATE(char(9),1)+
CAST(MIN(emailed_to) as CHAR(45))
from
#email_archive
group by
emailed_to
order by
COUNT(*) desc;
print #errmsg;
drop table #email_archive;
The answers above were all great answers..but the simplest of all came from Mike Talley overnight.
select #errmsg = #errmsg +REPLICATE(char(9),1)+
CAST(COUNT(*) as CHAR(10))+
REPLICATE(CHAR(9),1)+
cast(substring(emailed_to, 1, 45) as char(45))+
REPLICATE(CHAR(10),1)
Once I dropped the MIN and added in the substring the monitor worked like a charm..
Related
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
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.
I have seen a couple solutions to this SQL error(8115) but none seems to work for me. I think its because I am using dynamic SQL script. I dynamically pull data from multiple servers that have the same structure and insert into a central table. The issue I have having is that when using dynamic script as seen below I keep getting an error.
DECLARE #SQL NVARCHAR(4000);
DECLARE #counter int = 0;
DECLARE #scriptcnt int;
declare #startdate varchar(8) = '20180304';-- cast(#startdate as date)
declare #enddate varchar(8) = '20180305';--cast(#enddate as date)
select srv.name
INTO #ServerNames
from sys.servers srv
where name like 'Pulse%'
order by 1;
select name,right(name,5) store_no
into #locationCode
from #SERVERNAMES;
With ServerCTE AS (
SELECT Root=NAME + '.[POS].[dbo].'
FROM #SERVERNAMES)
Select
Root + '[Customer]' as Customer
INTO #Tables
from ServerCTE
Begin Transaction
delete from WFLCustomerMaster
--where Location_Code = '16450'--#storeNum
commit;
select
'
Begin TRY
Begin Transaction
Insert into WFLCustomerMaster(.....)
SELECT .....
FROM '+ Customer + '
where [Last_Order_Date] between '+ #startdate+ ' and '+ #enddate+ ' [This line is the issue]
commit;
END TRY
BEGIN CATCH
SELECT
ERROR_PROCEDURE() as nameofprocedure,
ERROR_NUMBER() AS errorcode,
error_line() as errorline,
ERROR_MESSAGE() as errormessage
END CATCH' as Customer into #query
from #Tables a, #locationCode b
where left(a.Customer,13) = b.name
select #scriptcnt = count(*) from #query
while #counter < #scriptcnt
begin
set #counter = #counter + 1
SET #SQL = (SELECT Customer FROM
(SELECT ROW_NUMBER() OVER (ORDER BY Customer) AS ID, Customer FROM #query) MySQL
WHERE ID=#Counter )
EXEC SP_EXECUTESQL #SQL
end
I tried converting #startdate and #enddate to date, datetime and datetime2 but nothing works.
I realize that when I hard code the date it works as per below:
...'where [Last_Order_Date] between ''20180306'' and ''20180306'''....
So I know that this part is the issue. Can someone assist?
I am trying to create a User Defined Cursor, but I get the following error,
Msg 102, Level 15, State 1, Line 25
Incorrect syntax near ';'.
I am using AdventureWorks2008r2
This is my code ;
USE AdventureWorks2008r2
GO
DECLARE
-- LOCAL VARIABLEs
#OrderId INT,
#status TINYINT
--declare the cursor
DECLARE mynamelist CURSOR STATIC
FOR
SELECT Sales.SalesOrderHeader.SalesOrderID, Sales.SalesOrderHeader.STATUS
FROM Sales.SalesOrderHeader;
OPEN mynamelist;
FETCH NEXT
FROM mynamelist
INTO #status,
#OrderId
WHILE ##FETCH_STATUS = 0
IF #status = 1
BEGIN
FETCH NEXT
FROM mynamelist
INTO #status,#OrderId
PRINT 'Order Number:' + CAST(#OrderId AS VARCHAR(10)) + 'Status:Approved';
END;
ELSE IF #status = 3
PRINT 'Order Number:' + CAST(#OrderId AS VARCHAR(10)) + 'Status:Backordered';
ELSE IF #status = 4
PRINT 'Order Number:' + CAST(#OrderId AS VARCHAR(10)) + 'Status:Rejected';
ELSE IF #status = 5
PRINT 'Order Number:' + CAST(#OrderId AS VARCHAR(10)) + 'Status:Shipped';
ELSE
PRINT 'Order Number:' + CAST(#OrderId AS VARCHAR(10)) + 'Status:Cancelled';
END;
CLOSE nynamelist;
DEALLOCATE mynamelist;
How can I fix this error
so you have a few syntax issues and then some logic issues with the way your code is written. You have the FETCH NEXT nested in an IF statement so the WHILE block would not actually select the next record unless #status = 1....
Organizing your code with indents and white space will help you dramatically to understand the logic and precedence of your steps. You were missing a BEGIN for the cursor loop.
Also note that semicolon's for sql-server are not always necessary except in the case of THROW and Common Table Expressions the statements prior to those have to be terminated with a semicolon which is why you often see them written as ;WITH cte as and ;THROW 51000,... I am sure there are other examples I am just giving a few.
Anyway, dump all of the If use a case expression which is much more geared for this type of operation build your string and then print it.
Also note, when you have a syntax error in the Results you can double click on it and it should bring you to the syntax error in question.
DECLARE
-- LOCAL VARIABLEs
#OrderId INT , #status TINYINT
--declare the cursor
DECLARE mynamelist CURSOR STATIC FOR
SELECT
h.SalesOrderID,
h.Status
FROM
Sales.SalesOrderHeader h
open mynamelist;
FETCH NEXT FROM mynamelist INTO #status, #OrderId
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #Message NVARCHAR(1000)
SET #Message = 'Order Number:' + CAST(#OrderId as VARCHAR(10)) + 'Status:'
SET #Message = #Message + CASE
WHEN #status = 1 THEN 'Approved'
WHEN #status = 3 THEN 'Backordered'
WHEN #status = 4 THEN 'Rejected'
WHEN #status = 5 THEN 'Shipped'
ELSE 'Cancelled'
END
PRINT #Message;
FETCH NEXT FROM mynamelist INTO #status, #OrderId
END
CLOSE nynamelist;
DEALLOCATE mynamelist;
I recently asked a question about making a query that would document the results of a test query in a separate table.
There was, however, one thing that I didn't ask in the previous question that seemed like it was a large enough matter to simply make a new topic about. This query (courtesy of Steve Mangiameli):
DECLARE #testStatus NVARCHAR(MAX);
IF (
SELECT COUNT(*)
FROM Table1
WHERE Table1.Column1 = ''
) = 0
SET #testStatus = 'Test Passed'
ELSE
SET #testStatus = 'Test Failed'
INSERT INTO Table2 (FileName, Date, Result)
VALUES ('File1', GetDate(), #testStatus)
needs to be run across multiple databases. Here is the current version that I have worked up using another query of mine that does a similar thing but this one doesn't work. EDIT: To be more clear, I get an error message `Must declare the scalar variable "#testStatus".
DECLARE #dbname NVARCHAR(200);
DECLARE #testStatus NVARCHAR(MAX);
DECLARE #query NVARCHAR(MAX);
DECLARE db_cursor CURSOR FOR
SELECT name FROM sys.databases
WHERE name LIKE '%DBTag%'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #dbname
WHILE ##FETCH_STATUS = 0
BEGIN
SET #query = CAST('
IF (
SELECT COUNT(*)
FROM [' +#dbname+ '].dbo.Table1
WHERE [' +#dbname+ '].dbo.Table1.Column1 = ''''
) = 0
SET #testStatus = ''Test Passed''
ELSE
SET #testStatus = ''Test Failed''
INSERT INTO [Database].dbo.Table2(FileName, Result, Date)
VALUES (''File1'', #testStatus, GETDATE())'
AS NVARCHAR(MAX))
EXECUTE (#query)
FETCH NEXT FROM db_cursor INTO #dbname
END
CLOSE db_cursor
DEALLOCATE db_cursor;
I'm wondering if there is something fundamentally "wrong" with how I've even gone about trying to make this work.
The variable is declared outside of your dynamic sql. When the dynamic sql executes your variables are out of scope. You can fix this easily by declaring the variable again inside your dynamic sql.
SET #query = CAST('
declare #testStatus varchar(20);
IF (
SELECT COUNT(*)
FROM [' +#dbname+ '].dbo.Table1
WHERE [' +#dbname+ '].dbo.Table1.Column1 = ''''
) = 0
SET #testStatus = ''Test Passed''
ELSE
SET #testStatus = ''Test Failed''
INSERT INTO [Database].dbo.Table2(FileName, Result, Date)
VALUES (''File1'', #testStatus, GETDATE())'
AS NVARCHAR(MAX))