将XML数据分解为SQL Server数据库列的最佳方法

时间:2008-09-14 09:39:30

标签: sql-server xml

将XML数据分解为各种数据库列的最佳方法是什么?到目前为止,我主要使用节点和值函数,如下所示:

INSERT INTO some_table (column1, column2, column3)
SELECT
Rows.n.value('(@column1)[1]', 'varchar(20)'),
Rows.n.value('(@column2)[1]', 'nvarchar(100)'),
Rows.n.value('(@column3)[1]', 'int'),
FROM @xml.nodes('//Rows') Rows(n)

但是我发现即使是中等大小的xml数据也会变得很慢。

8 个答案:

答案 0 :(得分:48)

在遇到类似问题的同时偶然发现了这个问题,我一直在运行查询处理一个7.5MB的XML文件(大约10,000个节点)大约3.5~4个小时,然后才最终放弃。

然而,经过一番研究后,我发现使用模式键入XML并创建了一个XML索引(我将其大量插入表中),相同的查询在~0.04ms内完成。

如何提高绩效!

创建架构的代码:

IF EXISTS ( SELECT * FROM sys.xml_schema_collections where [name] = 'MyXmlSchema')
DROP XML SCHEMA COLLECTION [MyXmlSchema]
GO

DECLARE @MySchema XML
SET @MySchema = 
(
    SELECT * FROM OPENROWSET
    (
        BULK 'C:\Path\To\Schema\MySchema.xsd', SINGLE_CLOB 
    ) AS xmlData
)

CREATE XML SCHEMA COLLECTION [MyXmlSchema] AS @MySchema 
GO

使用类型化XML列创建表的代码:

