【问题标题】:How to get inserted NewSequentialId value when doing multiple inserts?进行多次插入时如何获取插入的 NewSequentialId 值?
【发布时间】:2019-05-16 02:50:41
【问题描述】:

SQL Server 是否保证将按照 INSERT 语句的 ORDER BY 子句指定的顺序为每一行调用 NewSequentialId()?

目标是在 C# 中获取一个对象列表,每个对象代表要插入到表中的一行,并以相当快的速度将它们插入到表中。

我想要做的是使用 SqlBulkCopy 将行插入到临时表中,然后将临时表中的行插入到使用 NewSequentialId() 的表中,然后以可以使用的方式检索新 ID按照与 C# 中的对象列表相同的顺序排序,以便可以将 ID 附加到 C# 中的每个对应对象。

我使用的是 SQL Server 2016,这是目标表:

CREATE TABLE dbo.MyTable
(
    Id UNIQUEIDENTIFIER NOT NULL PRIMARY KEY DEFAULT NEWSEQUENTIALID(),
    SomeNonUniqueValue NVARCHAR(50) NOT NULL
)

首先我使用 SqlBulkCopy 将行插入到这个临时表中。 RowOrder 列包含应用程序中生成的整数。 RowOrder 是我需要返回生成的 ID 的顺序。在应用程序中,RowOrder 是列表中每个 C# 对象的索引。

CREATE TABLE #MyTableStaging
(
    RowOrder INT NOT NULL,
    SomeNonUniqueValue NVARCHAR(50) NOT NULL
)

然后我运行此 SQL 以从 #MyTableStaging 中获取行,将它们插入 MyTable 并检索插入的 ID。

DECLARE @MyTableOutput TABLE
(
    Id UNIQUEIDENTIFIER NOT NULL
)

INSERT INTO dbo.MyTable (SomeNonUniqueValue)
OUTPUT Inserted.Id INTO @MyTableOutput(Id)
SELECT SomeNonUniqueValue 
FROM #MyTableStaging
ORDER BY RowOrder

SELECT Id FROM @MyTableOutput ORDER BY Id

