【问题标题】:Is there a way to reserve generated ID on a temporary table有没有办法在临时表上保留生成的 ID
【发布时间】:2021-07-31 08:00:00
【问题描述】:

我发布了这个问题

INSERT Statement Expensive Queries on Activity Monitor

正如您将看到的那样,XML 结构具有不同的层次。

我创建了不同的表

Organisation = organisation_id (PRIMARY_KEY)
Contacts = organisation_id (FOREIGN_KEY)
Roles = organisation_id (FOREIGN_KEY)
Rels = organisation_id (FOREIGN_KEY)
Succs = organisation_id (FOREIGN_KEY)

我想要的是生成organisation_id 并以级联方式对每个表进行插入。目前,300k 的过程需要将近 2 个小时。我有 3 种方法

  1. 将 XML 转换为列表对象并以 JSON 文本形式批量发送(1000)并发送到使用 OPENJSON 的存储过程

  2. 将 XML 转换为列表对象并按批次发送 (1000) 并将批次保存为 JSON 文件,SQL Server 可以读取该文件并在存储过程中传递文件路径,然后使用 OPENROWSET 和 OPENJSON 打开 JSON 文件

  3. 将 XML 的路径发送到存储过程,然后使用 OPENROWSET 和 OPENXML。

所有进程 (1-3) 将数据插入到 FLAT 临时表中,然后迭代每一行以为每个表调用不同的 INSERT 存储过程。方法 #3 似乎因 300k 错误而失败,但适用于 4 条记录。

另一个问题是,如果我使用物理表会比使用临时表快得多吗?

-------更新------- 正如链接上所解释的,我正在做while循环。有人建议/评论对每个表进行批量插入。问题是,例如,我只能在知道组织 ID 的情况下执行此操作

    select
        organisation_id = IDENTITY( bigint ) -- IF I CAN GENERATE THE ACTUAL ORGANISATION ID
        ,name = Col.value('.','nvarchar(20)')           
        ,contact_type = c.value('(./@type)[1]','nvarchar(50)')
        ,contact_value= c.value('(./@value)[1]','nvarchar(50)')
    into
        #temporganisations
    from
        @xml.nodes('ns1:OrgRefData/Organisations/Organisation') as Orgs(Col)
     outer apply Orgs.Col.nodes('Contacts/Contact') as Cs(c)

然后当我进行批量插入时

insert into contacts
    (
      organisation_id,type,value
    )
select
   torg.organisation_id -- if this is the actual id then perfect
   ,torg.type
   ,torg.value
from #temporg torg

