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进行排序。我是通过Identity列进行排序的。)
我知道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):
使用SELECT和ORDER BY填充行的INSERT查询 确保如何计算身份值,但不能保证计算顺序 插入行。
答案 0 :(得分:2)
最简单(可能是最有效)的方法是直接插入目标MyTable
表,而无需中间登台表。我将使用表值参数将值表传递到您的存储过程中。
https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters
如果您确实要使用登台表,则不能依赖OUTPUT
子句返回的行顺序。您需要在#MyTableStaging.RowOrder
和生成的MyTable.Id
之间存储显式映射。在简单的OUTPUT
语句中使用INSERT
子句时,不能将源表中的列包括在输出中。有一种解决方法。您可以使用MERGE
代替INSERT
,并且OUTPUT
语句的MERGE
子句允许源表中的列。
看到非常相似的问题Combine OUTPUT inserted.id with value from selected row
MERGE
可以INSERT
,UPDATE
和DELETE
行。在我们的情况下,我们只需要INSERT
。
1=0
始终为假,因此始终执行NOT MATCHED BY TARGET
部分。
通常,可能还有其他分支,请参阅文档。
WHEN MATCHED
通常用于UPDATE
;
WHEN NOT MATCHED BY SOURCE
通常用于DELETE
,但是我们在这里不需要它们。
这种MERGE
复杂的形式等同于简单的INSERT
,
但是与简单的INSERT
不同,它的OUTPUT
子句允许引用我们需要的列。
它允许从源表和目标表中检索列,从而节省了旧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
的所有三个值都相同。这些映射中的任何一个都不比另一个“更正确”。
很显然,如果MyTable
之前的INSERT
不为空,则只需选择新行。