Sélectionnez les valeurs du champ XML dans SQL Server 2008


112

En regardant simplement mon champ XML, mes lignes ressemblent à ceci:

<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person>

Notez que ce sont trois lignes dans mon tableau.

Je voudrais renvoyer un résultat SQL sous forme de table comme dans

Jon  | Johnson
Kathy| Carter
Bob  | Burns

Quelle requête accomplira cela?


N'y a-t-il aucun moyen d'obtenir TOUS les éléments du xml? Vous devez spécifier un par un? Cela devient très vite fastidieux. Vous pouvez faire "select * from table", il semble que vous devriez pouvoir faire "select xml. * From xml" sans avoir à spécifier chaque élément que vous voulez.
Keith Tyler le

Réponses:


157

Étant donné que le champ XML est nommé 'xmlField' ...

SELECT 
[xmlField].value('(/person//firstName/node())[1]', 'nvarchar(max)') as FirstName,
[xmlField].value('(/person//lastName/node())[1]', 'nvarchar(max)') as LastName
FROM [myTable]

16
Vous devez utiliser .nodes () et appliquer une application croisée si xmlField contient plusieurs éléments <person>.
Remus Rusanu

SQL Server 2008 R2 Express, m'a renvoyé cette erreur avec votre solution The XQuery syntax '/function()' is not supported.:; D'autre part @Remus Rusanu semble le faire :)
RMiranda

2
Bizarre. Cela a été voté 102 fois plus haut, mais cette réponse ne renvoie que les données du premier enregistrement XML. Et cela fait référence à une table [myTable] ... d'où cela vient-il?!
Mike Gledhill

