web-dev-qa-db-ja.com

T-SQLはXMLをテーブルに動的にインポートします(SQL Server 2014)

この構造の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>

この情報を3つの異なるテーブルに保存する必要があります。Itemテーブルの場合、異なる<Item>ごとにレコードを作成する必要がありますが、Order Node詳細;このように:

|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|

必要な情報を「自動的に」さまざまなテーブルに書き込むために、私はコミュニティの大きな助けを借りてこのクエリを作成しました。

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"

......

ここでループを使用する方が良い

このクエリは確実に改善および最適化できますが、アイテムノードが1つしかなかったため、これまでうまく機能していましたが、アイテムノードが増えると、最初のノードのみが返されます。

アイテムコレクションを参照しようとするFROM句を変更しようとしましたが、成功しませんでしたが、アイテムノードの反復に成功しても、Order Node詳細、アイテムノードの親...

解決策を提案できますか?

ありがとう

5
Joe

Forrestから取得したクエリ は少し改善できます。

SQL ServerのxQueryでは、ほとんどの場合、親軸の使用は 本当に悪い考え です。最初に_Orders/Order_を細断し、次にクロスアプライを使用して_Items/Item_を細断することで、これを回避できます。

また、query('').value('.')を使用することはお勧めできません。 _[1]_を使用して、value()関数から1つの値のみを取得するようにしてください。

パフォーマンスのためのもう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);
_
4
Mikael Eriksson

悪い知らせ:スクリプト内のlocal-name(.) plus @XML.nodes('//*[count(child::*) = 0]')アプローチは、XMLドキュメントを完全にフラット化し、複数のX-per-Y構造が資料。動的SQLが必要な場合は、その側面をより簡単にテストするための例を提供してください。

別のアプローチは、必要なクエリを手動で作成することです。基本的な問題に親情報が含まれている場合は、以下のデモのSELECTクエリを変更できます。

N.B。Mikael Erikssonの回答のクエリが改善されました。それを参照してください。)

重要なアイデア:

_._はコンテキストノードであり、_.._は親を提供します

_.nodes_は、クエリのFROMセクションに属する別の種類のXML関数であり、通常、CROSS APPLYで表示されます。一致ごとにポインタを返し、複数の行を操作できるようにします。 詳細はこちら

.query().valueは、SQL Serverに値のメソッドに処理するデータが1つしかないことを知らせるためのいくつかの方法の1つです(「シングルトンが必要」エラーを修正)

_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)
_
7
Forrest

すべてのデータを含む一時的な非正規化テーブルを作成してみましょう。一部のフィールドは破棄されますが、自分で簡単に追加できます。自分の好みでグループ化の結果を本番テーブルに挿入できます。

    declare @xml varchar(max)
    declare @idoc int

    set @xml='<?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>'

    print 'prepare xml document'
    exec sp_xml_preparedocument @idoc output, @xml

    print 'create temporary table'
    CREATE TABLE #import (OrderID int, UserID varchar(500), AmountPaid numeric(18,2), AmountPaidCurrencyID char(3),  ShippingAddress_Name varchar(500), ShippingCosts numeric(18,2), ItemID int, Store int, Title varchar(500), SKU varchar(500), Quantity numeric(18,4), CurrencyID char(3), CurrencyPrice numeric(18,2))
    CREATE INDEX #ix_import ON #import (OrderID)

    print 'insert temporary table'
    insert into #import (  OrderID,  UserID,  AmountPaid,  AmountPaidCurrencyID,  ShippingAddress_Name,  ShippingCosts,  ItemID,  Store,  Title,  SKU,  Quantity,  CurrencyID,  CurrencyPrice)
    SELECT               a.OrderID,a.UserID,a.AmountPaid,a.AmountPaidCurrencyID,a.ShippingAddress_Name,a.ShippingCosts,b.ItemID,b.Store,b.Title,b.SKU,b.Quantity,b.CurrencyID,b.CurrencyPrice
    FROM        OPENXML (@idoc, '/Orders/Order',2)
                WITH    (
                         OrderID int                        'OrderID'
                        ,UserID varchar(500)                'UserID'
                        ,AmountPaidCurrencyID char(3)       'AmountPaid/@currencyID'
                        ,AmountPaid numeric(18,2)           'AmountPaid'
                        ,ShippingAddress_Name varchar(500)  'ShippingAddress/Name'
                        ,ShippingCosts numeric(18,2)        'ShippingCosts'
                        ) a

    LEFT JOIN   OPENXML (@idoc, '/Orders/Order/Items/Item',2)
                WITH    (
                         OrderID int                '../../OrderID'
                        ,ItemID int                 'Details/ItemID'
                        ,Store int                  'Details/Store'
                        ,Title varchar(500)         'Details/Title'
                        ,SKU varchar(500)           'Details/SKU'
                        ,Quantity numeric(18,4)     'Quantity'
                        ,CurrencyID char(3)         'Price/@currencyID'
                        ,CurrencyPrice numeric(18,2)'Price'
                        ) b ON a.OrderID=b.OrderID

    print 'remove xml document'
    exec sp_xml_removedocument @idoc

    -- 
    select * from #import

    print 'drop temporary table'
    DROP TABLE #import

これが結果セットです:

    OrderID UserID  AmountPaid  AmountPaidCurrencyID    ShippingAddress_Name    ShippingCosts   ItemID  Store   Title       SKU Quantity    CurrencyID  CurrencyPrice
    334     marc58  17.10       EUR                     Marc Juppe              4.50            3664    47      MCPU DDA010 mmx 1.0000      EUR         6.20
    334     marc58  17.10       EUR                     Marc Juppe              4.50            3665    45      MCPU DFZ42  mmy 2.0000      EUR         3.20
4
QuickJoe