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