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

问题描述 投票:0回答:1

我有一个存储过程,我发送一个用户定义的类型,这是一个表。我已经简化了以便于阅读。

 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做这个,如果一个失败,存储过程将继续尝试更新其他的。

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

sql-server tsql bulkinsert
1个回答
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处理因为您担心“可能”会导致系统关闭失败,这可能是浪费时间。然后,如果你遇到一个非常讨厌的错误,你无法恢复,合法地失败批处理。

橡胶

或者,如果您绝对需要逐行的成功或失败粒度,您可以尝试/捕获每个语句并让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,以便故障不会停止整个处理
  • 根据您的具体情况的随机几率和结束,您可能希望或需要混合这两种方法
© www.soinside.com 2019 - 2024. All rights reserved.