有没有比这更快的方法来从T-SQL中的XML节点提取数据?

时间:2018-09-18 17:06:23

标签: sql sql-server xml tsql

我目前正在尝试在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)

但是,我使用的XML表包含150列和200,000行以上。目前,在10,000行上执行此SQL大约需要142秒,因此,这对于处理包含大量行的XML表是完全不合适的。

有人可以建议一种加快此过程的方法吗?

4 个答案:

答案 0 :(得分:3)

查询大量列时,在SQL Server中使用node()/ value()分解XML会导致性能问题。有一个嵌套循环联接,其中每一列都调用xml函数。

三列查询计划:

enter image description here

5列的查询计划:

enter image description here

试想一下,如果超过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;

对我来说,使用node()/ value()时使用150列和1000行大约需要14秒钟,而使用OPENXML则需要3秒钟。

Vote for a change.

用于测试的代码;

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());

答案 1 :(得分:0)

您的选择取决于您对服务器的控制程度以及您愿意并能够进行的准备工作。

如果您有能力在调用过程之前清理数据(例如,运行可执行文件)...

您可以将数据反序列化为实体,然后使用您选择的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

如果我有其他想法,我将编辑这篇文章。

答案 2 :(得分:0)

SQL-Server处理XML的速度非常快,但是您没有告诉我们最重要的事情:@x来自哪里?

在SQL Server中,XML并不是作为字符串存储,而是作为 physical 表中的层次结构树存储。如果您以字符串为基础获取此XML,并将其分配给类型为XML的变量,则引擎将必须解析整个批次并将其所有内容传输到内部结构中。其余的应该很快。

乍看之下,有两个地方可以对其进行一些调整:

  • FROM @x.nodes('//Table/row') T(c)
    //将使用深层搜索,如果下面嵌套了另一个<row>,引擎将查看每个<Table>。宁可使用FROM @x.nodes('/Table/row') T(c)

  • 并使用'nvarchar(50)'代替'varchar(50)'。 XML在内部将其字符串存储为NVARCHAR。您可以避免所有这些强制转换...

如果您具有SQL Server 2016+,并且可以控制发件人,则可以尝试JSON。在一次性动作中,这样做会更好,因为它无法在内部结构中传输数据,然后才能使用它。

答案 3 :(得分:0)

我真的很喜欢Mikael Eriksson的答案并投票,但它有一个方面:

他的测试生成909 KB XML文档,包含1000行150列。 而sp_xml_preparedocument在他的情况下仅花费226毫秒(这确实非常快),但是...

我尝试将其应用于521 MB的XML文档。 它包含2045156行,其中包含11个不同的列,所有行均读取为nvarchar(255)

当我通过*选择所有11列时:

  • 通过.value()选择*耗时297秒
  • 通过openxml选择*总共花费了231秒: (sp_xml_preparedocument花了107秒,从openxml中选择*花了123秒)

openxml在这种情况下效果更好!

当我仅选择2列时:

  • 通过.value()选择2列耗时57秒
  • 通过openxml选择2列总共花费了189秒: (sp_xml_preparedocument-86秒,从openxml中选择*-103秒)

.value()在这种情况下效果更好!

因此,看起来哪种方法更快实际上取决于您从xml查询的xml大小,行数和列数!