web-dev-qa-db-fra.com

T-SQL importe dynamiquement du XML dans des tables (SQL Server 2014)

J'ai une réponse XML avec cette structure mais avec environ 150 nœuds différents:

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

J'ai besoin de stocker ces informations dans 3 tables différentes et pour la table Item, je dois créer un enregistrement pour chaque <Item>, mais en insérant également l'ordre Node détails; comme ceci:

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

Pour écrire "automatiquement" dans les différents tableaux les informations requises, j'ai construit avec la grande aide de la communauté cette requête:

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"

......

OK, mieux vaut utiliser une boucle ici

Cette requête, bien qu'elle puisse être sûrement améliorée et optimisée, a bien fonctionné jusqu'à présent, car il n'y avait qu'un seul nœud d'article, mais maintenant, avec plus de nœuds d'article, elle ne renvoie que le premier.

J'ai essayé de modifier la clause FROM en essayant de faire référence à la collection d'articles, mais sans succès, mais je pense que même si je réussis à itérer les nœuds d'élément (s), je n'ai aucune idée de comment obtenir la commande Node détails, qui sont le nœud Parent of Item ...

Pouvez-vous proposer une solution?

Merci

5
Joe

La requête que vous avez obtenue de Forrest peut être un peu améliorée.

L'utilisation de l'axe parent est presque toujours un très mauvaise idée dans xQuery dans SQL Server. Vous pouvez éviter cela en déchiquetant d'abord sur Orders/Order Puis en utilisant une croix d'application pour déchiqueter sur Items/Item.

L'utilisation de query('').value('.') n'est pas non plus une bonne idée. Mieux vaut vous assurer que vous n'obtenez qu'une seule valeur de la fonction value() en utilisant [1].

Une autre chose pour les performances est de spécifier le nœud text() dans 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

Mauvaise nouvelle: l'approche local-name(.) plus @XML.nodes('//*[count(child::*) = 0]') dans votre script aplatit complètement le document XML et devra être retravaillée si vous avez des structures multiples X-per-Y dans votre document. Si le SQL dynamique est une exigence, veuillez fournir un exemple pour tester cet aspect plus facilement.

Une autre approche peut consister à créer manuellement les requêtes dont vous avez besoin. Si votre problème fondamental inclut les informations sur les parents, vous pouvez modifier ma requête SELECT de démonstration ci-dessous.

( N.B. La réponse de Mikael Eriksson a une requête améliorée. Veuillez vous référer à cela.)

Idées clés:

. Est le nœud de contexte et .. Vous donne le parent

.nodes Est un type différent de fonction XML qui appartient à la section FROM de la requête et est généralement vu avec CROSS APPLY. Il renvoie un pointeur par correspondance et permet de travailler avec plusieurs lignes. En savoir plus ici.

.query().value est l'une des nombreuses méthodes permettant à SQL Server de savoir que la méthode value n'a qu'une seule donnée à utiliser (correction de l'erreur "nécessite un singleton")

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

Essayons de créer une table dénormalisée temporaire avec toutes vos données. Certains champs sont supprimés mais vous pouvez facilement les ajouter vous-même. Vous pourrez insérer dans vos tableaux de production un regroupement des résultats à votre goût.

    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

Voici l'ensemble de résultats:

    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