如何在SQL Server 中模拟"Bulk" Insert with DataTable
How to mimic "Bulk" Insert with DataTable in SQL Server
我有一个存储过程,我在其中发送一个用户定义的类型 table。我进行了简化以使其更易于阅读。
CREATE TYPE [dbo].[ProjectTableType] AS TABLE(
[DbId] [uniqueidentifier] NOT NULL,
[DbParentId] [uniqueidentifier] NULL,
[Description] [text] NULL
)
CREATE PROCEDURE [dbo].[udsp_ProjectDUI] (@cmd varchar(10),
@tblProjects ProjectTableType READONLY) AS BEGIN
DECLARE @myNewPKTable TABLE (myNewPK uniqueidentifier)
IF(LOWER(@cmd) = 'insert')
BEGIN
INSERT INTO
dbo.Project
(
DbId,
DbParentId,
Description)
OUTPUT INSERTED.DbId INTO @myNewPKTable
SELECT NEWID(),
DbParentId,
Description
FROM @tblProjects;
SELECT * FROM dbo.Project WHERE dbid IN (SELECT myNewPK FROM @myNewPKTable);
END
这是用于其他应用程序将使用的 DLL,因此我们不一定负责验证。我想模仿 BULK INSERT,如果其中一行无法插入但其他行没问题,正确的行仍会插入。有没有办法做到这一点?我也想为 UPDATE 做这件事,如果一个失败,存储过程将继续尝试更新其他过程。
我能想到的唯一选择是一次只做一个(要么在多次调用存储过程的代码中循环,要么在存储过程中循环),但想知道性能如何hit 就是为了这个,或者如果有更好的解决方案。
不确定您想继续处理哪些错误,但除非您运行遇到各种意外错误,否则我会尽量避免陷入 RBAR。
检查明显的违规行为
我认为主要是 PK 波动,您可以通过在插入(和更新)之前检查是否存在来避免这种波动。如果还有其他业务逻辑失败的情况,也可以在这里查看。
insert into dbo.Project
(
DbId,
DbParentId,
Description
)
output insert.DbId
into @myNewPKTable (DbId)
select
DbId = newid(),
DbParentId = s.DbParentId,
Description = s.Description
from @tblProjects s -- source
-- LOJ null check makes sure we don't violate PK
-- NOTE: I'm pretending this is an alternate key of the table.
left outer join dbo.Project t -- target
on s.dbParentId = t.dbParentId
where t.dbParentId is null
如果可能的话,我会尝试坚持使用批量更新,并使用连接谓词来消除您期望看到的大多数错误的可能性。因为担心 "might" 系统关闭失败而更改为 RBAR 处理可能是浪费时间。然后,如果你遇到了一个非常严重的错误,你无法从中恢复,合法地使批处理失败。
RBAR
或者,如果您绝对需要逐行粒度的成功或失败,您可以围绕每个语句执行 try/catch 并让 catch 块不执行任何操作(或记录某些内容)。
declare
@DBParentId int,
@Description nvarchar(1000),
@Ident int
declare c cursor local fast_forward for
select
DbParentId = s.DbParentId,
Description = s.Description
from @tblProjects
open c
fetch next from c into @DBParentId, @Description
while @@fetch_status = 0
begin
begin try
insert into dbo.Project
(
DbId,
DbParentId,
Description
)
output insert.DbId
into @myNewPKTable (DbId)
select
newid(),
@DBParentId,
@Description
end try
begin catch
-- log something if you want
print error_message()
end catch
fetch next from c into @DBParentId, @Description
end
混合
您可能会变得聪明并将事物混合在一起。一种选择可能是使面向 Web 的插入过程实际插入到一个轻量级的、最小的 keyed/constrainted table(如队列)中。然后,每隔一分钟左右,通过记录的调用有一个 Agent 作业 运行,并对其进行批量操作。不会从根本上改变这里的任何模式,但它使处理异步,因此调用者不必等待,并且通过将请求批处理在一起,您可以节省处理能力,依靠 SQL 最擅长的东西;基于集合的操作。
另一种选择可能是尽可能地进行基于集合的处理(使用检查来防止违反业务规则或约束)。如果有任何失败,您可以为剩余的行分拆一个 RBAR 进程。如果一切都成功,则 RBAR 进程永远不会被命中。
结论
有几种方法可以解决这个问题。我会尝试尽可能多地使用基于集合的操作,除非您有非常充分的理由需要逐行粒度。
只需正确构建 insert/update
语句即可避免大多数错误。
如果需要,您可以使用 try/catch
和 "empty" catch 块,这样失败就不会停止整个处理过程
根据您具体情况的随机结果,您可能希望或需要混合使用这两种方法
我有一个存储过程,我在其中发送一个用户定义的类型 table。我进行了简化以使其更易于阅读。
CREATE TYPE [dbo].[ProjectTableType] AS TABLE(
[DbId] [uniqueidentifier] NOT NULL,
[DbParentId] [uniqueidentifier] NULL,
[Description] [text] NULL
)
CREATE PROCEDURE [dbo].[udsp_ProjectDUI] (@cmd varchar(10),
@tblProjects ProjectTableType READONLY) AS BEGIN
DECLARE @myNewPKTable TABLE (myNewPK uniqueidentifier)
IF(LOWER(@cmd) = 'insert')
BEGIN
INSERT INTO
dbo.Project
(
DbId,
DbParentId,
Description)
OUTPUT INSERTED.DbId INTO @myNewPKTable
SELECT NEWID(),
DbParentId,
Description
FROM @tblProjects;
SELECT * FROM dbo.Project WHERE dbid IN (SELECT myNewPK FROM @myNewPKTable);
END
这是用于其他应用程序将使用的 DLL,因此我们不一定负责验证。我想模仿 BULK INSERT,如果其中一行无法插入但其他行没问题,正确的行仍会插入。有没有办法做到这一点?我也想为 UPDATE 做这件事,如果一个失败,存储过程将继续尝试更新其他过程。
我能想到的唯一选择是一次只做一个(要么在多次调用存储过程的代码中循环,要么在存储过程中循环),但想知道性能如何hit 就是为了这个,或者如果有更好的解决方案。
不确定您想继续处理哪些错误,但除非您运行遇到各种意外错误,否则我会尽量避免陷入 RBAR。
检查明显的违规行为
我认为主要是 PK 波动,您可以通过在插入(和更新)之前检查是否存在来避免这种波动。如果还有其他业务逻辑失败的情况,也可以在这里查看。
insert into dbo.Project
(
DbId,
DbParentId,
Description
)
output insert.DbId
into @myNewPKTable (DbId)
select
DbId = newid(),
DbParentId = s.DbParentId,
Description = s.Description
from @tblProjects s -- source
-- LOJ null check makes sure we don't violate PK
-- NOTE: I'm pretending this is an alternate key of the table.
left outer join dbo.Project t -- target
on s.dbParentId = t.dbParentId
where t.dbParentId is null
如果可能的话,我会尝试坚持使用批量更新,并使用连接谓词来消除您期望看到的大多数错误的可能性。因为担心 "might" 系统关闭失败而更改为 RBAR 处理可能是浪费时间。然后,如果你遇到了一个非常严重的错误,你无法从中恢复,合法地使批处理失败。
RBAR
或者,如果您绝对需要逐行粒度的成功或失败,您可以围绕每个语句执行 try/catch 并让 catch 块不执行任何操作(或记录某些内容)。
declare
@DBParentId int,
@Description nvarchar(1000),
@Ident int
declare c cursor local fast_forward for
select
DbParentId = s.DbParentId,
Description = s.Description
from @tblProjects
open c
fetch next from c into @DBParentId, @Description
while @@fetch_status = 0
begin
begin try
insert into dbo.Project
(
DbId,
DbParentId,
Description
)
output insert.DbId
into @myNewPKTable (DbId)
select
newid(),
@DBParentId,
@Description
end try
begin catch
-- log something if you want
print error_message()
end catch
fetch next from c into @DBParentId, @Description
end
混合 您可能会变得聪明并将事物混合在一起。一种选择可能是使面向 Web 的插入过程实际插入到一个轻量级的、最小的 keyed/constrainted table(如队列)中。然后,每隔一分钟左右,通过记录的调用有一个 Agent 作业 运行,并对其进行批量操作。不会从根本上改变这里的任何模式,但它使处理异步,因此调用者不必等待,并且通过将请求批处理在一起,您可以节省处理能力,依靠 SQL 最擅长的东西;基于集合的操作。
另一种选择可能是尽可能地进行基于集合的处理(使用检查来防止违反业务规则或约束)。如果有任何失败,您可以为剩余的行分拆一个 RBAR 进程。如果一切都成功,则 RBAR 进程永远不会被命中。
结论 有几种方法可以解决这个问题。我会尝试尽可能多地使用基于集合的操作,除非您有非常充分的理由需要逐行粒度。
只需正确构建
insert/update
语句即可避免大多数错误。如果需要,您可以使用
try/catch
和 "empty" catch 块,这样失败就不会停止整个处理过程根据您具体情况的随机结果,您可能希望或需要混合使用这两种方法