CREATE TABLE [dbo].[XmlFiles] (
    [Id] [uniqueidentifier] NOT NULL,

    -- Data from CV element 
    [Data] xml(CONTENT dbo.[MyXmlSchema]) NOT NULL,

CONSTRAINT [PK_XmlFiles] PRIMARY KEY NONCLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

创建索引的代码

CREATE PRIMARY XML INDEX PXML_Data
ON [dbo].[XmlFiles] (Data)

但有几点需要注意。 SQL Server的Schema实现不支持xsd:include。这意味着如果您有一个引用其他模式的模式,则必须将所有这些复制到一个模式中并添加它。

我也会收到错误:

XQuery [dbo.XmlFiles.Data.value()]: Cannot implicitly atomize or apply 'fn:data()' to complex content elements, found type 'xs:anyType' within inferred type 'element({http://www.mynamespace.fake/schemas}:SequenceNumber,xs:anyType) ?'.

如果我尝试在我用节点功能选择的节点上方导航。 E.g。

SELECT
    ,C.value('CVElementId[1]', 'INT') AS [CVElementId]
    ,C.value('../SequenceNumber[1]', 'INT') AS [Level]
FROM 
    [dbo].[XmlFiles]
CROSS APPLY
    [Data].nodes('/CVSet/Level/CVElement') AS T(C)

发现处理此问题的最佳方法是使用OUTER APPLY实际上对XML执行“外连接”。

SELECT
    ,C.value('CVElementId[1]', 'INT') AS [CVElementId]
    ,B.value('SequenceNumber[1]', 'INT') AS [Level]
FROM 
    [dbo].[XmlFiles]
CROSS APPLY
    [Data].nodes('/CVSet/Level') AS T(B)
OUTER APPLY
    B.nodes ('CVElement') AS S(C)

希望这对某人有所帮助,因为那几乎是我的一天。

答案 1 :(得分:5)

在我的情况下,我正在运行SQL 2005 SP2(9.0)。

唯一有帮助的是添加OPTION(OPTIMIZE FOR(@your_xml_var = NULL))。 说明在以下链接中。

示例:

INSERT INTO @tbl (Tbl_ID, Name, Value, ParamData)
SELECT     1,
    tbl.cols.value('name[1]', 'nvarchar(255)'),
    tbl.cols.value('value[1]', 'nvarchar(255)'),
    tbl.cols.query('./paramdata[1]')
FROM @xml.nodes('//root') as tbl(cols) OPTION ( OPTIMIZE FOR ( @xml = NULL ) )

https://connect.microsoft.com/SQLServer/feedback/details/562092/an-insert-statement-using-xml-nodes-is-very-very-very-slow-in-sql2008-sp1

答案 2 :(得分:3)

我不确定什么是最好的方法。我使用了OPENXML构造:

INSERT INTO Test
SELECT Id, Data 
FROM OPENXML (@XmlDocument, '/Root/blah',2)
WITH (Id   int         '@ID',
      Data varchar(10) '@DATA')

为了加快速度,您可以创建XML索引。您可以专门为功能性能优化设置索引。您还可以使用类型化的xml列,它们表现更好。

答案 3 :(得分:3)

我们在这里遇到了类似的问题。我们的DBA(SP,你是那个人)看了我的代码,对语法进行了一些调整,我们得到了我们期待的速度。这很不寻常,因为我从XML中选择的速度非常快,但插入速度很慢。因此,请尝试使用此语法:

INSERT INTO some_table (column1, column2, column3)
    SELECT 
        Rows.n.value(N'(@column1/text())[1]', 'varchar(20)'), 
        Rows.n.value(N'(@column2/text())[1]', 'nvarchar(100)'), 
        Rows.n.value(N'(@column3/text())[1]', 'int')
    FROM @xml.nodes('//Rows') Rows(n) 

因此,指定text()参数似乎确实会对性能产生影响。从'我必须写错了 - 让我停止它'到大约3秒钟,我们插入了2K行。这比我们通过连接运行的原始插入语句快2倍。

答案 4 :(得分:2)

我不会声称这是“最佳”解决方案,但我为此目的编写了一个通用的SQL CLR程序 - 它采用了“表格式”Xml结构(例如FOR XML RAW返回的结构)和输出结果集。

它不需要对Xml中“表”的结构进行任何定制/知识,并且结果非常快/有效(尽管这不是设计目标)。我只是在20秒内切碎了一个25MB(无类型)的xml变量,返回了25,000行相当宽的表。

希望这有助于某人: http://architectshack.com/ClrXmlShredder.ashx

答案 5 :(得分:0)

这不是一个答案,更多是对这个问题的补充 - 我刚遇到同样的问题,我可以像评论中的edg那样给出数字。

我的测试有xml,导致插入244条记录 - 所以244个节点。

我正在重写的代码平均需要0.4秒才能运行。(10个测试运行,从.56秒扩展到.344秒)性能不是代码被重写的主要原因,但新代码需要表现同样好或更好。这个旧代码循环xml节点,调用sp为每个循环插入一次

新代码几乎只是一个sp;传递xml;撕碎它。

切换新代码的测试显示新sp平均需要3.7秒 - 几乎慢10倍。

我的查询是在此问题中发布的表单中;

INSERT INTO some_table (column1, column2, column3)
SELECT
Rows.n.value('(@column1)[1]', 'varchar(20)'),
Rows.n.value('(@column2)[1]', 'nvarchar(100)'),
Rows.n.value('(@column3)[1]', 'int'),
FROM @xml.nodes('//Rows') Rows(n)

执行计划似乎表明,对于每一列,sql server正在执行单独的“表值函数[XMLReader]”,返回所有244行,使用嵌套循环(内部联接)加入所有备份。所以在我的情况下,我正在切碎/插入大约30列,这似乎分别发生了30次。

我将不得不转储此代码,我认为任何优化都不会克服这种方法本来就很慢。我将尝试使用sp_xml_preparedocument / OPENXML方法,看看性能是否更好。如果有人从网络搜索中遇到这个问题(就像我一样),我强烈建议你在SQL Server中使用这种类型的粉碎之前做一些性能测试

答案 6 :(得分:0)

有一个XML Bulk load COM对象(.NET Example

来自MSDN

  

您可以将XML数据插入SQL   使用INSERT的服务器数据库   语句和OPENXML函数;   但是,批量加载实用程序   当你提供更好的表现   需要插入大量的XML   数据

答案 7 :(得分:0)

我目前的大型XML集(> 500个节点)解决方案是使用SQL Bulk Copy(System.Data.SqlClient.SqlBulkCopy),使用DataSet将XML加载到内存中,然后将表传递给SqlBulkCopy(定义XML架构有帮助)。

显然存在诸如不必要地使用DataSet并首先将整个文档加载到内存中的缺陷。我希望将来能够进一步实现我自己的IDataReader以绕过DataSet方法,但是目前DataSet对于这项工作来说还“足够好”。

基本上我从来没有找到解决我的原始问题的解决方案,因为这种类型的XML碎化效果很慢。由于键入的xml查询本身很慢或与事务和SQL Server日志有关,因此可能会很慢。我想类型化的xml函数从未设计用于在非平凡的节点大小上运行。

XML批量加载:我试过这个并且它 很快但是我无法让COM dll在64位环境下工作,我通常会尝试避免看起来不再受支持的COM dll。

sp_xml_preparedocument / OPENXML:我从未走过这条路,所以有兴趣了解它的表现。