Sql server SQL中此游标的可选基于集合的选项?

Sql server SQL中此游标的可选基于集合的选项?,sql-server,asynchronous,Sql Server,Asynchronous,我是那些努力用集合来思考问题的开发人员之一。至少我知道。:-) 我被要求调查向用户发送事务性电子邮件的问题。我不是功能的原始程序员,我认为原始程序员也离开了公司。叹息 目前,一些电子邮件是直接从SQL发送的,但不是通过数据库邮件。 因此,我正在研究使用数据库邮件(sp_send_dbmail)。 我可能会彻底检查工作方式,希望使用数据库邮件,但与此同时,我想知道是否有一种更简单(临时)的方法可以通过修改当前的SQL代码来提高性能 在存储过程中,游标用于调用另一个存储过程,该存储过程对控制器类进行

我是那些努力用集合来思考问题的开发人员之一。至少我知道。:-)

我被要求调查向用户发送事务性电子邮件的问题。我不是功能的原始程序员,我认为原始程序员也离开了公司。叹息

目前,一些电子邮件是直接从SQL发送的,但不是通过数据库邮件。 因此,我正在研究使用数据库邮件(sp_send_dbmail)。 我可能会彻底检查工作方式,希望使用数据库邮件,但与此同时,我想知道是否有一种更简单(临时)的方法可以通过修改当前的SQL代码来提高性能

在存储过程中,游标用于调用另一个存储过程,该存储过程对控制器类进行API调用,控制器类再调用几个其他方法,最终发送电子邮件。这些C#方法不是异步实现的。我也在忙着将这些方法更改为异步。 毫不奇怪,当发送这些电子邮件时,SQL server已经半死不活了

所以我想知道:

  • 在这种情况下,是否有替代的基于集合的方式而不是使用游标
  • 正如我所说,我将在任何情况下(因为系统的其他部分使用它)将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
    
    更新

    我已将光标移出存储的过程接口PaySlipEmails并移到存储的过程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
    

    回到Sp_SendPayslipEmail,您需要更改这两个进程,所以决定创建具有不同名称的新进程,或者只是更改它

    有两种方法:

  • Sp\u接口\u PayslipEmails
    本身中执行
    Sp\u SendPayslipEmail
    操作
  • 在这种方法中,
    SET Base Query
    将根据Sp_SendPayslipEmail正在执行的操作而有所不同

  • 使用
    表值参数
  • 创建
    用户定义表类型

    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\u Interface\u 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 
    
    在更改前了解表值参数

    这很容易

    你的进程还有很多其他的优化范围,但一次只能优化一个

    一次将发送多少邮件

    编辑1:sp_OA*对象已过时。请在服务器上配置。然后使用

    编辑2:参见
    EXEC msdb.dbo.sp\u send\u dbmail
    不能用于
    基于集合的方法
    (无循环)。Ypu必须涉及类似循环的
    ,而
    光标
    ,首选
    光标

    Sp_SendPayslipEmail
    内部使用光标远远优于在
    Sp_Interface\u paysipemail
    内部使用光标

    sp\u Interface\u PayslipEmails
    的情况下,执行时光标仍处于打开状态

    EXEC Sp_SendPayslipEmail @PortalURL,@RT,@EmailAddress,@Collection,@Message
    
    游标打开的时间要长得多。游标是消耗性的,它会消耗大量内存

    因此,在
    Sp_SendPayslipEmail
    中使用光标。使用TVP记录循环

    我们可以通过其他方式来比较游标的使用


    注意:以后不要在存储过程前面加上
    'sp\u'

    为了更清晰,我在原始帖子中添加了第二个存储过程sp\u SendPayslipEmail的代码。谢谢你的回答。我将看一看表值参数。我还笑着说“你的进程有很多优化的余地,但一次只能优化一个”。这不是我的程序,但不管怎么说,我对sql都相当无知,所以谢谢你耐心善良,一次只改进一件事。:-@Igavshne,我对这个词不太熟悉。像Sp_SendPayslipEmail这样的程序应该分开保存,当然。其他方法也可能会使用它。所以我认为你应该使用TVP。同时,我会看看如何在Sp_SendPayslipEmail中实现TVP。@Igavshne,你必须确保Sp_OACreate'Msxml2.ServerXMLHTTP.6.0'是否足够快。这个方法已经过时了。检查我的帖子,编辑1。好的,你建议我离开TVP,实现数据库邮件并使用sp_send_dbmail?