GMa*_*lla 11 xml sql-server stored-procedures t-sql upsert
我正在尝试使用一组值更新表。数组中的每一项都包含与 SQL Server 数据库表中的行匹配的信息。如果该行已存在于表中,我们将使用给定数组中的信息更新该行。否则,我们在表中插入一个新行。我已经基本上描述了 upsert。
现在,我试图在采用 XML 参数的存储过程中实现这一点。我使用 XML 而不是表值参数的原因是,执行后者时,我必须在 SQL 中创建自定义类型并将此类型与存储过程相关联。如果我以后更改了存储过程或数据库架构中的某些内容,则必须重做存储过程和自定义类型。我想避免这种情况。此外,TVP 相对于 XML 的优势对我的情况没有用,因为我的数据数组大小永远不会超过 1000。这意味着我不能使用这里提出的解决方案:How to insert multiple records using XML in SQL server 2008
此外,这里的类似讨论(UPSERT - 是否有更好的替代 MERGE 或 @@rowcount?)与我所问的不同,因为我试图将多行插入到表中。
我希望我可以简单地使用以下一组查询来更新 xml 中的值。但这行不通。当输入是单行时,这种方法应该有效。
begin tran
update table with (serializable) set select * from xml_param
where key = @key
if @@rowcount = 0
begin
insert table (key, ...) values (@key,..)
end
commit tran
Run Code Online (Sandbox Code Playgroud)
下一个替代方法是使用详尽的 IF EXISTS 或其以下形式的变体之一。但是,我以次优效率为由拒绝了这一点:
IF (SELECT COUNT ... ) > 0
UPDATE
ELSE
INSERT
Run Code Online (Sandbox Code Playgroud)
下一个选项是使用 Merge 语句,如下所述:http : //www.databasejournal.com/features/mssql/using-the-merge-statement-to-perform-an-upsert.html。但是,然后我在这里阅读了有关合并查询的问题:http : //www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/。出于这个原因,我试图避免合并。
所以,现在我的问题是:在 SQL Server 2008 存储过程中是否有其他选择或更好的方法来使用 XML 参数实现多个 upsert?
请注意,XML 参数中的数据可能包含一些由于比当前记录更旧而不应进行 UPSERT 的记录。ModifiedDateXML 和目标表中都有一个字段需要进行比较,以确定是否应该更新或丢弃记录。
Sol*_*zky 13
源是 XML 还是 TVP 并没有太大的区别。整体操作本质上是:
您按该顺序执行此操作,因为如果您先 INSERT,则所有行都存在以获取 UPDATE,并且您将对刚刚插入的任何行进行重复工作。
除此之外,还有不同的方法可以实现这一点,也可以通过各种方法来调整一些额外的效率。
让我们从最低限度开始。由于提取 XML 可能是此操作中成本较高的部分之一(如果不是最昂贵的),我们不想这样做两次(因为我们有两个操作要执行)。因此,我们创建一个临时表并将 XML 中的数据提取到其中:
CREATE TABLE #TempImport
(
Field1 DataType1,
Field2 DataType2,
...
);
INSERT INTO #TempImport (Field1, Field2, ...)
SELECT tab.col.value('XQueryForField1', 'DataType') AS [Field1],
tab.col.value('XQueryForField2', 'DataType') AS [Field2],
...
FROM @XmlInputParam.nodes('XQuery') tab(col);
Run Code Online (Sandbox Code Playgroud)
从那里我们执行 UPDATE 然后执行 INSERT:
UPDATE tab
SET tab.Field1 = tmp.Field1,
tab.Field2 = tmp.Field2,
...
FROM [SchemaName].[TableName] tab
INNER JOIN #TempImport tmp
ON tmp.IDField = tab.IDField
... -- more fields if PK or alternate key is composite
INSERT INTO [SchemaName].[TableName]
(Field1, Field2, ...)
SELECT tmp.Field1, tmp.Field2, ...
FROM #TempImport tmp
WHERE NOT EXISTS (
SELECT *
FROM [SchemaName].[TableName] tab
WHERE tab.IDField = tmp.IDField
... -- more fields if PK or alternate key is composite
);
Run Code Online (Sandbox Code Playgroud)
现在我们已经完成了基本操作,我们可以做一些事情来优化:
捕获 @@ROWCOUNT 插入临时表并与更新的 @@ROWCOUNT 进行比较。如果它们相同,那么我们可以跳过 INSERT
捕获通过 OUTPUT 子句更新的 ID 值并从临时表中删除这些值。然后 INSERT 不需要WHERE NOT EXISTS(...)
如果在应输入数据的任何行不进行同步(即,既不插入也不更新),那么这些记录应该做UPDATE之前去除
CREATE TABLE #TempImport
(
Field1 DataType1,
Field2 DataType2,
...
);
DECLARE @ImportRows INT;
DECLARE @UpdatedIDs TABLE ([IDField] INT NOT NULL);
BEGIN TRY
INSERT INTO #TempImport (Field1, Field2, ...)
SELECT tab.col.value('XQueryForField1', 'DataType') AS [Field1],
tab.col.value('XQueryForField2', 'DataType') AS [Field2],
...
FROM @XmlInputParam.nodes('XQuery') tab(col);
SET @ImportRows = @@ROWCOUNT;
IF (@ImportRows = 0)
BEGIN
RAISERROR('Seriously?', 16, 1); -- no rows to import
END;
-- optional: test to see if it helps or hurts
-- ALTER TABLE #TempImport
-- ADD CONSTRAINT [PK_#TempImport]
-- PRIMARY KEY CLUSTERED (PKField ASC)
-- WITH FILLFACTOR = 100;
-- optional: remove any records that should not be synced
DELETE tmp
FROM #TempImport tmp
INNER JOIN [SchemaName].[TableName] tab
ON tab.IDField = tmp.IDField
... -- more fields if PK or alternate key is composite
WHERE tmp.ModifiedDate < tab.ModifiedDate;
BEGIN TRAN;
UPDATE tab
SET tab.Field1 = tmp.Field1,
tab.Field2 = tmp.Field2,
...
OUTPUT INSERTED.IDField
INTO @UpdatedIDs ([IDField]) -- capture IDs that are updated
FROM [SchemaName].[TableName] tab
INNER JOIN #TempImport tmp
ON tmp.IDField = tab.IDField
... -- more fields if PK or alternate key is composite
IF (@@ROWCOUNT < @ImportRows) -- if all rows were updates then skip, else insert remaining
BEGIN
-- get rid of rows that were updates, leaving only the ones to insert
DELETE tmp
FROM #TempImport tmp
INNER JOIN @UpdatedIDs del
ON del.[IDField] = tmp.[IDField];
-- OR, rather than the DELETE, maybe add a column to #TempImport for:
-- [IsUpdate] BIT NOT NULL DEFAULT (0)
-- Then UPDATE #TempImport SET [IsUpdate] = 1 JOIN @UpdatedIDs ON [IDField]
-- Then, in below INSERT, add: WHERE [IsUpdate] = 0
INSERT INTO [SchemaName].[TableName]
(Field1, Field2, ...)
SELECT tmp.Field1, tmp.Field2, ...
FROM #TempImport tmp
END;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK;
END;
-- THROW; -- if using SQL 2012 or newer, use this and remove the following 3 lines
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR(@ErrorMessage, 16, 1);
RETURN;
END CATCH;
Run Code Online (Sandbox Code Playgroud)
我在导入/ETL 上多次使用过这个模型,它们要么有超过 1000 行,要么是 500 行,总共有 2 万行——超过一百万行。但是,我尚未测试从临时表中删除更新行与仅更新 [IsUpdate] 字段之间的性能差异。
请注意关于使用 XML over TVP 的决定,因为一次最多导入 1000 行(在问题中提到):
如果这被多次调用,那么很可能在 TVP 中的微小性能提升可能不值得额外的维护成本(需要在更改用户定义的表类型、应用程序代码更改等之前删除 proc) . 但是如果你导入 400 万行,一次发送 1000 行,那就是 4000 次执行(还有 400 万行 XML 解析,不管它如何分解),即使只执行几次也会有微小的性能差异加起来有明显的差异。
话虽如此,除了将 SELECT FROM @XmlInputParam 替换为 SELECT FROM @TVP 之外,我所描述的方法不会改变。由于 TVP 是只读的,您将无法从中删除。我想您可以简单地将 a 添加WHERE NOT EXISTS(SELECT * FROM @UpdateIDs ids WHERE ids.IDField = tmp.IDField)到最终的 SELECT(与 INSERT 绑定)而不是简单的WHERE IsUpdate = 0. 如果您以@UpdateIDs这种方式使用table 变量,那么您甚至可以不将传入的行转储到临时表中。
| 归档时间: |
|
| 查看次数: |
12060 次 |
| 最近记录: |