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