T-SQL 将 XML 动态导入表 (SQL Server 2014)

Joe*_*Joe 5 xml sql-server

我有一个具有这种结构的 XML 响应,但有大约 150 个不同的节点:

<?xml version="1.0" encoding="UTF-8"?>
<Orders>
    <Order>
        <OrderID>334</OrderID>
        <AmountPaid currencyID="EUR">17.10</AmountPaid>
        <UserID>marc58</UserID>
        <ShippingAddress>
            <Name>Marc Juppé</Name>
            <Address>Rue</Address>
            <City>Paris</City>
            <StateOrProvince></StateOrProvince>
            <Country>FR</Country>
            <Phone>333333333</Phone>
            <PostalCode>22222</PostalCode>
         </ShippingAddress>
         <ShippingCosts>4.50</ShippingCosts>
         <Items>
            <Item>
               <Details>
                    <ItemID>3664</ItemID>
                    <Store>47</Store>
                    <Title>MCPU DDA010</Title>
                    <SKU>mmx</SKU>
                </Details>
                <Quantity>1</Quantity>
                <Price currencyID="EUR">6.2</Price>
            </Item>
            <Item>
               <Details>
                    <ItemID>3665</ItemID>
                    <Store>45</Store>
                    <Title>MCPU DFZ42</Title>
                    <SKU>mmy</SKU>
                </Details>
                <Quantity>2</Quantity>
                <Price currencyID="EUR">3.2</Price>
            </Item>
        </Items>
    </Order>
</Orders>
Run Code Online (Sandbox Code Playgroud)

我需要将此信息存储到 3 个不同的表中,对于Item表,我需要为每个不同的表创建记录<Item>,但还要插入订单节点的详细信息;像这样:

|ItemID|Store|Title |SKU|Quantity|Price|OrderID|AmountPaid|UserID|ShippingCost|
|3664  |   47|DDA010|mmx|       1|  6.2|    334|     17.10|marc58|        4.50|
|3665  |   45|DFZ42 |mmy|       2|  3.2|    334|     17.10|marc58|        4.50|
Run Code Online (Sandbox Code Playgroud)

为了在不同的表中“自动”写入所需的信息,我在社区的大力帮助下构建了这个查询:

