如何使用作存储过程输入的临时表上的列可选?

时间:2018-07-12 18:54:03

标签: sql sql-server sql-server-2014

我一直在努力将基于循环的电子邮件发送过程替换为基于集合的过程。目的是能够轻松地选择一个临时表,然后调用邮件发送过程,从而使我能够快速创建新的通知机制。

仅供参考,这是针对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

0 个答案:

没有答案