使用没有明显匹配条件的 table 变量进行 MERGE

MERGE using a table variable without an obvious matching criteria

我有一个供用户填写的 InfoPath 表单。它被分配了一个唯一的报告编号,在报告中他们可以 select 零个或多个受影响的部分,每个部分都有一堆列。随着报告的处理,受影响部件的列表可能会发生变化。向 SharePoint 提交或重新提交报告会调用一个 SQL 存储过程,其参数包括列数据,分号分隔,一个 NVARCHAR(MAX) 代表一列中的多行。存储过程解析以分号分隔的 NVARCHAR 以填充 table 变量,然后将该 table 变量与将用于数据分析的主 table 合并。当用户每个报告只有一个具有给定零件号的受影响零件时,一切都很好(因为我的 MERGE 语句使用它来确定它是否需要更新现有行或创建新行),最初是acceptable 限制。粗略SQL总结:

CREATE TABLE [Report_to_Parts](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Report_ID] [nvarchar](255) NOT NULL,
    [PartNumberOrdered] [nvarchar](255) NULL,
    [OtherColumns] [nvarchar](255) NULL)

CREATE PROCEDURE [MergeReport_sp] 
    @paramReport_ID nvarchar(255),
    @paramPartNumberString nvarchar(MAX) = NULL,
    @paramOtherColumnsString nvarchar(MAX) = NULL
AS
BEGIN
    DECLARE @tempPartTable TABLE (
        [Report_ID] [nvarchar](255) NOT NULL,
        [PartNumberOrdered] [nvarchar](255) NOT NULL,
        [OtherColumns] [nvarchar](255) NULL)

    DECLARE @WorkingPartNumberString nvarchar(255),
        @WorkingOtherColumnsString nvarchar(255)

    -- Magic to parse the semicolon delimited parameters omitted
    WHILE (PartsRemainingToParse)
    BEGIN
        INSERT INTO @tempPartTable (
            [Report_ID],
            [PartNumberOrdered],
            [OtherColumns])
        VALUES (
            @paramReport_ID,
            @WorkingPartNumberString,
            @WorkingOtherColumnsString)         
    END

    MERGE [Report_to_Parts]
    USING @tempPartTable AS [Source]
    ON (
        [Report_to_Parts].[Report_ID] = @paramReport_ID AND
        [Report_to_Parts].[PartNumberOrdered] = [Source].[PartNumberOrdered])
    WHEN NOT MATCHED BY TARGET
        THEN INSERT(
            [Report_ID],
            [PartNumberOrdered],
            [OtherColumns])
        VALUES (
            @paramReport_ID,
            [Source].[PartNumberOrdered],
            [Source].[OtherColumns])
    WHEN MATCHED
        THEN UPDATE SET [OtherColumns]=[Source].[OtherColumns]
    WHEN NOT MATCHED BY SOURCE AND [Report_to_Parts].[Report_ID] = @paramReport_ID
        THEN DELETE;
END

当前工作的输入和结果,假设 [Report_ID] 为 123456 的部分在 [Report_to_Parts] 中不存在,但其他一些条目存在:

EXEC MergeReport_sp
    @paramReport_ID = N'123456',
    @paramPartNumberString = N'Part 1;abcd-efg;Part 3;'
    @paramOtherColumnsString = N'There are many;other columns;but I simplified;'

SELECT * FROM [Report_to_Parts] WHERE [Report_ID] = N'123456'

---------------------------------------------------------
| ID | Report_ID | PartNumberOrdered | OtherColumns     |
| 05 | 123456    | Part 1            | There are many   |
| 06 | 123456    | abcd-efg          | other columns    |
| 07 | 123456    | Part 3            | but I simplified |

EXEC MergeReport_sp
    @paramReport_ID = N'123456',
    @paramPartNumberString = N'Part 1;Part 3;New Part;'
    @paramOtherColumnsString = N'Updates;Work;Too;'

SELECT * FROM [Report_to_Parts] WHERE [Report_ID] = N'123456'

-----------------------------------------------------
| ID | Report_ID | PartNumberOrdered | OtherColumns |
| 05 | 123456    | Part 1            | Updates      |
| 07 | 123456    | Part 3            | Work         |
| 08 | 123456    | New Part          | Too          |

但是以下失败了,因为它试图以 PartNumberOrdered 为基础:

EXEC MergeReport_sp
    @paramReport_ID = N'123456',
    @paramPartNumberString = N'Part 1;Part 1;'
    @paramOtherColumnsString = N'Thing 1;Thing 2;'

