2009-05-22 6 views
92

Il suffit de regarder mon champ XML, mes lignes ressemblent à ceci:Sélectionner les valeurs champ XML dans SQL Server 2008

<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 ces trois lignes dans ma table.

Je voudrais retourner un résultat SQL comme une table comme dans

Jon | Johnson 
Kathy| Carter 
Bob | Burns 

Quelle requête y parvenir?

Répondre

134

É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 croix applicable si XMLField contient plus d'un éléments. –

+0

SQL Server 2008 R2 Express, m'a renvoyé cette erreur avec votre solution: 'La syntaxe XQuery '/ function()' n'est pas supportée. D'autre part @Remus Rusanu semble le faire :) – RMiranda

+0

@RemusRusanu c'était la clé pour moi !!! Merci! – andrew

103

Considérant que les données XML provient d'une table « table » et est stockée dans une colonne « champ »: utilisez le XML methods, extrait valeurs avec xml.value(), nœuds de projet avec xml.nodes(), utilisez CROSS APPLY rejoindre:

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 le fossé nodes() et cross apply si chaque champ contient exactement un élément « personne ». Si le code XML est une variable, sélectionnez FROM @variable.nodes(...) et vous n'avez pas besoin de cross apply.

+1

Je me demande à quel point cette méthode est efficace et s'il y a une meilleure façon de procéder. Le résultat de l'opération CROSS APPLY avec les résultats XPath semble donner lieu à une requête 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. http://msdn.microsoft.com/en-us/library/ms345118%28SQL.90%29.aspx –

+2

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

15

Ce message a été utile pour résoudre mon problème qui a un peu différent format XML ... 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 la table et le remplir 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) 

est ici les résultats de la requête ...

 
ExecutionKey Key 
1 1 
1 2 
1 3 
2 100 
2 101 
8

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 
1
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] 
3

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

J'ai toujours trouvé certaines de ces suggestions confuses. Chaque fois que j'utilisais value avec [1] dans la chaîne, il ne récupérait que la première valeur. Et certaines suggestions recommandées en utilisant cross apply qui (dans mes tests) vient de ramener beaucoup trop de données. Donc, voici mon exemple simple de la façon dont vous créez un objet xml, 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:

enter image description here

Il est la 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 correcte répondre à cette question.

En supposant que votre vos données XML dans une variable @xml de type xml (comme l'a démontré dans mon exemple ci-dessus), voici comment vous reviendriez les trois lignes de données à partir 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) 

enter image description here

+0

Je ne vois pas comment c'est la bonne réponse. L'OP demande d'interroger une colonne d'une table de type XML, et dans ce cas, vous devez utiliser '[1]', l'index ordinal pour le forcer à retourner une ligne, ou vous devez appliquer le colonne avec 'nodes()' pour obtenir une structure qui peut avoir xpath exécuté contre elle. Votre code ne traduit pas ce scénario sans beaucoup de modifications. Vous utilisez une variable, pas une colonne de table. Vous utilisez également la fonction 'query()' qui renvoie xml. par exemple. vous pourriez avoir juste 'x.Rec.value ('(./ firstName) [1]', 'nvarchar (2000)') AS Prénom ' – Davos

2

Si vous êtes en mesure d'envelopper votre XML dans un élément racine - dire alors ce qui suit est votre solution:

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) 

enter image description here

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('/*') 
Questions connexes