有没有办法在临时 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 种方法
将 XML 转换为列表对象并按批次 (1000) 作为 JSON 文本发送并发送到使用 OPENJSON[=16 的存储过程=]
将 XML 转换为列表对象并按批次发送 (1000) 并将批次保存为 JSON SQL 服务器可以读取和传递的文件存储过程上的文件路径,然后使用 OPENROWSET 和 OPENJSON
打开 JSON 文件
将 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 客户端,然后切换到做某种批量复制,这样通常性能会好得多。
目前,您不能进行正常的bcp
或SqlBulkCopy
,因为您还需要外键。您需要一种在批次中唯一标识 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
,并且您冒着使客户端代码显着复杂化的风险。
我发布了这个问题
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 种方法
将 XML 转换为列表对象并按批次 (1000) 作为 JSON 文本发送并发送到使用 OPENJSON[=16 的存储过程=]
将 XML 转换为列表对象并按批次发送 (1000) 并将批次保存为 JSON SQL 服务器可以读取和传递的文件存储过程上的文件路径,然后使用 OPENROWSET 和 OPENJSON
打开 JSON 文件将 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 客户端,然后切换到做某种批量复制,这样通常性能会好得多。
目前,您不能进行正常的bcp
或SqlBulkCopy
,因为您还需要外键。您需要一种在批次中唯一标识 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
,并且您冒着使客户端代码显着复杂化的风险。