J'ai essayé cela tant de fois et je ne l'ai jamais fait fonctionner. Mon XML est <BAM><Type>Electrical</Type><BaIds><a:int>7330</a:int></BaIds></BAM>, mon choix est select e.MessageData.value('(/BAM/Type)[1]', 'varchar(100)'). J'ai aussi essayé de sélection e.MessageData.value('(/BAM/Type/node())[1]', 'varchar(100)'), et '(//Type/node())[1]', '(./Type)[1]'et toute autre combinaison que je peux penser. Tout ce que je reçois, c'est NULL.
JonathanPeel

1
@MikeGledhill il renvoie les valeurs de plusieurs enregistrements XML très bien pour moi. Aussi le seul nom à la table que l'OP donne est "ma table" :)
Paul

123

Considérant que les données XML proviennent d'une table 'table' et sont stockées dans une colonne 'field': utilisez les méthodes XML , extrayez les valeurs avec xml.value(), les nœuds de projet avec xml.nodes(), utilisez CROSS APPLYpour joindre:

SELECT 
    p.value('(./firstName)[1]', 'VARCHAR(8000)') AS firstName,
    p.value('(./lastName)[1]', 'VARCHAR(8000)') AS lastName
FROM table 
    CROSS APPLY field.nodes('/person') t(p)

Vous pouvez abandonner le nodes()et cross applysi chaque champ contient exactement un élément «personne». Si le XML est une variable que vous sélectionnez FROM @variable.nodes(...)et que vous n'avez pas besoin du fichier cross apply.


1
Je me demande à quel point cette méthode est efficace et s'il existe une meilleure solution. La combinaison CROSS APPLY avec les résultats XPath semble entraîner une requête assez gourmande en ressources.
redcalx

1
@thelocster: ce n'est pas différent de l'accès aux données ordinaire. Les techniques d'amélioration des performances XML sont bien documentées. msdn.microsoft.com/en-us/library/ms345118%28SQL.90%29.aspx
Remus Rusanu

2
gardez à l'esprit que si votre XML a des espaces de noms xmlns définis, vous devrez les définir dans l'expression XQuery (XPath) ci-dessus. Voir stackoverflow.com/a/1302150/656010 pour un exemple.
Tom Wayson

Légèrement différent de ce dont j'avais besoin, mais c'était une solution parfaite à un problème que j'avais qui était plusieurs lignes avec une colonne XML - je voulais boucler les lignes et extraire les champs de données de la colonne XML et les mettre dans une instruction d'insertion. Donc 5 lignes, chacune pour 3 colonnes de données dans le champ XML = 15 insertions, parfait.
dan richardson

17

Cet article a été utile pour résoudre mon problème qui a un format XML légèrement différent ... mon XML contient une liste de clés comme l'exemple suivant et je stocke le XML dans la colonne SourceKeys dans une table nommée DeleteBatch:

<k>1</k>
<k>2</k>
<k>3</k>

Créez le tableau et remplissez-le avec des données:

CREATE TABLE dbo.DeleteBatch (
    ExecutionKey INT PRIMARY KEY,
    SourceKeys XML)

INSERT INTO dbo.DeleteBatch ( ExecutionKey, SourceKeys )
SELECT 1, 
    (CAST('<k>1</k><k>2</k><k>3</k>' AS XML))

INSERT INTO dbo.DeleteBatch ( ExecutionKey, SourceKeys )
SELECT 2, 
    (CAST('<k>100</k><k>101</k>' AS XML))

Voici mon SQL pour sélectionner les clés du XML:

SELECT ExecutionKey, p.value('.', 'int') AS [Key]
FROM dbo.DeleteBatch
    CROSS APPLY SourceKeys.nodes('/k') t(p)

Voici les résultats de la requête ...

Clé ExecutionKey
1 1
1 2
1 3
2 100
2 101

9

Cela peut répondre à votre question:

select cast(xmlField as xml) xmlField into tmp from (
select '<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>' xmlField
union select '<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>'
union select '<person><firstName>Bob</firstName><lastName>Burns</lastName></person>'
) tb

SELECT
    xmlField.value('(person/firstName)[1]', 'nvarchar(max)') as FirstName
    ,xmlField.value('(person/lastName)[1]', 'nvarchar(max)') as LastName
FROM tmp

drop table tmp

6

Blimey. C'était un fil vraiment utile à découvrir.

J'ai toujours trouvé certaines de ces suggestions déroutantes. Chaque fois que j'utilisais valueavec [1]dans la chaîne, il ne récupérait que la première valeur. Et quelques suggestions recommandées d'utilisercross apply ce qui (dans mes tests) a juste rapporté beaucoup trop de données.

Alors, voici mon exemple simple de la façon dont vous créez un xmlobjet, puis lisez ses valeurs dans une table.

DECLARE @str nvarchar(2000)

SET @str = ''
SET @str = @str + '<users>'
SET @str = @str + '  <user>'
SET @str = @str + '     <firstName>Mike</firstName>'
SET @str = @str + '     <lastName>Gledhill</lastName>'
SET @str = @str + '     <age>31</age>'
SET @str = @str + '  </user>'
SET @str = @str + '  <user>'
SET @str = @str + '     <firstName>Mark</firstName>'
SET @str = @str + '     <lastName>Stevens</lastName>'
SET @str = @str + '     <age>42</age>'
SET @str = @str + '  </user>'
SET @str = @str + '  <user>'
SET @str = @str + '     <firstName>Sarah</firstName>'
SET @str = @str + '     <lastName>Brown</lastName>'
SET @str = @str + '     <age>23</age>'
SET @str = @str + '  </user>'
SET @str = @str + '</users>'

DECLARE @xml xml
SELECT @xml = CAST(CAST(@str AS VARBINARY(MAX)) AS XML) 

--  Iterate through each of the "users\user" records in our XML
SELECT 
    x.Rec.query('./firstName').value('.', 'nvarchar(2000)') AS 'FirstName',
    x.Rec.query('./lastName').value('.', 'nvarchar(2000)') AS 'LastName',
    x.Rec.query('./age').value('.', 'int') AS 'Age'
FROM @xml.nodes('/users/user') as x(Rec)

Et voici la sortie:

entrez la description de l'image ici

C'est une syntaxe bizarre, mais avec un exemple décent, il est assez facile d'ajouter à vos propres fonctions SQL Server.

En parlant de cela, voici la bonne réponse à cette question.

En supposant que vous ayez vos données xml dans une @xmlvariable de type xml(comme illustré dans mon exemple ci-dessus), voici comment vous renverriez les trois lignes de données du xml cité dans la question:

SELECT 
    x.Rec.query('./firstName').value('.', 'nvarchar(2000)') AS 'FirstName',
    x.Rec.query('./lastName').value('.', 'nvarchar(2000)') AS 'LastName'
FROM @xml.nodes('/person') as x(Rec)

entrez la description de l'image ici


Je ne vois pas en quoi c'est la bonne réponse. L'OP demande d'interroger une colonne à partir d'une table de type XML, et dans ce cas, vous devez soit utiliser [1], l'ordinal d'index pour le forcer à renvoyer 1 ligne, soit vous devez appliquer la colonne avec nodes()pour obtenir un structure sur laquelle xpath peut être exécuté. Votre code ne se traduit pas dans ce scénario sans beaucoup de modifications. Vous utilisez une variable, pas une colonne de table. Vous abusez également de la query()fonction qui renvoie xml. par exemple, vous pourriez avoir justex.Rec.value('(./firstName)[1]', 'nvarchar(2000)') AS FirstName
Davos

3

Si vous êtes capable d'encapsuler votre XML dans un élément racine - disons alors la solution suivante est:

DECLARE @PersonsXml XML = '<persons><person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person></persons>'

SELECT  b.value('(./firstName/text())[1]','nvarchar(max)') as FirstName, b.value('(./lastName/text())[1]','nvarchar(max)') as LastName
FROM @PersonsXml.nodes('/persons/person') AS a(b)

entrez la description de l'image ici


3

MSSQL utilise les règles XPath régulières comme suit:

  • nodename Sélectionne tous les nœuds avec le nom "nodename"
  • / Sélectionne depuis le nœud racine
  • // Sélectionne les nœuds du document à partir du nœud actuel qui correspondent à la sélection, peu importe où ils se trouvent
  • . Sélectionne le nœud actuel
  • .. Sélectionne le parent du nœud actuel
  • @ Sélectionne les attributs

W3Schools


2
SELECT 
cast(xmlField as xml).value('(/person//firstName/node())[1]', 'nvarchar(max)') as FirstName,
cast(xmlField as xml).value('(/person//lastName/node())[1]', 'nvarchar(max)') as LastName
FROM [myTable]

0

/ * Cet exemple utilise une variable XML avec un schéma * /

IF EXISTS (SELECT * FROM sys.xml_schema_collections 
           WHERE name = 'OrderingAfternoonTea')
BEGIN
    DROP XML SCHEMA COLLECTION dbo.OrderingAfternoonTea 
END
GO

CREATE XML SCHEMA COLLECTION dbo.OrderingAfternoonTea AS
N'<?xml version="1.0" encoding="UTF-16" ?>
  <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     targetNamespace="http://Tfor2.com/schemas/actions/orderAfternoonTea"
     xmlns="http://Tfor2.com/schemas/actions/orderAfternoonTea"
     xmlns:TFor2="http://Tfor2.com/schemas/actions/orderAfternoonTea"
     elementFormDefault="qualified"
     version="0.10"
   > 
    <xsd:complexType name="AfternoonTeaOrderType">
       <xsd:sequence>
         <xsd:element name="potsOfTea" type="xsd:int"/>
         <xsd:element name="cakes" type="xsd:int"/>
         <xsd:element name="fruitedSconesWithCream" type="xsd:int"/>
         <xsd:element name="jams" type="xsd:string"/>
      </xsd:sequence>
      <xsd:attribute name="schemaVersion" type="xsd:long" use="required"/>
    </xsd:complexType>

    <xsd:element name="afternoonTeaOrder"
                 type="TFor2:AfternoonTeaOrderType"/>

  </xsd:schema>' ;
GO

DECLARE @potsOfTea int;
DECLARE @cakes int;
DECLARE @fruitedSconesWithCream int;
DECLARE @jams nvarchar(128);

DECLARE @RequestMsg NVARCHAR(2048);
DECLARE @RequestXml XML(dbo.OrderingAfternoonTea);

set @potsOfTea = 5;
set @cakes = 7;
set @fruitedSconesWithCream = 25;
set @jams = N'medlar jelly, quince and mulberry';

SELECT @RequestMsg = N'<?xml version="1.0" encoding="utf-16" ?>
<TFor2:afternoonTeaOrder schemaVersion="10"
    xmlns:TFor2="http://Tfor2.com/schemas/actions/orderAfternoonTea">
    <TFor2:potsOfTea>' + CAST(@potsOfTea as NVARCHAR(20)) 
        + '</TFor2:potsOfTea>
    <TFor2:cakes>' + CAST(@cakes as NVARCHAR(20)) + '</TFor2:cakes>
    <TFor2:fruitedSconesWithCream>' 
        + CAST(@fruitedSconesWithCream as NVARCHAR(20))
        + '</TFor2:fruitedSconesWithCream>
    <TFor2:jams>' + @jams + '</TFor2:jams>
</TFor2:afternoonTeaOrder>';

SELECT @RequestXml  = CAST(CAST(@RequestMsg AS VARBINARY(MAX)) AS XML) ;

with xmlnamespaces('http://Tfor2.com/schemas/actions/orderAfternoonTea'
                    as tea)
select
    cast( x.Rec.value('.[1]/@schemaVersion','nvarchar(20)') as bigint )
        as schemaVersion,
    cast( x.Rec.query('./tea:potsOfTea')
               .value('.','nvarchar(20)') as bigint ) as potsOfTea,
    cast( x.Rec.query('./tea:cakes')
               .value('.','nvarchar(20)') as bigint )  as cakes,
    cast( x.Rec.query('./tea:fruitedSconesWithCream')
               .value('.','nvarchar(20)') as bigint ) 
      as fruitedSconesWithCream,
    x.Rec.query('./tea:jams').value('.','nvarchar(50)')  as jams
from @RequestXml.nodes('/tea:afternoonTeaOrder')  as x(Rec);

select @RequestXml.query('/*')
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.