【问题讨论】:

  • 您的问题究竟是什么?你似乎在解释你在做什么,并且它有效?
  • 目前,300k 是在将近 2 小时内完成的。我试图找到一种方法来加快它。目前,我正在执行一个 while 循环以从具有 300k 行的临时表中获取数据,并调用 5 个存储过程在每个表上插入。我希望不是在临时表上有一个身份,而是我可以为 organization_id 保留 id,这样我就可以使用它来进行选择并在每个表上进行批量插入,而不是 while 循环。我将更新帖子以尝试解释我的意思
  • 不确定我的观察是否正确。当我批量处理时(方法 #2),while 循环的处理速度比我使用 OPENXML(方法 #3)时快一点(每秒大约 200 条记录),因为表有 300k 它可能需要很长时间时间(大约每秒 2-3 条记录)。
  • 你如何放弃所有这些并使用 C# 中的 SqlBulkCopybcp 命令行来批量插入全部内容。它可能会快一个数量级。如果您正在向表中插入,为什么要将其转换为 XML 或 JSON 并再次返回
  • @Charlieface。我只为组织信息完成了 SqlBulkCopy,而且速度很快。唯一的麻烦是我需要将组织链接到的其他表。就像我可以创建一个批量联系人,但我需要组织 ID。我认为在 api 中循环并一次调用一个存储过程比批量处理更昂贵。这就是为什么我需要转换为 JSON。请注意,原始数据是 XML。

标签: sql-server openxml openrowset open-json


【解决方案1】:

我建议您将 XML 客户端切碎,然后转而进行某种批量复制,这通常会执行得更好。

目前,您无法执行普通的bcpSqlBulkCopy,因为您还需要外键。您需要一种在批次中唯一标识Organisation 的方法,您说这很困难,因为需要的列数。

相反,您需要在客户端生成某种唯一 ID,一个递增的整数即可。然后,在将 XML 分解为 Datatables / IEnumerables / CSV 文件时,将此 ID 分配给子对象。

您有两种选择:

  • 在许多方面最简单的是不要使用OrganisationId 中的IDENTITY 而是直接插入您生成的ID。这意味着您可以利用标准的SqlBulkCopy 程序。

缺点是您失去了自动分配IDENTITY 的好处,但您可以改用仅适用于此插入的SqlBulkCopyOptions.KeepIdentity 选项,并继续使用IDENTITY 进行其他插入。您需要估计一批不会发生冲突的正确 ID。

对此的一种变体是使用 GUID,它们始终是唯一的。我真的不推荐这个选项。


  • 如果您不想这样做,那么它会变得相当复杂。

您需要为每个表定义等效的表类型。每个都有一列用于Organisation的临时主键

CREATE TYPE OrganisationType AS TABLE
    (TempOrganisationID int PRIMARY KEY,
     SomeData varchar...

将分解后的 XML 作为表值参数传递。你会拥有@Organisations@Contacts 等。

那么您将拥有如下几行的 SQL:

-- This stores the real IDs
DECLARE @OrganisationIDs TABLE
    (TempOrganisationID int PRIMARY KEY, OrganisationId int NOT NULL);

-- We need a hack to get OUTPUT to work with non-inserted columns, so we use a weird MERGE
MERGE INTO Organisation t
USING @Organisations s
  ON 1 = 0   -- never match
WHEN NOT MATCHED THEN
  INSERT (SomeData, ...)
  VALUES (s.SomeData, ...)
OUTPUT
    s.TempOrganisationID, inserted.OrganisationID
INTO @OrganisationIDs
    (TempOrganisationID, OrganisationID);

-- We now have each TempOrganisationID matched up with a real OrganisationID
-- Now we can insert the child tables

INSERT Contact
    (OrganisationID, [Type], [Value]...)
SELECT o.OrganisationID, c.[Type], c.[Value]
FROM @Contact c
JOIN @OrganisationIDs o ON o.TempOrganisationID = c.TempOrganisationID;

-- and so on for all the child tables
  • 除了将 ID 保存到表变量之外,您还可以将 OUTPUT 流式传输回客户端,并让客户端将 ID 加入子表,然后将它们作为子表的一部分再次批量复制回来。 这使 SQL 更简单,但您仍然需要 MERGE,并且您可能会大大复杂化客户端代码。

【讨论】:

    【解决方案2】:

    您可以尝试使用以下概念示例。

    SQL

    -- DDL and sample data population, start
    USE tempdb;
    GO
    
    DROP TABLE IF EXISTS #city;
    DROP TABLE IF EXISTS #state;
    
    -- parent table
    CREATE TABLE #state  (
       stateID INT IDENTITY PRIMARY KEY, 
       stateName VARCHAR(30), 
       abbr CHAR(2), 
       capital VARCHAR(30)
    );
    -- child table (1-to-many)
    CREATE TABLE #city (
       cityID INT IDENTITY, 
       stateID INT NOT NULL FOREIGN KEY REFERENCES #state(stateID), 
       city VARCHAR(30), 
       [population] INT,
       PRIMARY KEY (cityID, stateID, city)
    );
    -- mapping table to preserve IDENTITY ids
    DECLARE @idmapping TABLE (GeneratedID INT PRIMARY KEY,
        NaturalID VARCHAR(20) NOT NULL UNIQUE);
    
    DECLARE @xml XML =
    N'<root>
       <state>
          <StateName>Florida</StateName>
          <Abbr>FL</Abbr>
          <Capital>Tallahassee</Capital>
          <cities>
             <city>
                <city>Miami</city>
                <population>470194</population>
             </city>
             <city>
                <city>Orlando</city>
                <population>285713</population>
             </city>
          </cities>
       </state>
       <state>
          <StateName>Texas</StateName>
          <Abbr>TX</Abbr>
          <Capital>Austin</Capital>
          <cities>
             <city>
                <city>Houston</city>
                <population>2100263</population>
             </city>
             <city>
                <city>Dallas</city>
                <population>5560892</population>
             </city>
          </cities>
       </state>
    </root>';
    -- DDL and sample data population, end
    
    ;WITH rs AS 
    (
        SELECT stateName   = p.value('(StateName/text())[1]', 'VARCHAR(30)'),
               abbr         = p.value('(Abbr/text())[1]', 'CHAR(2)'),
               capital      = p.value('(Capital/text())[1]', 'VARCHAR(30)')
        FROM   @xml.nodes('/root/state') AS t(p)
     )
     MERGE #state AS o
     USING rs ON 1 = 0
     WHEN NOT MATCHED THEN
        INSERT(stateName, abbr, capital)  
           VALUES(rs.stateName, rs.Abbr, rs.Capital)
     OUTPUT inserted.stateID, rs.stateName 
       INTO @idmapping (GeneratedID, NaturalID);
    
    ;WITH Details AS 
    (
        SELECT NaturalID   = p.value('(StateName/text())[1]', 'VARCHAR(30)'),
               city         = c.value('(city/text())[1]', 'VARCHAR(30)'),
               [population]   = c.value('(population/text())[1]', 'INT')
        FROM   @xml.nodes('/root/state') AS A(p)   -- parent
          CROSS APPLY A.p.nodes('cities/city') AS B(c) -- child
    ) 
    INSERT #city (stateID, city, [Population])
    SELECT m.GeneratedID, d.city, d.[Population]
    FROM   Details AS d
       INNER JOIN @idmapping AS m ON d.NaturalID = m.NaturalID;
    
    -- test
    SELECT * FROM #state;
    SELECT * FROM @idmapping;
    SELECT * FROM #city;
    

    【讨论】:

    • xml 数据类型有大小限制吗?
    • 根据文档,它是 2GB
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-17
    • 1970-01-01
    • 1970-01-01
    • 2011-08-01
    相关资源
    最近更新 更多