Альтернативный вариант на основе набора для этого курсора в SQL? - PullRequest
0 голосов
/ 18 июня 2020

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

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

В настоящее время некоторые электронные письма отправляются напрямую из SQL, но не через Database Mail. Итак, я изучаю возможность использования Database Mail (sp_send_dbmail). Я, вероятно, сделаю полный пересмотр того, как все работает, надеюсь, используя Database Mail, но пока мне интересно, есть ли более простой (временный) способ повышения производительности, возясь с текущим кодом SQL.

В хранимой процедуре курсор используется для вызова другой хранимой процедуры, которая вызывает API-вызов класса контроллера, который с несколькими вызовами других методов в конечном итоге отправляет электронное письмо. Эти C# методы не реализуются асинхронно. Я тоже занят изменением этих методов на asyn c. Неудивительно, что сервер SQL наполовину мертв, когда эти электронные письма отправляются.

Поэтому мне интересно:

  1. Есть ли альтернативный способ на основе набора вместо использования курсора в этом случае?
  2. Как я уже сказал, я собираюсь изменить методы C# на asyn c в любом случае (поскольку его используют другие части системы), но выключено манжета, заметно ли изменится производительность? Мое довольно невежественное мнение заключалось бы в том, что основная проблема связана скорее с курсором.

Вот хранимая процедура с курсором, sp_Interface_PayslipEmails:

DROP PROCEDURE [dbo].[sp_Interface_PayslipEmails]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE PROC [dbo].[sp_Interface_PayslipEmails](@Periods VARCHAR(MAX),@Resourcetag INT = 0)
AS
BEGIN
DECLARE @Collection VARCHAR(50)
DECLARE @Periodid INT
DECLARE @PortalURL VARCHAR(500) = (SELECT TOP 1 Value from [System Variables] WHERE [Variable] =  'Portal URL')
DECLARE @PeriodTable TABLE ([Period id] INT )
IF @Resourcetag = 0 SET @Resourcetag = NULL
INSERT INTO @PeriodTable
(
    [Period id]
)
select [Value] FROM dbo.fn_Split(@Periods,',') 

SET @Periodid = (SELECT MAX([period id]) FROM @PeriodTable)

SET @Collection = (SELECT dbo.fn_GetCalendarCollectionfromPeriod(@Periodid))
SET @Collection = UPPER(REPLACE(@Collection,'Payrun ',''))

UPDATE I
SET I.[Status] = 'Processed'
FROM [dbo].[PER REM Infoslip] I
INNER JOIN @PeriodTable P
ON I.[Period id] = P.[Period id]
INNER JOIN [dbo].[Calendar Periods] cp
ON P.[Period id]  = cp.[Period ID]
AND [cp].[RunType] = 'Normal'
AND I.[Resource Tag] =ISNULL(@Resourcetag,I.[Resource Tag])
 

SELECT DISTINCT I.[Resource Tag],[I].[E-mail Address] 
INTO #Employees
FROM [dbo].[PER REM Infoslip] I
INNER JOIN @PeriodTable P
ON I.[Period id] = P.[Period id]
INNER JOIN [dbo].[Calendar Periods] cp
ON P.[Period id]  = cp.[Period ID]
AND [cp].[RunType] = 'Normal' --only normal periods available for now
WHERE ISNULL([I].[E-mail Address],'') != ''
AND I.[status] = 'Processed'
AND I.[Resource Tag] = ISNULL(@Resourcetag,I.[Resource Tag])

/* declare variables */
DECLARE @RT INT
DECLARE @EmailAddress VARCHAR(150)
DECLARE @Message VARCHAR(MAX) =''

IF @Collection LIKE '%Feb%2020%'
SET @Message = 'Friendly Reminder. All excess leave will be forfeited on the 28th February as per previous communications.'

DECLARE cursor_name CURSOR FOR SELECT [Resource Tag],[E-mail Address] FROM #Employees
OPEN cursor_name

FETCH NEXT FROM cursor_name INTO @RT,@EmailAddress

WHILE @@FETCH_STATUS = 0
BEGIN
    --API Call to send email
    PRINT @Collection
    PRINT @PortalURL
        EXEC Sp_SendPayslipEmail @PortalURL,@RT,@EmailAddress,@Collection,@Message

    FETCH NEXT FROM cursor_name INTO @RT,@EmailAddress
END

CLOSE cursor_name
DEALLOCATE cursor_name
END 

GO

Для большей ясности у меня есть включил вторую хранимую процедуру, код Sp_SendPayslipEmail здесь:

DROP PROCEDURE [dbo].[Sp_SendPayslipEmail]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[Sp_SendPayslipEmail](
  @Hostname NVARCHAR(MAX) 
, @ResourceTag INT
, @Email VARCHAR(256)
, @Period     NVARCHAR(256)
, @Message VARCHAR(MAX))
AS

DECLARE @now VARCHAR(MAX) =  CONVERT(NVARCHAR(MAX), GETDATE(), 13)

