如何在SQL Server中模拟DataTable的“批量”插入

时间:2018-09-25 17:40:44

标签: sql-server tsql bulkinsert

我有一个存储过程,我在其中发送用户定义的类型(即表)。我已简化以使其易于阅读。

 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使用的,因此我们不必负责验证。我想模仿大容量插入,如果其中一行不能插入而其他行很好,那么正确的行仍会插入。有没有办法做到这一点?我也想对UPDATE执行此操作,如果其中一个失败,则存储过程将继续尝试更新其他过程。

我能想到的唯一选择是一次只执行一次(或者多次调用存储过程的代码中的循环,或者一次存储过程中的循环),但是我想知道性能会受到什么影响是为了解决这个问题,还是有更好的解决方案。

1 个答案:

答案 0 :(得分:2)

不确定要继续发生哪些错误,但是除非遇到各种意外错误,否则我将尽力避免演变为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

如果可能的话,我会尽量坚持进行批处理更新,并使用连接谓词消除您期望看到的大多数错误的可能性。由于担心“可能”导致系统关闭失败而更改为RBAR处理可能会浪费时间。然后,如果您遇到了一个非常讨厌的错误,您将无法恢复,并合法地使该批次失败。

RBAR

或者,如果您绝对需要成功或失败的逐行粒度,则可以尝试/捕获每个语句并使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的插入过程实际上插入轻量级,最小化键控/约束表(如队列)中。然后,每隔大约一分钟,使一次代理作业通过记录的呼叫运行,并批量对其进行操作。根本上不会改变这里的任何模式,但是它使处理异步,因此调用者不必等待,并且通过将请求分批处理,可以节省搭载SQL最佳性能的处理能力。设置为基础的操作。

另一种选择可能是执行最多的基于集合的处理(使用检查来防止违反业务规则或约束)。如果有任何失败,则可以对剩余的行进行RBAR处理。如果一切成功,那么RBAR流程将永远不会实现。

结论 有几种解决方法。除非您有充分的理由需要逐行粒度,否则我将尝试尽可能多地使用基于集合的操作。

  • 仅通过正确构建insert/update语句即可避免大多数错误。

  • 如果需要,可以将try/catch与“空” catch块一起使用,这样失败就不会停止整个处理

  • 根据您特定情况的随机性,您可能需要或需要将这两种方法混合使用