无法将简单的MsSQL选择转换为监视器

时间:2010-10-13 02:17:32

标签: sql sql-server tsql monitoring

我有一个可以按照我想要的方式运行的SQL语句。

select COUNT(*), MIN(emailed_to)from email.email_archive
group by emailed_to
order by COUNT(*) desc

输出看起来像这样。

13   deadlockIE12388nnhy32@hepmeplease.com;
8   deadlockIE1277yhygt@hepmeplease.com;
4   deadlockFF17uyt9xx967@hepmeplease.com;
...
...
...
1       deadlockFF17uytsdfa7@hepmeplease.com;

这很简单,但是我必须记住每天运行选择并确保一切正常。我想让存储过程偶尔给我发电子邮件,我可以决定是否有问题。所以我从许多资源中攻击了以下内容:

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

但它然后通过电子邮件向我发送以下输出,这只是一行。我知道有很多行,但出于某些原因,当我将COUNT(*), MIN(emailed_to)放入CAST语句时,这不再起作用。我收到一封包含标题和一行的电子邮件。如果我只是打印@errmsg的输出我就是我在电子邮件中得到的,标题和一行。就像下面一样。

# of Emails  Email Address
    1           y@y.com;

我不确定我的演员声明我做错了什么。

4 个答案:

答案 0 :(得分:2)

<强>解释

我的猜测是你实际使用的代码与你在这里发布的代码略有不同,因为当我把你的代码和以下数据放在测试数据库中时,事情就好了。

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')

我想你可能遇到了这里讨论的问题:http://bit.ly/cMlnjt

由于我无法确定我为您提供了两个可以完成工作的替代解决方案,即使正如其他人提到的那样这个聚合连接应该无需工作一个问题。

<强>备选方案:

为了得到你想要的东西,我更喜欢以下两个选项之一

1)只需让sp_send_dbmail为你完成工作。

2)使用光标解决方案

选项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'

注意:having子句仅显示计数大于5的行。

选项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

注意:我更喜欢这种方法,因为我在格式化邮件方面具有灵活性。如果返回的行达到最小计数,这也将发送电子邮件。

答案 1 :(得分:0)

您只会获得一个值,因为您没有循环包含电子邮件的记录集。您需要使用CURSOR遍历该集合,并使用每条记录的内容构建您的@errmsg变量。

Nimble SQL jockeys可能会指出你可以避免CURSOR在查询中使用正确的连接类型,但我总是觉得比这种方法更难,如果它不是高性能它会这份工作。

这是一些示例代码;

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;

永远记住循环中的FETCH NEXT,除非您想要杀死进程!您应始终CLOSEDEALLOCATE光标,否则最终会耗尽内存。

答案 2 :(得分:0)

在尝试使用此方法进行连接时,我遇到了类似的问题。通常一些搞乱SELECT的解决方案。 FOR XML连接方法更加健壮。

我试图在这里重现这个问题但是不能(它对我来说正常工作并在串联中输出2个不同的电子邮件地址)。

这对你有用吗?如果是这样,可以比较执行计划,看看有什么不同。

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;

答案 3 :(得分:0)

上面的答案都是很好的答案..但最简单的是一夜之间来自Mike Talley。

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)

一旦我放下MIN并添加到子串中,监视器就像一个魅力......