[英]Is there a faster way than this to extract data from XML nodes in T-SQL?
我目前正在嘗試在T-SQL中創建一個存儲過程,它以XML表作為輸入,然后將數據插入到臨時表中。
我使用的XML具有以下格式:
<Table>
<row MyFirstColumn="foo" MySecondColumn="bar" ... />
</Table>
我用於將此XML數據插入臨時表的SQL具有以下格式:
INSERT INTO
#TempTable
SELECT
T.c.value('@MyFirstColumn', 'varchar(50)')
,T.c.value('@MySecondColumn', 'varchar(50)')
,...
FROM
@x.nodes('//Table/row') T(c)
但是,我使用包含150列和超過200,000行的XML表來執行此操作。 目前,在10,000行上執行此SQL需要大約142秒,因此這對於處理包含大量行的XML表是完全不合適的。
有誰能建議加快這個過程的方法?
在查詢大量列時,在SQL Server中使用nodes()/ value()粉碎XML會出現性能問題。 有一個嵌套循環連接,每個列都調用xml函數。
查詢計划有3列:
查詢計划有5列:
想象一下超過150列的情況會是什么樣子。
另一個選擇是使用OPENXML 。 它與許多列沒有相同的問題。
您的查詢看起來像這樣:
declare @H int;
declare @X xml;
exec sys.sp_xml_preparedocument @H output,
@X;
select C1,
C2,
C3
from
openxml(@H, 'Table/row', 0)
with (
C1 int,
C2 int,
C3 int
);
exec sys.sp_xml_removedocument @H;
對我來說,使用150列和1000行使用nodes()/ value()花費大約14秒,使用OPENXML花費3秒。
用於測試的代碼;
drop table T;
go
declare @C int = 150;
declare @S nvarchar(max);
declare @X xml;
declare @N int = 1000;
declare @D datetime;
set @S = 'create table T('+
stuff((
select top(@C) ', '+N'C'+cast(row_number() over(order by 1/0) as nvarchar(3)) + N' int'
from sys.columns
for xml path('')
), 1, 2, '') + ')'
exec sp_executesql @S;
set @S = 'insert into T select top(@N) '+
stuff((
select top(@C) ',1'
from sys.columns as c1
for xml path('')
), 1, 1, '') + ' from sys.columns as c1, sys.columns as c2';
exec sp_executesql @S, N'@N int', @N;
set @X = (
select *
from dbo.T
for xml raw, root('Table')
);
set @S = 'select '+
stuff((
select top(@C) ', '+N'T.X.value(''@C'+cast(row_number() over(order by 1/0) as nvarchar(3)) + N''', ''int'')'
from sys.columns
for xml path('')
), 1, 2, '') + ' from @X.nodes(''Table/row'') as T(X)'
set @D = getdate();
exec sp_executesql @S, N'@X xml', @X;
select datediff(second, @D, getdate());
set @S = 'declare @H int;
exec sp_xml_preparedocument @H output, @X;
select *
from openxml(@H, ''Table/row'', 0)
with (' +
stuff((
select top(@C) ', C'+cast(row_number() over(order by 1/0) as nvarchar(3))+ ' int'
from sys.columns
for xml path('')
), 1, 2, '') + ');
exec sys.sp_xml_removedocument @H';
set @D = getdate();
exec sp_executesql @S, N'@X xml', @X
select datediff(second, @D, getdate());
您的選擇取決於您對服務器的控制程度以及您願意和能夠做的准備。
如果您能夠在調用過程之前清理數據(例如,運行可執行文件)...
您可以將數據反序列化為實體,並使用您選擇的ORM工具(nHibernate,EntityFramework等)來存儲實體。
您可以將XML解析為批量導入程序可以處理的對象,將其存儲到文件中,並使用sql的批量導入功能。 https://docs.microsoft.com/en-us/sql/t-sql/statements/bulk-insert-transact-sql?view=sql-server-2017
如果您能夠在服務器上使用自定義功能,則可以使用CLR用戶定義的函數來執行此工作,而不是在單獨的可執行文件中運行它。 https://docs.microsoft.com/en-us/sql/relational-databases/clr-integration-database-objects-user-defined-functions/clr-user-defined-functions?view=sql-server-2017
如果我想到其他任何事情,我會編輯這篇文章。
SQL-Server處理XML的速度非常快,但是你並沒有告訴我們最重要的事情: @x
來自哪里?
在SQL-Server中,XML不會像您看到的那樣存儲,而是存儲在物理表中的分層組織樹中。 如果您在字符串基礎上獲得此XML並將其分配給XML
類型的變量,則引擎將必須解析整個批次並將其所有內容傳輸到內部結構中。 剩下的應該是相當快的。
第一眼看到有兩個地方可以調整一下:
FROM @x.nodes('//Table/row') T(c)
//
將使用深度搜索 ,如果下面嵌套了另一個<Table>
,引擎將查看每個<row>
。 而是使用FROM @x.nodes('/Table/row') T(c)
。
並使用'nvarchar(50)'
而不是'varchar(50)'
。 內部XML將其字符串存儲為NVARCHAR
。 你可以避免所有這些演員......
如果您擁有SQL-Server 2016+並且您可以控制發件人,則可以嘗試使用JSON
。 這在一次性操作中更好,因為在它可以使用之前,它不會在內部結構中傳輸數據。
我非常喜歡並投票支持Mikael Eriksson的回答,但有一個方面是:
他的測試生成了909 KB XML文檔,包含1000行,150列。 並且sp_xml_preparedocument在他的情況下只需要226毫秒(這真的很快),但......
我嘗試將它應用於我的XML文檔,即521 MB。 它包含2045156行,包含11個不同的列,所有行都讀取為nvarchar(255)
當我通過*選擇所有11列時:
openxml在這種情況下效果更好!
當我只選擇2列時:
在這種情況下,.value()效果更好!
所以看起來哪個方法更快實際上取決於你從xml查詢的xml大小,行數和列數!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.