在我所有的测试中,这都有效。但是我最近发现,OUTPUT子句中指定的表中插入行的顺序并不总是和INSERT语句中ORDER BY指定的顺序相同(我发现这个是因为这个系统的原始设计是在#MyTableStaging 中使用身份,而不是按#MyTableStaging.Id 排序,而是按身份列排序)。

我知道 SQL Server 保证标识值按照 INSERT 语句的 ORDER BY 子句中指定的顺序生成(来自https://docs.microsoft.com/en-us/sql/t-sql/statements/insert-transact-sql?view=sql-server-2017#limitations-and-restrictions):

使用带有 ORDER BY 的 SELECT 来填充行的 INSERT 查询 保证身份值的计算方式,但不保证计算的顺序 行已插入。

【问题讨论】:

    标签: sql sql-server newsequentialid


    【解决方案1】:

    最简单(也可能是最有效)的方法是直接插入目标MyTable 表,而无需中间临时表。我会使用表值参数将值表传递到您的存储过程中。

    https://docs.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine?view=sql-server-2017

    https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters


    如果你真的想使用临时表,你不能依赖OUTPUT 子句返回的行的顺序。您需要存储#MyTableStaging.RowOrder 和生成的MyTable.Id 之间的显式映射。当您在简单的INSERT 语句中使用OUTPUT 子句时,您不能将源表中的列包含到输出中。有一种解决方法。您可以使用MERGE 代替INSERTMERGE 语句的OUTPUT 子句允许来自源表的列。

    查看非常相似的问题Combine OUTPUT inserted.id with value from selected row

    MERGE 可以INSERTUPDATEDELETE 行。在我们的例子中,我们只需要INSERT1=0 始终为 false,因此始终执行 NOT MATCHED BY TARGET 部分。 一般来说,可能还有其他分支,请参阅文档。 WHEN MATCHED 通常用于UPDATEWHEN NOT MATCHED BY SOURCE 通常用于DELETE,但我们这里不需要它们。

    MERGE 的这种复杂形式等价于简单的INSERT, 但与简单的INSERT 不同,它的OUTPUT 子句允许引用我们需要的列。 它允许从源表和目标表中检索列,从而保存新旧 ID 之间的映射。

    DECLARE @MyTableOutput TABLE
    (
        OldRowOrder int NOT NULL
        ,NewID UNIQUEIDENTIFIER NOT NULL
    );
    
    
    MERGE INTO dbo.MyTable
    USING
    (
        SELECT RowOrder, SomeNonUniqueValue
        FROM #MyTableStaging
    ) AS Src
    ON 1 = 0
    WHEN NOT MATCHED BY TARGET THEN
    INSERT (SomeNonUniqueValue)
    VALUES (Src.SomeNonUniqueValue)
    OUTPUT Src.RowOrder AS OldRowOrder, inserted.ID AS NewID
    INTO @MyTableOutput(OldRowOrder, NewID)
    ;
    

    如果您的 DBA 如此害怕MERGE,您不必使用它。但是,它的效率会降低。

    只需插入所有行。

    INSERT INTO dbo.MyTable (SomeNonUniqueValue)
    SELECT SomeNonUniqueValue 
    FROM #MyTableStaging
    ;
    

    我们不关心订单。

    如果SomeNonUniqueValue 是唯一的,您可以加入此列以将RowOrder 映射到Id。由于这些值不是唯一的,因此我们需要一个额外的步骤并生成唯一的行号以进行连接。

    WITH
    CTE_Dst
    AS
    (
    
        SELECT
            Id
            ,SomeNonUniqueValue
            ,ROW_NUMBER() OVER (ORDER BY SomeNonUniqueValue) AS rn
        FROM dbo.MyTable
    )
    ,CTE_Src
    AS
    (
    
        SELECT
            RowOrder
            ,SomeNonUniqueValue
            ,ROW_NUMBER() OVER (ORDER BY SomeNonUniqueValue) AS rn
        FROM #MyTableStaging
    )
    SELECT
        CTE_Dst.Id
        ,CTE_Src.RowOrder
    FROM
        CTE_Dst
        INNER JOIN CTE_Src ON CTE_Src.rn = CTE_Dst.rn
    ;
    

    例如,如果您有三行具有相同的SomeNonUniqueValue,那么如何将这些行映射在一起并不重要,因为SomeNonUniqueValue 是相同的。

    例子:

    #MyTableStaging
    +----------+--------------------+
    | RowOrder | SomeNonUniqueValue |
    +----------+--------------------+
    |        1 | qwerty             |
    |        2 | qwerty             |
    |        3 | qwerty             |
    |        4 | asdf               |
    |        5 | asdf               |
    +----------+--------------------+
    
    MyTable
    +----+--------------------+
    | ID | SomeNonUniqueValue |
    +----+--------------------+
    | A  | qwerty             |
    | B  | qwerty             |
    | C  | qwerty             |
    | D  | asdf               |
    | E  | asdf               |
    +----+--------------------+
    

    你可以像这样映射它们:

    +----------+----+--------------------+
    | RowOrder | ID | SomeNonUniqueValue |
    +----------+----+--------------------+
    |        1 | A  | qwerty             |
    |        2 | B  | qwerty             |
    |        3 | C  | qwerty             |
    |        4 | D  | asdf               |
    |        5 | E  | asdf               |
    +----------+----+--------------------+
    

    或者,您可以像这样映射它们:

    +----------+----+--------------------+
    | RowOrder | ID | SomeNonUniqueValue |
    +----------+----+--------------------+
    |        1 | B  | qwerty             |
    |        2 | C  | qwerty             |
    |        3 | A  | qwerty             |
    |        4 | E  | asdf               |
    |        5 | D  | asdf               |
    +----------+----+--------------------+
    

    它仍然是一个有效的映射,因为qwerty 的所有三个值都是相同的。这些映射都不比另一个“更正确”。

    显然,如果您的MyTableINSERT 之前不为空,您只需选择新行。

    【讨论】:

    • 谢谢。关于使用 tvp 的想法:我不明白使用 tvp 作为源数据如何解决输出排序问题。在我看来,从 tvp 插入会产生与从临时表插入相同的排序问题。如果您只是说使用 tvp 比使用临时表更有效,我认为您可能是对的。
    • 使用 tvp 绝对是个好主意,我不使用 tvp 的原因是因为实际上我正在为数百个不同的表使用此过程并在 C# 中动态生成 SQL 文本,所以我要么必须手动管理 tvp 所需的所有 UDT 表类型,要么动态生成表类型,并且由于我不想为运行此过程的服务帐户提供足够的权限来创建 udt,我使用了一个临时表。
    • 我认为您的 MERGE 语句想法可能是可以找到解决此问题的最佳解决方案。我要等几天看看人们有什么其他解决方案,然后我会将其标记为已接受的答案。谢谢你的帮助!我对使用 MERGE 犹豫不决,因为我听说过有关它的坏消息,而且我的 DBA 实际上已经全面禁止我们使用 MERGE,但我无法想象像这样使用它会导致问题。这是我的 DBA 在几个月前实施禁令时发给我们的:mssqltips.com/sqlservertip/3074/…
    • 1) 是的,我正在考虑使用没有临时表的 TVP。 2) 我一直在生产中的 SQL Server 2008 中使用 MERGE。那篇文章中列出的错误是针对非常特殊的情况。 3)实际上,如果您的 DBA 如此害怕,您不必使用 MERGE。您可以通过加入 SomeNonUniqueValue 将 RowOrder 映射到 ID,但 OUTPUT 子句非常简单。
    猜你喜欢
    • 2011-07-24
    • 2012-06-11
    • 2019-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-13
    • 1970-01-01
    相关资源
    最近更新 更多