现在我需要它能够优雅地处理多个部件共享部件号的报告。离开 InfoPath and/or SharePoint 不是一个选项,也不是删除分号解析方面。我正在考虑的选项:

  1. 存储过程的每次调用,从 [Report_to_Parts] 中删除具有匹配 [Report_ID] 的所有行并插入所有 @tempPartTable 行。这是我想要的数据分析最终结果,但我担心它效率低得可怕,并且 [ID] 会随着系统的使用而变得不必要的大。
  2. 更改 [Report_to_Parts] 使其具有 [ID] 和 [Report_ID] 的复合键,并想出一种方法让 [ID] 从 1 开始自动递增每个 [Report_ID],然后如上删除插入。
  3. 为 [ID] 添加一列到 @tempPartTable,[Report_to_Parts] SELECT 以获取此 [[=] table 中已有部件的 [ID] 42=]],根据它设置@tempPartTable [ID]s,MERGE on [ID]。
  4. 更改 [Report_to_Parts] 使其具有复合键,为 [ID] 添加一列到 @tempPartTable,该列从 1 开始自动递增,MERGE 按 [ID] 和 [Report_ID].

我在想出 3 的代码时遇到了困难,而且我当然也愿意接受其他想法。 Here's an earlier question I asked about this same system.

写这个问题给了我一些想法,我能够通过选项 4 解决这个问题。我修改了 [Report_to_Parts] 以便 [ID] 不是身份,而是一个复合主键以及[Report_ID]。然后我更新了存储过程代码(还包括解析 "magic" 以防有人偶然发现这个 post 试图解决解析问题):

ALTER PROCEDURE [MergeReport_sp]
    @paramReport_ID nvarchar(255),
    @paramPartNumberString nvarchar(MAX) = NULL,
    @paramOtherColumnsString nvarchar(MAX) = NULL
AS
BEGIN
    DECLARE @tempPartTable TABLE (
        [ID] [int] NOT NULL,
        [Report_ID] [nvarchar](255) NOT NULL,
        [PartNumberOrdered] [nvarchar](255) NOT NULL,
        [OtherColumns] [nvarchar](255) NULL)

    DECLARE @WorkingPartNumberString nvarchar(255),
        @WorkingOtherColumnsString nvarchar(255),
        @PartNumberPos int = CHARINDEX(N';', @paramPartNumberString),
        @OtherColumnsPos int = CHARINDEX(N';', @paramOtherColumnsString),
        @PartTableID int = 1

    WHILE (ISNULL(@PartNumberPos,0) > 0)
    BEGIN
        IF @OtherColumnsPos = 0 SET @OtherColumnsPos = 1

        SET @WorkingPartNumberString = NULLIF(SUBSTRING(@paramPartNumberString,1,@PartNumberPos - 1), '')
        SET @WorkingOtherColumnsString = NULLIF(SUBSTRING(@WorkingOtherColumnsString ,1,@OtherColumnsPos - 1), '')

        INSERT INTO @tempPartTable (
            [ID],
            [Report_ID],
            [PartNumberOrdered],
            [OtherColumns])
        VALUES (
            @PartTableID,
            @paramReport_ID,
            @WorkingPartNumberString,
            @WorkingOtherColumnsString)

        SET @PartTableID = @PartTableID + 1

        SET @paramPartNumberString = SUBSTRING(@paramPartNumberString, @PartNumberPos+1, LEN(@paramPartNumberString))
        SET @paramOtherColumnsString = SUBSTRING(@paramOtherColumnsString, @OtherColumnsPos +1, LEN(@paramOtherColumnsString))

        SET @PartNumberPos = CHARINDEX(N';',@paramPartNumberString)
        SET @OtherColumnsPos = CHARINDEX(N';',@paramOtherColumnsString)
    END

    MERGE [Report_to_Parts]
    USING @tempPartTable AS [Source]
    ON (
        [Report_to_Parts].[ID] = [Source].[ID] AND
        [Report_to_Parts].[Report_ID] = @paramReport_ID)
    WHEN NOT MATCHED BY TARGET
        THEN INSERT(
            [ID],
            [Report_ID],
            [PartNumberOrdered],
            [OtherColumns])
        VALUES (
            [Source].[ID],
            @paramReport_ID,
            [Source].[PartNumberOrdered],
            [Source].[OtherColumns])
    WHEN MATCHED
        THEN UPDATE SET
            [PartNumberOrdered] = [Source].[PartNumberOrdered]
            [OtherColumns]=[Source].[OtherColumns]
    WHEN NOT MATCHED BY SOURCE AND [Report_to_Parts].[Report_ID] = @paramReport_ID
        THEN DELETE;
END