多次插入时如何获取插入的 NewSequentialId 值?

How to get inserted NewSequentialId value when doing multiple inserts?

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

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

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

我正在使用 SQL Server 2016,这是目标 table:

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

首先,我使用 SqlBulkCopy 将行插入此临时文件 table。 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子句中指定的table中插入行的顺序并不总是与INSERT语句中ORDER BY指定的顺序一致(我发现这是因为原来该系统的设计是在#MyTableStaging 中使用身份,而不是按#MyTableStaging.Id 排序,我按身份列排序)。

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

INSERT queries that use SELECT with ORDER BY to populate rows guarantees how identity values are computed but not the order in which the rows are inserted.

最简单(也可能是最有效)的方法是直接插入目标 MyTable table,无需中间阶段 table。我会使用 table-valued-parameter 将值的 table 传递到您的存储过程中。

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


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

的列

看到非常相似的问题

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

MERGE 的复杂形式等同于简单的 INSERT, 但与简单的 INSERT 不同,它的 OUTPUT 子句允许引用我们需要的列。 它允许从源和目标检索列 tables 从而保存旧 ID 和新 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 的所有三个值都相同。这些映射都不 "more correct" 比另一个

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