我一直在努力将基于循环的电子邮件发送过程替换为基于集合的过程。目的是能够轻松地选择一个临时表,然后调用邮件发送过程,从而使我能够快速创建新的通知机制。
仅供参考,这是针对PowerCampus(学生信息系统)的。它仅针对少量事件提供有限的内置电子邮件通知。供应商的代码使用动态SQL调用一个sproc并获取新的MESSAGEID
键,然后为每个接收者调用另一个sproc,输入MESSAGEID
作为参数之一。
除了内置的电子邮件通知外,该产品还具有一个灵活的表,称为“计划的操作”(ACTIONSCHEDULE
),该表具有自动放置操作的WYSIWG触发器。然后可以针对这些操作和标记为已完成的操作运行Outlook邮件合并。我的目标是让SQL作业代替发送电子邮件,然后如果成功发送电子邮件,则将操作标记为完成。
我有一个可行的解决方案,但我确实希望我的某些列是可选的,以使编写新的通知作业更加容易。这是我的存储过程。无效的部分记为--BEGIN part that doesn't work!
和--END part that doesn't work!
。
似乎无法添加一列,然后按名称在同一存储过程中选择该列。但是,SELECT * FROM #Messages
可以使用并包括新列。为什么一个起作用而另一个不起作用?在忽略调用者可能输入的任何额外列的同时,如何使某些列为可选?
在理解我无法更改供应商的数据库结构的同时,也可以随意推荐一种完全不同的方法。
-- =============================================
-- Description: Sends an email via the MessageQueue table and related tables. Set-based instead of using a loop. Adds MySchool header, footer, and optionally body styling.
-- Requires #Messages table to already exist with the columns specified below.
-- #Messages will be de-duplicated. Messages where a [to] email can't be found will be deleted.
-- The output is a similarly constructed table of messages sent along with the MESSAGEIDs.
-- Depends on MySchool custom functions Get_MySchoolEmail and Get_AltEmail.
--
-- TODO: Replace XACT_ABORT with TRY/CATCH.
-- Add validation of PEOPLE_CODE_ID's.
-- Find a way to make optional columns actually optionally.
--
-- Usage: Select some emails into #Messages table as shown above. Then EXEC [dbo].[MySchool_SP_SendEmails].
-- People Code Id's can be used instead of actual email addresses.
-- Future: Columns marked with 'O' are optional columns that do not need to exist.
/*
CREATE TABLE #Messages (
[from] NVARCHAR(255) NOT NULL --an email address
O ,[fromId] NVARCHAR(10) NULL --sender People Code Id
O ,[to] NVARCHAR(255) NULL --an email address. If NULL, will be automatically supplied from toId.
,[toId] NVARCHAR(10) NOT NULL --recipient People Code Id
,[toTypeFlag] SMALLINT NOT NULL --Flag 0/1 to control which email to select for [to]. 0 = Campus address, 1 = Alternate address.
O ,[cc] NVARCHAR(255) NULL --an email address. If NULL, will be automatically supplied from ccId.
O ,[ccId] NVARCHAR(10) NULL --cc recipient People Code Id
O ,[bcc] NVARCHAR(255) NULL --an email address. If NULL, will be automatically supplied from bccId.
O ,[bccId] NVARCHAR(10) NULL --bcc recipient People Code Id
,[subject] NVARCHAR(255) NOT NULL --email subject line
,[body] NVARCHAR(MAX) NOT NULL --email body (HTML ok)
,[formatBodyFlag] SMALLINT NOT NULL --Flag 0/1 to indicate whether body should be wrapped with styling
O ,[uniqueKey] NVARCHAR(255) NULL UNIQUE --Can be used by caller to compare input and output rows. Suggest using [ACTIONSCHEDULE].[UNIQUE_KEY] for this purpose.
);
INSERT INTO #Messages
VALUES
('selfservice@MySchool.edu'
,NULL
,NULL
,'P000123456'
,0
,'someone@gmail.com'
,NULL
,NULL
,NULL
,'Test Email 1'
,'This is a test of the stored procedure [dbo].[MySchool_SP_SendEmails].'
,1)
,('selfservice@MySchool.edu'
,NULL
,'someone@MySchool.edu'
,'P000123456'
,0
,NULL
,'P000234567'
,NULL
,NULL
,'Test Email 2'
,'This is a test of the stored procedure [dbo].[MySchool_SP_SendEmails].'
,1);
EXEC [dbo].[MySchool_SP_SendEmails]
*/
-- =============================================
ALTER PROCEDURE [dbo].[MySchool_SP_SendEmails]
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; --Stop on all errors.
DECLARE @Today datetime = dbo.fnMakeDate(getdate())
DECLARE @Now datetime = dbo.fnMakeTime(getdate())
CREATE TABLE #MessagesStrict (
[msKey] INT IDENTITY PRIMARY KEY --Identity key used internally by the procedure
,[from] NVARCHAR(255) NOT NULL --an email address
,[fromId] NVARCHAR(10) NULL --sender People Code Id
,[to] NVARCHAR(255) NULL --an email address. If NULL, will be automatically supplied from toId.
,[toId] NVARCHAR(10) NOT NULL --recipient People Code Id
,[toTypeFlag] SMALLINT NOT NULL --Flag 0/1 to control which email to select for [to]. 0 = Campus address, 1 = Alternate address.
,[cc] NVARCHAR(255) NULL --an email address. If NULL, will be automatically supplied from ccId.
,[ccId] NVARCHAR(10) NULL --cc recipient People Code Id
,[bcc] NVARCHAR(255) NULL --an email address. If NULL, will be automatically supplied from bccId.
,[bccId] NVARCHAR(10) NULL --bcc recipient People Code Id
,[subject] NVARCHAR(255) NOT NULL --email subject line
,[body] NVARCHAR(MAX) NOT NULL --email body (HTML ok)
,[formatBodyFlag] SMALLINT NOT NULL --Flag 0/1 to indicate whether body should be wrapped with styling
,[uniqueKey] NVARCHAR(255) NULL UNIQUE --Can be used by caller to compare input and output rows. Suggest using [ACTIONSCHEDULE].[UNIQUE_KEY] for this purpose.
,[MESSAGEID] INT NULL --Will be used later to link MESSAGEQUE to MESSAGERECIPIENTS
);
--BEGIN part that doesn't work!
--Add optional columns
IF 'fromId' NOT IN (SELECT [name] FROM tempdb.sys.columns WHERE [object_id] = OBJECT_ID('tempdb..#Messages'))
BEGIN
ALTER TABLE #Messages
ADD [fromId] NVARCHAR(255) NULL
END;
IF 'to' NOT IN (SELECT [name] FROM tempdb.sys.columns WHERE [object_id] = OBJECT_ID('tempdb..#Messages'))
BEGIN
ALTER TABLE #Messages
ADD [to] NVARCHAR(255) NULL
END;
IF 'cc' NOT IN (SELECT [name] FROM tempdb.sys.columns WHERE [object_id] = OBJECT_ID('tempdb..#Messages'))
BEGIN
ALTER TABLE #Messages
ADD [cc] NVARCHAR(255) NULL
END;
IF 'ccId' NOT IN (SELECT [name] FROM tempdb.sys.columns WHERE [object_id] = OBJECT_ID('tempdb..#Messages'))
BEGIN
ALTER TABLE #Messages
ADD [ccId] NVARCHAR(255) NULL
END;
IF 'bcc' NOT IN (SELECT [name] FROM tempdb.sys.columns WHERE [object_id] = OBJECT_ID('tempdb..#Messages'))
BEGIN
ALTER TABLE #Messages
ADD [bcc] NVARCHAR(255) NULL
END;
IF 'bccId' NOT IN (SELECT [name] FROM tempdb.sys.columns WHERE [object_id] = OBJECT_ID('tempdb..#Messages'))
BEGIN
ALTER TABLE #Messages
ADD [bccId] NVARCHAR(255) NULL
END;
IF 'uniqueKey' NOT IN (SELECT [name] FROM tempdb.sys.columns WHERE [object_id] = OBJECT_ID('tempdb..#Messages'))
BEGIN
ALTER TABLE #Messages
ADD [uniqueKey] NVARCHAR(255) NULL UNIQUE
END;
--END part that doesn't work!
--Basic validation. Wasteful, but we're not expecting to run massive mailings.
INSERT INTO #MessagesStrict (
[from]
,[fromId]
,[to]
,[toId]
,[toTypeFlag]
,[cc]
,[ccId]
,[bcc]
,[bccId]
,[subject]
,[body]
,[formatBodyFlag]
,[uniqueKey]
)
SELECT DISTINCT
[from]
,[fromId]
,[to]
,[toId]
,[toTypeFlag]
,[cc]
,[ccId]
,[bcc]
,[bccId]
,[subject]
,[body]
,[formatBodyFlag]
,[uniqueKey]
FROM #Messages; --Input table
BEGIN TRAN MySchool_SP_SendEmails
--Supply [to] campus addresses
UPDATE #MessagesStrict
SET [to] = [dbo].[Get_MySchoolEmail]([toId])
WHERE [to] IS NULL
AND [toTypeFlag] = 0;
--Supply [to] secondary addresses
UPDATE #MessagesStrict
SET [to] = [dbo].[Get_AltEmail]([toId])
WHERE [to] IS NULL
AND [toTypeFlag] = 1;
--Delete messages where correct address couldn't be found
DELETE FROM #MessagesStrict
WHERE [to] IS NULL;
--Supply [cc]
UPDATE #MessagesStrict
SET [cc] = [dbo].[fnGetPrimaryEmail]([ccId])
WHERE [cc] IS NULL
AND [ccId] IS NOT NULL;
--Supply [bcc]
UPDATE #MessagesStrict
SET [bcc] = [dbo].[fnGetPrimaryEmail]([bccId])
WHERE [bcc] IS NULL
AND [bccId] IS NOT NULL;
--Format bodies with inner styling (formatBodyFlag)
UPDATE #MessagesStrict
SET [body] = '<div style=''a bunch of styling''>' + [body] +'</div>'
WHERE [formatBodyFlag] = 1;
--Declare a table to hold new MESSAGEID's
DECLARE @MessageIds TABLE
([msKey] INT NOT NULL
,[MESSAGEID] INT NOT NULL);
--Insert into MESSAGEQUEUE. A MERGE statement is used to allow returning columns we didn't actually insert, specifically #MessagesStrict.sortKey.
--MySchool header and footer are also added.
MERGE INTO [MESSAGEQUEUE]
USING #MessagesStrict M
ON 1 = 0 --Will never be true, so no UPDATEs will ever happen
WHEN NOT MATCHED
THEN
INSERT (
[MESSAGESOURCE]
,[SUBJECT]
,[BODY]
,[BODYFORMAT]
,[TIMESENT]
,[SENDER_EMAIL]
,[PEOPLECODEID]
,[HIDERECIPIENTS]
,[SENDTIME]
,[STATUS]
,[CREATE_OPID]
,[CREATE_TERMINAL]
,[CREATE_DATE]
,[CREATE_TIME]
,[REVISION_OPID]
,[REVISION_TERMINAL]
,[REVISION_DATE]
,[REVISION_TIME]
)
VALUES (
''
,M.[subject]
,'a bunch of styling' + M.[body] + 'a bunch more styling'
,1
,NULL
,M.[from]
,M.[fromId]
,0
,@Now
,'N'
,'SYSADMIN'
,'0001'
,@Today
,@Now
,'SYSADMIN'
,'0001'
,@Today
,@Now
)
OUTPUT M.[msKey], inserted.[MESSAGEID]
INTO @MessageIds;
--Associate MESSAGEIDs with #MessagesStrict
UPDATE #MessagesStrict
SET #MessagesStrict.[MESSAGEID] = MIDS.[MESSAGEID]
FROM @MessageIds MIDS
WHERE #MessagesStrict.[msKey] = MIDS.[msKey];
ALTER TABLE #MessagesStrict ALTER COLUMN [MESSAGEID] INT NOT NULL; --A little extra validation
--Insert [to] recipients into MESSAGERECIPIENTS
INSERT INTO [MESSAGERECIPIENTS] (
[MESSAGEID]
,[RECIPIENTTYPE]
,[EMAILADDRESS]
,[PEOPLECODEID]
,[CREATE_OPID]
,[CREATE_TERMINAL]
,[CREATE_DATE]
,[CREATE_TIME]
,[REVISION_OPID]
,[REVISION_TERMINAL]
,[REVISION_DATE]
,[REVISION_TIME]
)
SELECT
M.[MESSAGEID]
,'to'
,M.[to]
,M.[toId]
,'SYSADMIN'
,'0001'
,@Today
,@Now
,'SYSADMIN'
,'0001'
,@Today
,@Now
FROM #MessagesStrict M
--Insert [cc] recipients into MESSAGERECIPIENTS
INSERT INTO [MESSAGERECIPIENTS] (
[MESSAGEID]
,[RECIPIENTTYPE]
,[EMAILADDRESS]
,[PEOPLECODEID]
,[CREATE_OPID]
,[CREATE_TERMINAL]
,[CREATE_DATE]
,[CREATE_TIME]
,[REVISION_OPID]
,[REVISION_TERMINAL]
,[REVISION_DATE]
,[REVISION_TIME]
)
SELECT
M.[MESSAGEID]
,'cc'
,M.[cc]
,M.[ccId]
,'SYSADMIN'
,'0001'
,@Today
,@Now
,'SYSADMIN'
,'0001'
,@Today
,@Now
FROM #MessagesStrict M
WHERE M.[cc] IS NOT NULL
--Insert [to] recipients into MESSAGERECIPIENTS
INSERT INTO [MESSAGERECIPIENTS] (
[MESSAGEID]
,[RECIPIENTTYPE]
,[EMAILADDRESS]
,[PEOPLECODEID]
,[CREATE_OPID]
,[CREATE_TERMINAL]
,[CREATE_DATE]
,[CREATE_TIME]
,[REVISION_OPID]
,[REVISION_TERMINAL]
,[REVISION_DATE]
,[REVISION_TIME]
)
SELECT
M.[MESSAGEID]
,'bcc'
,M.[bcc]
,M.[bccId]
,'SYSADMIN'
,'0001'
,@Today
,@Now
,'SYSADMIN'
,'0001'
,@Today
,@Now
FROM #MessagesStrict M
WHERE M.[bcc] IS NOT NULL
--Select the results as output
SELECT
[from]
,[fromId]
,[to]
,[toId]
,[toTypeFlag]
,[cc]
,[ccId]
,[bcc]
,[bccId]
,[subject]
,[body]
,[formatBodyFlag]
,[uniqueKey]
,[MESSAGEID]
FROM #MessagesStrict;
COMMIT TRAN MySchool_SP_SendEmails
END
如果有帮助,这是调用SQL的工作。注意所有无用的SELECT NULL AS
行。我想使这项工作尽可能容易/简单地编写代码。
SET XACT_ABORT ON; --Stop on all errors.
BEGIN TRAN SendCancEmails
--Select actions to process into temp table
SELECT
'selfservice@MySchool.edu' [from]
,NULL [fromId]
,NULL [to]
,[PEOPLE_ORG_CODE_ID] [toId]
,NULL [cc]
,NULL [ccId]
,NULL [bcc]
,NULL [bccId]
,0 [toTypeFlag]
,AC.[ACTION_NAME] [subject]
,AC.[NOTE] [body]
,CASE WHEN TM.[EVENT_ID] IS NOT NULL THEN TM.[EVENT_ID] + '/' + CST.[LONG_DESC] + '/' + TM.[SECTION] + ' (' + [EVENT_LONG_NAME] + ')'
WHEN PATINDEX('%20__/%/%/%', ACS.[ACTION_NAME]) > 0
THEN SUBSTRING(ACS.[ACTION_NAME], CHARINDEX('/',ACS.[ACTION_NAME], CHARINDEX('/',ACS.[ACTION_NAME], 21) + 1) + 1, 100) + '...' --Try to extract section name from action name (it's often unfortunately truncated)
ELSE '(unknown)'
END AS [sectionCode]
,1 [formatBodyFlag]
,[UNIQUE_KEY] [uniqueKey]
INTO #Messages
FROM [ACTIONSCHEDULE] ACS
INNER JOIN [ACTION] AC
ON AC.[ACTION_ID] = ACS.[ACTION_ID]
LEFT JOIN [TRANSCRIPTMARKETING] TM
ON TM.[PEOPLE_CODE_ID] = ACS.[PEOPLE_ORG_CODE_ID]
AND TM.[DROP_REASON] = 'CANCEL'
AND TM.[REVISION_DATE] = ACS.[REQUEST_DATE]
AND TM.[REVISION_TIME] BETWEEN ([dbo].[fnMakeTime](ACS.[REQUEST_TIME]) - '00:00:05') AND ([dbo].[fnMakeTime](ACS.[REQUEST_TIME]) + '00:00:05') --Allow 10 seconds drift
LEFT JOIN [SECTIONS] S
ON S.[ACADEMIC_YEAR] = TM.ACADEMIC_YEAR
AND S.ACADEMIC_TERM = TM.ACADEMIC_TERM
AND S.ACADEMIC_SESSION = TM.ACADEMIC_SESSION
AND S.EVENT_ID = TM.EVENT_ID
AND S.EVENT_SUB_TYPE = TM.EVENT_SUB_TYPE
AND S.SECTION = TM.SECTION
LEFT JOIN CODE_EVENTSUBTYPE CST
ON CST.CODE_VALUE = TM.EVENT_SUB_TYPE
WHERE ACS.[ACTION_ID] = 'CANCSECT'
AND [COMPLETED] <> 'Y'
AND [CANCELED] <> 'Y'
AND [WAIVED] <> 'Y';
--Supply student names
UPDATE M
SET [body] = REPLACE([body], '##FIRST_NAME##', [FIRST_NAME])
FROM #Messages AS M
INNER JOIN PEOPLE AS P
ON P.[PEOPLE_CODE_ID] = M.[toId];
--Supply section names
UPDATE #Messages
SET [body] = REPLACE([body], '##SECTION##', [sectionCode]);
ALTER TABLE #Messages DROP COLUMN [sectionCode];
--Prepare table to capture results
CREATE TABLE #MessagesResults (
[from] NVARCHAR(255) NOT NULL
,[fromId] NVARCHAR(10) NULL
,[to] NVARCHAR(255) NULL
,[toId] NVARCHAR(10) NOT NULL
,[toTypeFlag] SMALLINT NOT NULL
,[cc] NVARCHAR(255) NULL
,[ccId] NVARCHAR(10) NULL
,[bcc] NVARCHAR(255) NULL
,[bccId] NVARCHAR(10) NULL
,[subject] NVARCHAR(255) NOT NULL
,[body] NVARCHAR(MAX) NOT NULL
,[formatBodyFlag] SMALLINT NOT NULL
,[uniqueKey] NVARCHAR(255) NULL UNIQUE
,[MESSAGEID] INT NULL
);
--Actually send messages and capture results
INSERT INTO #MessagesResults
EXEC [dbo].[MySchool_SP_SendEmails];
--Complete actions upon successful sending.
UPDATE ACS
SET [COMPLETED] = 'Y'
,[EXECUTION_DATE] = [dbo].[fnMakeDate](GETDATE())
,[COMPLETED_BY] = 'P000000001' --System Administrator
FROM [ACTIONSCHEDULE] AS ACS
INNER JOIN #MessagesResults AS MR
ON MR.[uniqueKey] = ACS.[UNIQUE_KEY];
DROP TABLE #Messages, #MessagesResults;
COMMIT TRAN SendCancEmails