DECLARE @OBJECT INT
DECLARE @RESPONSETEXT VARCHAR(8000)
DECLARE @URL NVARCHAR(MAX) =  'https://' + @Hostname + '/payslip/sendpayslipemail?resourceTag=' + CAST(@ResourceTag AS VARCHAR(50))+ '&emailAddress=' + @Email  + '&period=' + @Period + '&message=' + ISNULL(@Message,'') + '&_=' + @now


EXEC sp_OACreate 'Msxml2.ServerXMLHTTP.6.0', @OBJECT OUT
EXEC sp_OAMethod @OBJECT, 'setTimeouts', NULL, 5000, 5000, 30000, 300000

EXEC sp_OAMethod @OBJECT, 'open', NULL, 'get', @URL, 'false'
EXEC sp_OAGetErrorInfo @object
EXEC sp_OAMethod @OBJECT, 'send'
EXEC sp_OAGetErrorInfo @object
EXEC sp_OAMethod @OBJECT, 'responseText',@RESPONSETEXT OUTPUT
SELECT @RESPONSETEXT
EXEC sp_OADestroy @OBJECT
EXEC sp_OAGetErrorInfo @object

GO

UPDATE

Я переместил курсор из сохраненного pro c InterfacePayslipEmails в сохраненный pro c SendPayslipEmail. Также была реализована TVP. SendPayslipEmail использует хранимую процедуру sp_send_dbmail и больше не вызывает API-вызов другой части нашей системы для отправки почты.

Я проверил, что могу получать электронные письма.

Вот измененные хранимые процедуры:

Alter PROC [dbo].[Interface_PayslipEmails](@Periods VARCHAR(MAX),@Resourcetag INT = 0)
AS
BEGIN
    DECLARE @Collection VARCHAR(50)
    DECLARE @Periodid INT
    DECLARE @PortalURL VARCHAR(500) = (SELECT TOP 1 Value from [ System Variables] WHERE [Variable] =  'Portal URL')
    DECLARE @PeriodTable TABLE ([Period id] INT )
    IF @Resourcetag = 0 SET @Resourcetag = NULL
    INSERT INTO @PeriodTable
    (
        [Period id]
    )
    select [Value] FROM dbo.fn_Split(@Periods,',') 
    
    SET @Periodid = (SELECT MAX([period id]) FROM @PeriodTable)

    SET @Collection = (SELECT dbo.fn_GetCalendarCollectionfromPeriod(@Periodid))
    SET @Collection = UPPER(REPLACE(@Collection,'Payrun ',''))

    UPDATE I
    SET I.[Status] = 'Processed'
    FROM [dbo].[PER REM Infoslip] I
    INNER JOIN @PeriodTable P
    ON I.[Period id] = P.[Period id]
    INNER JOIN [dbo].[Calendar Periods] cp
    ON P.[Period id]  = cp.[Period ID]
    AND [cp].[RunType] = 'Normal'
    AND I.[Resource Tag] =ISNULL(@Resourcetag,I.[Resource Tag])
     

    SELECT DISTINCT I.[Resource Tag],[I].[E-mail Address] 
    INTO #Employees
    FROM [dbo].[PER REM Infoslip] I
    INNER JOIN @PeriodTable P
    ON I.[Period id] = P.[Period id]
    INNER JOIN [dbo].[Calendar Periods] cp
    ON P.[Period id]  = cp.[Period ID]
    AND [cp].[RunType] = 'Normal' --only normal periods available for now
    WHERE ISNULL([I].[E-mail Address],'') != ''
    AND I.[status] = 'Processed'
    AND I.[Resource Tag] = ISNULL(@Resourcetag,I.[Resource Tag])

    
/* declare variables */
--DECLARE @RT INT
--DECLARE @EmailAddress VARCHAR(150)
DECLARE @Message VARCHAR(MAX) =''

IF @Collection LIKE '%Feb%2020%'
SET @Message = 'Friendly Reminder. All excess leave will be forfeited on the 28th February as per previous communications.'


--declare variable for Table type
DECLARE @EmailTVP AS EmailAddressTableType;
----insert 
insert into @EmailTVP([resource tag],[e-mail address])
select [resource tag],[e-mail address] from #employees
       
    EXEC SendPayslipEmail @EmailTVP,@PortalURL,@Collection,@Message
    
END 


GO

Обновленный SendPayslipEmail:

Alter Proc [dbo].[SendPayslipEmail] 
(@TVP EmailAddressTableType READONLY,
@PortalURL VARCHAR(500),
@Collection VARCHAR(50),
@Message VARCHAR(MAX))

as

begin

DECLARE @ResTag INT
DECLARE @Email_Address VARCHAR(150)

DECLARE cursorSendEmail CURSOR
    FOR SELECT [Resource Tag],[E-mail Address] FROM @TVP;
    
OPEN cursorSendEmail  

FETCH NEXT FROM cursorSendEmail INTO @ResTag,@Email_Address;

WHILE @@FETCH_STATUS = 0
BEGIN

 EXEC msdb.dbo.sp_send_dbmail  
    @profile_name = 'PayslipEmails',  
    @recipients = @Email_Address,  
    @body =  @ResTag,  
    @subject = 'Testing DBMail' ;  
        

FETCH NEXT FROM cursorSendEmail INTO @ResTag,@Email_Address;
END