Set @T1='Orders'
Set @F1='OrderID'
Set @V=''

    SELECT 
        @C= IIF (CHARINDEX('['+T.X.value('local-name(.)', 'nvarchar(100)')+']',@C)=0, CONCAT( ISNULL(@C + ',','') , QUOTENAME(T.X.value('local-name(.)', 'nvarchar(100)'))), @C),
        @D= IIF (CHARINDEX('['+T.X.value('local-name(.)', 'nvarchar(100)')+']',@CP)=0, CONCAT( ISNULL(@D + ',N','') , '''',  T.X.value(N'text()[1]', 'nvarchar(max)'),''''), @D),
        @U= IIF (CHARINDEX('['+T.X.value('local-name(.)', 'nvarchar(100)')+']',@CP)=0, CONCAT( ISNULL(@U + ',','') , QUOTENAME( T.X.value('local-name(.)', 'nvarchar(100)')) ,'=', '''',T.X.value(N'text()[1]', 'nvarchar(max)'),''''), @U),
        @V= IIF(T.X.value('local-name(.)', 'nvarchar(100)') =@F2, T.X.value('text()[1]', 'nvarchar(100)'), @V), 
    FROM @XML.nodes('//*[count(child::*) = 0]') AS T(X)
    WHERE  T.X.value(N'local-name(.)', 'nvarchar(500)') 
    IN (select name from db1.sys.columns where [object_id]=OBJECT_ID(@T1)and is_identity=0)

    SELECT @C = STUFF(@C, 1, 1, '');
    SELECT @D = STUFF(@D, 1, 1, '');
    SELECT @U = STUFF(@U, 1, 1, '');

    SET @S=N'IF NOT EXISTS (SELECT 1 FROM '+@T1+' WHERE '+@F1+' = '''+@V+''') 
             INSERT INTO '+@T1+' ('+@C+') VALUES ('+@D+''') 
             ELSE UPDATE '+@T1+' SET '+@U+''' WHERE '+@F2+'='''+@V+''''

    Print @S

    EXEC sp_executesql .....

Set @T1="Users"

......
Run Code Online (Sandbox Code Playgroud)

好的,最好在这里使用循环

这个查询虽然肯定可以改进和优化,但到目前为止一直运行良好,因为只有 1 个 Item Node,但是现在,随着更多的 Item Node,它只返回第一个。

我试图修改 FROM 子句试图引用项目集合,但没有成功,但我认为即使我成功迭代项目节点,我也不知道如何获取订单节点的详细信息,即父节点项目节点...

你能提出一个解决方案吗?

谢谢

For*_*est 7

坏消息:脚本中的local-name(.)plus@XML.nodes('//*[count(child::*) = 0]')方法完全扁平化了 XML 文档,如果文档中有多个 X-per-Y 结构,则需要重新设计。如果需要动态 SQL,请提供示例以更轻松地测试该方面。

另一种方法可能是手动构建您需要的查询。如果您的基本问题是包含父信息,那么您可以修改下面的演示 SELECT 查询。

NB Mikael Eriksson 的回答有一个改进的查询。请参考。)

关键思想:

.是上下文节点,并..为您提供父节点

.nodes是属于查询的 FROM 部分的另一种 XML 函数,通常与 CROSS APPLY 一起出现。它为每个匹配返回一个指针,并且允许处理多行。在这里阅读更多。

.query().value 是让 SQL Server 知道 value 方法只有一条数据可以使用的几种方法之一(修复“需要单例”错误)

DECLARE @XML xml = 
'<Orders>
    <Order>
        <OrderID>334</OrderID>
        <AmountPaid currencyID="EUR">17.10</AmountPaid>
        <UserID>marc58</UserID>
        <ShippingAddress>
            <Name>Marc Juppé</Name>
            <Address>Rue</Address>
            <City>Paris</City>
            <StateOrProvince></StateOrProvince>
            <Country>FR</Country>
            <Phone>333333333</Phone>
            <PostalCode>22222</PostalCode>
         </ShippingAddress>
         <ShippingCosts>4.50</ShippingCosts>
         <Items>
            <Item>
               <Details>
                    <ItemID>3664</ItemID>
                    <Store>47</Store>
                    <Title>MCPU DDA010</Title>
                    <SKU>mmx</SKU>
                </Details>
                <Quantity>1</Quantity>
                <Price currencyID="EUR">6.2</Price>
            </Item>
            <Item>
               <Details>
                    <ItemID>3665</ItemID>
                    <Store>45</Store>
                    <Title>MCPU DFZ42</Title>
                    <SKU>mmy</SKU>
                </Details>
                <Quantity>2</Quantity>
                <Price currencyID="EUR">3.2</Price>
            </Item>
        </Items>
    </Order>
</Orders>'

SELECT 
    x.value('./ItemID[1]','int') AS ItemID,
    x.value('./Store[1]','int') AS Store,
    x.value('./Title[1]','nvarchar(100)') AS Title,
    x.value('./SKU[1]','nvarchar(100)') AS SKU,
    x.value('../Quantity[1]','int') AS Qty,
    x.value('../Price[1]','decimal(11,2)') AS Price,
    x.query('//OrderID[1]').value('.','int') AS OrderID,
    x.query('//AmountPaid[1]').value('.','decimal(11,2)') AS AmountPaid,
    x.query('//UserID[1]').value('.','nvarchar(100)') AS UserID,
    x.query('//ShippingCosts[1]').value('.','decimal(11,2)') AS ShippingCosts
FROM @XML.nodes('//Item/Details') i(x)
Run Code Online (Sandbox Code Playgroud)


Mik*_*son 4

您从 Forrest 得到的查询可以稍微改进一下。

在 SQL Server 的 xQuery 中使用父轴几乎总是一个非常糟糕的主意。您可以通过Orders/Order先切碎 on ,然后使用交叉应用来切碎 on 来避免这种情况Items/Item

使用也不query('').value('.')是一个好主意。value()最好使用 来确保您只从函数中获取一个值[1]

为了提高性能,一件额外的事情是text()value().

select I.X.value('(Details/ItemID/text())[1]', 'int') as ItemID,
       I.X.value('(Details/Store/text())[1]', 'int') as Store,
       I.X.value('(Details/Title/text())[1]', 'nvarchar(100)') as Title,
       I.X.value('(Details/SKU/text())[1]', 'nvarchar(100)') as SKU,
       I.X.value('(Quantity/text())[1]', 'int') as Quantity,
       I.X.value('(Price/text())[1]', 'decimal(11,2)') as Quantity,
       O.X.value('(OrderID/text())[1]', 'int') as OrderID,
       O.X.value('(AmountPaid/text())[1]', 'decimal(11,2)') as AmountPaid,
       O.X.value('(UserID/text())[1]', 'nvarchar(100)') as UserID,
       O.X.value('(ShippingCosts/text())[1]', 'decimal(11,2)') as ShippingCosts
from @XML.nodes('/Orders/Order') as O(X)
  cross apply O.X.nodes('Items/Item') as I(X);
Run Code Online (Sandbox Code Playgroud)