Проблема превращения простого выбора MsSQL в монитор - PullRequest
1 голос
/ 13 октября 2010

У меня есть оператор SQL, который работает так, как я хочу.

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

Вывод выглядит следующим образом.

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

Это достаточно просто, но тогда я должен был бы помнить, чтобы запустить выбор каждый день и убедиться, что все в порядке. Я хочу, чтобы хранимая процедура время от времени отправляла мне электронное письмо, и я могу решить, есть ли у меня проблема. Итак, я взломал следующее из многих ресурсов:

<code>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, то получу именно то, что получу в письме, заголовке и одной строке. как показано ниже.

<code># of Emails  Email Address
    1           y@y.com;

Я не уверен, что я делаю неправильно с моим заявлением.

Ответы [ 4 ]

2 голосов
/ 13 октября 2010

Объяснение:

Я предполагаю, что код, который вы на самом деле используете, немного отличается от кода, который вы разместили здесь, потому что когда я беру ваш код и следующие данные в тестебаза данных, все работает отлично.

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'

Примечание. При условии, что в этом разделе отображаются только те строки, число которых превышает 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

Примечание: Я предпочитаю этот метод из-за гибкости, которую я имею в способе форматирования сообщений.Это также только отправит электронное письмо, если будут возвращены строки, в которых достигнуто минимальное количество.

0 голосов
/ 13 октября 2010

Ответы, приведенные выше, были отличными. Но самое простое из них пришло от Майка Тэлли за одну ночь.

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 и добавил в подстроку, монитор работал как шарм

0 голосов
/ 13 октября 2010

У меня были подобные проблемы при попытке использовать этот подход к конкатенации в прошлом.Часто некоторые возни с SELECT разрешают это.Подход FOR XML к объединению является более надежным.

Я попытался воспроизвести проблему здесь, но не смог (он работал правильно для меня и вывел 2 разных адреса электронной почты в объединении).1004 * Это работает для вас?Если так, возможно, сравните планы выполнения и посмотрите, что отличается.

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;
0 голосов
/ 13 октября 2010

Вы получите только одно значение, потому что у вас нет циклического перебора набора записей, содержащего электронные письма.Вам нужно будет использовать CURSOR для обхода набора и создать переменную @errmsg с содержанием каждой записи.

Проворные SQL-жокеи, вероятно, укажут, что вы могли бы избежать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 внутри цикла, если вы не хотите убивать процесс!Вы также должны всегда указывать CLOSE и DEALLOCATE курсор, иначе со временем у вас закончится память.

...