有没有办法在临时 table 上保留生成的 ID

Is there a way to reserve generated ID on a temporary table

我发布了这个问题

INSERT Statement Expensive Queries on Activity Monitor

如您所见,XML 结构具有不同的级别。

我创建了不同的 tables

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 并以级联方式在每个 table 上插入。目前,这个过程需要将近 2 个小时才能达到 300k。我有 3 种方法

  1. 将 XML 转换为列表对象并按批次 (1000) 作为 JSON 文本发送并发送到使用 OPENJSON[=16 的存储过程=]

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

    打开 JSON 文件
  3. 将 XML 的路径发送到存储过程,然后使用 OPENROWSET 和 OPENXML.

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

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

--------更新------ 正如 link 中所解释的那样,我正在执行 while 循环。有人建议/评论对每个 table 进行批量插入。问题是,例如,如果我知道 organisation_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

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

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 客户端,然后切换到做某种批量复制,这样通常性能会好得多。

目前,您不能进行正常的bcpSqlBulkCopy,因为您还需要外键。您需要一种在批次中唯一标识 Organisation 的方法,并且您说这很困难,因为它需要的列数。

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

你有两个选择:

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

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

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


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

您需要为每个 table 定义等效的 Table 类型。每个都有一列用于 Organisation

的临时主键
CREATE TYPE OrganisationType AS TABLE
    (TempOrganisationID int PRIMARY KEY,
     SomeData varchar...

将切碎的 XML 作为 Table 值参数传递。您将有 @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 保存到 table 变量,而是将 OUTPUT 流回客户端,并让客户端将 ID 加入到子 table 中,然后将它们作为子 tables.
    的一部分再次复制回来 这使得 SQL 更简单,但是您仍然需要 MERGE,并且您冒着使客户端代码显着复杂化的风险。