CLOSE cursorSendEmail
DEALLOCATE cursorSendEmail

end

1 Ответ

1 голос
/ 19 июня 2020

Сделайте резервную копию Sp_SendPayslipEmail, вам нужно изменить оба pro c, поэтому решите создать новый pro c с другим именем или просто изменить его.

Есть 2 способа:

  1. Сделайте Sp_SendPayslipEmail вещь внутри sp_Interface_PayslipEmails самого себя.

В этом подходе SET Base Query будет отличаться в зависимости от того, что делает Sp_SendPayslipEmail.

Используйте Table Value Paramters.

Создайте User define Table Type

CREATE TYPE EmailAddressTableType 
   AS TABLE
      ( RT int
      , EmailAddress VARCHAR(150) );
GO

Затем измените Sp_SendPayslipEmail

Alter proc Sp_SendPayslipEmail 
@TVP EmailAddressTableType READONLY,
--Other paramter
@PortalURL,
@RT,
@EmailAddress,
@Collection,
@Message

as

begin
 print 'do your thing'
end

Наконец, sp_Interface_PayslipEmails будет выглядеть так ,

Alter PROC [dbo].[sp_Interface_PayslipEmails](@Periods VARCHAR(MAX),@Resourcetag INT = 0)
AS
BEGIN
DECLARE @Collection VARCHAR(50)
DECLARE @Periodid INT
DECLARE @PortalURL VARCHAR(500) = (SELECT TOP 1 Value from [System Variables] WHERE [Variable] =  'Portal URL')
DECLARE @PeriodTable TABLE ([Period id] INT )
IF @Resourcetag = 0 SET @Resourcetag = NULL
INSERT INTO @PeriodTable
(
    [Period id]
)
select [Value] FROM dbo.fn_Split(@Periods,',') 

SET @Periodid = (SELECT MAX([period id]) FROM @PeriodTable)

SET @Collection = (SELECT dbo.fn_GetCalendarCollectionfromPeriod(@Periodid))
SET @Collection = UPPER(REPLACE(@Collection,'Payrun ',''))

UPDATE I
SET I.[Status] = 'Processed'
FROM [dbo].[PER REM Infoslip] I
INNER JOIN @PeriodTable P
ON I.[Period id] = P.[Period id]
INNER JOIN [dbo].[Calendar Periods] cp
ON P.[Period id]  = cp.[Period ID]
AND [cp].[RunType] = 'Normal'
AND I.[Resource Tag] =ISNULL(@Resourcetag,I.[Resource Tag])


SELECT DISTINCT I.[Resource Tag],[I].[E-mail Address] 
INTO #Employees
FROM [dbo].[PER REM Infoslip] I
INNER JOIN @PeriodTable P
ON I.[Period id] = P.[Period id]
INNER JOIN [dbo].[Calendar Periods] cp
ON P.[Period id]  = cp.[Period ID]
AND [cp].[RunType] = 'Normal' --only normal periods available for now
WHERE ISNULL([I].[E-mail Address],'') != ''
AND I.[status] = 'Processed'
AND I.[Resource Tag] = ISNULL(@Resourcetag,I.[Resource Tag])

/* declare variables */
--DECLARE @RT INT
--DECLARE @EmailAddress VARCHAR(150)
DECLARE @Message VARCHAR(MAX) =''

IF @Collection LIKE '%Feb%2020%'
SET @Message = 'Friendly Reminder. All excess leave will be forfeited on the 28th February as per previous communications.'


--declare variable for Table type
DECLARE @EmailTVP AS EmailAddressTableType;
--insert 
insert into @EmailTVP(RT,EmailAddress)
select RT,EmailAddress from #Employees
    --API Call to send email
    --PRINT @Collection
    --PRINT @PortalURL

    --Done
        EXEC Sp_SendPayslipEmail @EmailTVP,@PortalURL,@Collection,@Message
END 

Получите представление о параметре значения таблицы перед изменением,

Табличные параметры

Это очень просто.

Ваш pro c имеет много других возможностей оптимизации, но по одной.

Сколько писем будет отправлено за один раз ??

Edit 1 : объекты sp_OA * устарели. Настройте Dayabase Mail на своем сервере. затем используйте sp_send_dbmail

Изменить 2: См. EXEC msdb.dbo.sp_send_dbmail не может использовать In SET Based Approach (без l oop). Ypu должен включать L oop, например, While или Cursor, предпочитать Cursor.

Использование курсора внутри Sp_SendPayslipEmail далеко намного лучше, чем использовать внутри sp_Interface_PayslipEmails.

В случае, если sp_Interface_PayslipEmails курсор все еще открыт, пока вы выполняете

EXEC Sp_SendPayslipEmail @PortalURL,@RT,@EmailAddress,@Collection,@Message

Курсор открыт гораздо дольше. это потребляет много записок ry.

Так что используйте курсор внутри Sp_SendPayslipEmail. Используйте запись TVP для l oop.

Мы можем конкурировать за использование курсора, оптимизируя pro c каким-либо другим способом.

Примечание: Не добавляйте в дальнейшем к процедуре сохранения префикс 'sp_'.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...