web-dev-qa-db-fra.com

XQuery [value ()]: 'value ()' nécessite un singleton (ou une séquence vide), opérande trouvé de type 'xdt: untypedAtomic *'

J'essaie d'insérer des lignes dans une table en utilisant une sélection de XML. Je pense que je suis proche. Où est-ce que je vais mal?

declare @xmldata xml;
set @xmldata = '<Database>
                  <PurchaseDetails>
                    <PurchaseDetail>
                      <Upc>72594206916</Upc>
                      <Quantity>77</Quantity>
                      <PurchaseDate>9/2010</PurchaseDate>
                      <PurchaseCity>Dallas</PurchaseCity>
                      <PurchaseState>TX</PurchaseState>
                    </PurchaseDetail>
                    <PurchaseDetail>
                      <Upc>72594221854</Upc>
                      <Quantity>33</Quantity>
                      <PurchaseDate>12/2013</PurchaseDate>
                      <PurchaseCity>Nashville</PurchaseCity>
                      <PurchaseState>TN</PurchaseState>
                    </PurchaseDetail>
                  </PurchaseDetails>
                </Database>'

insert into PurchaseDetails
(Upc, Quantity, PurchaseDate, PurchaseCity, PurchaseState)
select
    x.Rec.value('Upc','char(11)'),
    x.Rec.value('Quantity','int'),
    x.Rec.value('PurchaseDate','varchar(7)'),
    x.Rec.value('PurchaseCity','varchar(50)'),
    x.Rec.value('PurchaseState','char(2)')
from @xmlData.nodes('//Database/PurchaseDetails/PurchaseDetail') as x(Rec)
37
birdus

Un collègue avait déjà abordé un problème similaire. Voici ce que nous avons trouvé. PAS intuitif!

insert into PurchaseDetails
(Upc, Quantity, PurchaseDate, PurchaseCity, PurchaseState)
select
    pd.value('Upc[1]','char(11)'),
    pd.value('Quantity[1]','int'),
    pd.value('PurchaseDate[1]','varchar(7)'),
    pd.value('PurchaseCity[1]','varchar(50)'),
    pd.value('PurchaseState[1]','char(2)')
from @xmlData.nodes('//Database/PurchaseDetails') as x(Rec)
cross apply @xmlData.nodes('//Database/PurchaseDetails/PurchaseDetail') as i(pd)
45
birdus
insert into PurchaseDetails(Upc, Quantity, PurchaseDate, PurchaseCity, PurchaseState)
select T.X.value('(Upc/text())[1]', 'char(11)'),
       T.X.value('(Quantity/text())[1]', 'int'),
       T.X.value('(PurchaseDate/text())[1]', 'varchar(7)'),
       T.X.value('(PurchaseCity/text())[1]', 'varchar(50)'),
       T.X.value('(PurchaseState/text())[1]', 'char(2)')
from @xmlData.nodes('/Database/PurchaseDetails/PurchaseDetail') as T(X)
18
Mikael Eriksson

Essaye ça!
query () puis value ()
exécutez ceci dans SQL Server et 100% a fonctionné
mettez d'abord un point (.) puis la balise enfant.
La balise PurchaseDetail existe 2 fois, donc le point (.) Remplace la première et la deuxième balise.
Le point peut empêcher l'utilisation de [1] sur XQuery.
Le point représente les première et deuxième balises PurchaseDetail.

INSERT INTO PurchaseDetails(Upc, Quantity, PurchaseDate, PurchaseCity, PurchaseState)
SELECT col.query('./Upc').value('.', 'char(11)'),
    col.query('./Quantity').value('.', 'int'),
    col.query('./PurchaseDate').value('.', 'varchar(7)'),
    col.query('./PurchaseCity').value('.', 'varchar(50)'),
    col.query('./PurchaseState').value('.', 'char(2)')
FROM @xmlData.nodes('/Database/PurchaseDetails/PurchaseDetail') as ref(col)

C'est une requête plus simplifiée jusqu'à présent.
Voir si cela fonctionne

17
marion-jeff
select
    x.Rec.query('./Upc').value('.','char(11)')
    ,x.Rec.query('./Quantity').value('.','int')
    ,x.Rec.query('./PurchaseDate').value('.','varchar(7)')
    ,x.Rec.query('./PurchaseCity').value('.','varchar(50)')
    ,x.Rec.query('./PurchaseState').value('.','char(2)')
from @xmlData.nodes('/Database/PurchaseDetails/PurchaseDetail') as x(Rec)
13
user809316

Luttant avec un problème similaire, et a constaté que la réponse de @ birdus ne fonctionnait pas si vous avez des couches supplémentaires d'imbrication dans votre xml que vous référenciez dans votre XQuery, par exemple en supposant une forme XML légèrement différente, si vous aviez

T.x.value('PurchasePlace/PurchaseCity[1]','varchar(50)')

vous obtiendrez toujours l'erreur singleton. Bien que la solution de @ birdus fonctionne pour ce cas spécifique, une solution plus généralement applicable qui combine le meilleur de la solution de @ birdus et @ Mikael-Eriksson est de faire:

insert into PurchaseDetails(Upc, Quantity, PurchaseDate, PurchaseCity, PurchaseState)
select T.X.value('(Upc)[1]', 'char(11)'),
T.X.value('(Quantity)[1]', 'int'),
T.X.value('(PurchaseDate)[1]', 'varchar(7)'),
T.X.value('(PurchaseCity)[1]', 'varchar(50)'),
T.X.value('(PurchaseState)[1]', 'char(2)')
from @xmlData.nodes('/Database/PurchaseDetails/PurchaseDetail') as T(X)

L'omission de @ birdus de cette combinaison de /text(), qui est superflue, mais ajoute les parenthèses de @ Mikael-Eriksson autour du sélecteur d'élément, pour permettre plusieurs sélecteurs d'élément comme dans mon exemple modifié qui devient:

T.x.value('(PurchasePlace/PurchaseCity)[1]','varchar(50)')

La raison de cela, que certains ont posée, n'est pas que la version de @ birdus renvoie autre chose qu'un singleton dans l'un des exemples discutés ici, mais qu'il pourrait . Par Microsoft Docs :

Les étapes d'emplacement, les paramètres de fonction et les opérateurs qui nécessitent des singletons renverront une erreur si le compilateur ne peut pas déterminer si un singleton est garanti au moment de l'exécution.

6
pbz

Pour répondre à la question de pourquoi la nécessité du prédicat de position (c'est-à-dire [1]) dans le littéral de chaîne XQuery, comme indiqué par @pbz, un singleton est requis et doit donc être garanti. Pour ajouter plus de substance à la réponse de @ pbz, voir ci-dessous.

Selon Microsoft SQL Docs :

Dans l'exemple suivant, une instance XML est stockée dans une variable de type xml. La méthode value () récupère la valeur d'attribut ProductID du XML. La valeur est ensuite affectée à une variable int.

DECLARE @myDoc xml  
DECLARE @ProdID int  
SET @myDoc = '<Root>  
<ProductDescription ProductID="1" ProductName="Road Bike">  
<Features>  
  <Warranty>1 year parts and labor</Warranty>  
  <Maintenance>3 year parts and labor extended maintenance is available</Maintenance>  
</Features>  
</ProductDescription>  
</Root>'  

SET @ProdID =  @myDoc.value('(/Root/ProductDescription/@ProductID)[1]', 'int' )  
SELECT @ProdID  

La valeur 1 est renvoyée en conséquence.

Bien qu'il n'y ait qu'un seul attribut ProductID dans l'instance XML, les règles de typage statique vous obligent à spécifier explicitement que l'expression de chemin retourne un singleton . Par conséquent, le [1] est spécifié à la fin de l'expression du chemin. Pour plus d'informations sur le typage statique, voir XQuery et Static Typing .

Suivre ce lien nous conduit alors à:

Comme mentionné précédemment, l'inférence de type fréquemment infère un type plus large que ce que l'utilisateur sait sur le type des données transmises . Dans ces cas, l'utilisateur doit réécrire la requête. Certains cas typiques sont les suivants:

...

  • Le type induit une cardinalité plus élevée que ce que les données contiennent réellement. Cela se produit fréquemment, car le type de données xml peut
    contient plus d'un élément de niveau supérieur
    , et une collection de schémas XML ne peut pas contraindre cela. Afin de réduire le type statique et
    garantit qu'il y a effectivement au plus une valeur transmise, vous
    devrait utiliser le prédicat de position [1]
    . Par exemple, pour ajouter 1 à la valeur de l'attribut c de l'élément b sous le niveau supérieur a
    , vous devez écrire (/a/b/@c)[1]+1. De plus, le DOCUMENT
    Le mot-clé peut être utilisé avec une collection de schémas XML.
0
WutDuk