Seleziona i valori dal campo XML in SQL Server 2008


112

Guardando il mio campo XML, le mie righe hanno questo aspetto:

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

Nota che queste sono tre righe nella mia tabella.

Vorrei restituire un risultato SQL come tabella come in

Jon  | Johnson
Kathy| Carter
Bob  | Burns

Quale query eseguirà questo?


Non c'è modo di ottenere semplicemente TUTTI gli elementi nell'xml? Devi specificare uno per uno? Diventa davvero noioso in fretta. Puoi fare "seleziona * dalla tabella", sembra che dovresti essere in grado di fare "seleziona xml. * Da xml" senza dover specificare ogni singolo elemento che desideri.
Keith Tyler

Risposte:


157

Dato che il campo XML è denominato "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
È necessario utilizzare .nodes () e cross apply se xmlField contiene più di un elemento <person>.
Remus Rusanu

SQL Server 2008 R2 Express, mi ha restituito questo errore con la tua soluzione The XQuery syntax '/function()' is not supported.:; D'altra parte @Remus Rusanu sembra farlo :)
RMiranda

2
Bizzarro. Questo è stato votato per 102 volte, ma questa risposta restituisce solo i dati dal primo record XML. E si riferisce a qualche tabella [myTable] ... da dove viene?!
Mike Gledhill

L'ho provato così tante volte e non l'ho mai fatto funzionare. Il mio XML è <BAM><Type>Electrical</Type><BaIds><a:int>7330</a:int></BaIds></BAM>, la mia selezione è select e.MessageData.value('(/BAM/Type)[1]', 'varchar(100)'). Ho anche provato selezionare e.MessageData.value('(/BAM/Type/node())[1]', 'varchar(100)'), e '(//Type/node())[1]', '(./Type)[1]'e ogni altra combinazione che posso pensare. Tutto quello che ho mai ottenuto è NULL.
JonathanPeel

1
@ MikeGledhill restituisce valori da più record XML per me. Anche l'unico nome al tavolo che l'OP dà è "il mio tavolo" :)
Paul

123

Considerando che i dati XML provengono da una tabella 'tabella' e sono memorizzati in una colonna 'campo': utilizzare i metodi XML , estrarre i valori con xml.value(), i nodi del progetto con xml.nodes(), utilizzare CROSS APPLYper unire:

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)

Puoi eliminare nodes()e cross applyse ogni campo contiene esattamente un elemento "persona". Se l'XML è una variabile selezionata FROM @variable.nodes(...)e non è necessario il file cross apply.


1
Mi chiedo quanto sia efficiente questo metodo e se esista un modo migliore. La combinazione CROSS APPLY con i risultati di XPath sembra che potrebbe risultare in una query piuttosto affamata di risorse.
redcalx

1
@thelocster: questo non è diverso dal normale accesso ai dati. Le tecniche per migliorare le prestazioni XML sono ben documentate. msdn.microsoft.com/en-us/library/ms345118%28SQL.90%29.aspx
Remus Rusanu

2
tieni presente che se il tuo XML ha spazi dei nomi xmlns definiti, dovrai definirli nell'espressione XQuery (XPath) sopra. Vedi stackoverflow.com/a/1302150/656010 per un esempio.
Tom Wayson

Leggermente diverso da quello di cui avevo bisogno, ma questa era una soluzione perfetta per un problema che stavo avendo che era più righe con una colonna XML: volevo scorrere le righe ed estrarre i campi dati dall'interno della colonna XML e inserirli in una dichiarazione di inserimento. Quindi 5 righe, ciascuna per 3 colonne di dati nel campo XML = 15 inserti, perfetto.
dan richardson

17

Questo post è stato utile per risolvere il mio problema che ha un formato XML leggermente diverso ... il mio XML contiene un elenco di chiavi come il seguente esempio e memorizzo l'XML nella colonna SourceKeys in una tabella denominata DeleteBatch:

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

Crea la tabella e popolala con alcuni dati:

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

Ecco il mio SQL per selezionare le chiavi dall'XML:

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

Ecco i risultati della query ...

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

9

Questo potrebbe rispondere alla tua domanda:

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

Accidenti. Questo è stato un thread davvero utile da scoprire.

Ho ancora trovato alcuni di questi suggerimenti confusi. Ogni volta che ho usato valuecon [1]nella stringa, recuperava solo il primo valore. E alcuni suggerimenti consigliavano di utilizzare cross applyche (nei miei test) hanno riportato troppi dati.

Quindi, ecco il mio semplice esempio di come creare un xmloggetto, quindi leggere i suoi valori in una tabella.

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)

Ed ecco l'output:

inserisci qui la descrizione dell'immagine

È una sintassi bizzarra, ma con un esempio decente è abbastanza facile da aggiungere alle proprie funzioni di SQL Server.

A proposito, ecco la risposta corretta a questa domanda.

Supponendo che tu abbia i tuoi dati xml in una @xmlvariabile di tipo xml(come dimostrato nel mio esempio sopra), ecco come restituiresti le tre righe di dati dall'xml citato nella domanda:

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)

inserisci qui la descrizione dell'immagine


Non vedo come questa sia la risposta corretta. L'OP sta chiedendo di interrogare una colonna da una tabella che è di tipo XML, e in quel caso devi usare [1], l'ordinale dell'indice per costringerlo a restituire 1 riga, oppure devi applicare la colonna con nodes()per ottenere un struttura che può far funzionare xpath contro di essa. Il tuo codice non si traduce in quello scenario senza molte modifiche. Stai utilizzando una variabile, non una colonna di tabella. Stai anche esagerando con la query()funzione che restituisce xml. ad esempio, potresti avere solox.Rec.value('(./firstName)[1]', 'nvarchar(2000)') AS FirstName
Davos

3

Se sei in grado di racchiudere il tuo XML in un elemento radice, dì che la soluzione seguente è:

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)

inserisci qui la descrizione dell'immagine


3

MSSQL utilizza le regole XPath regolari come segue:

  • nome nodo Seleziona tutti i nodi con il nome "nome nodo"
  • / Seleziona dal nodo radice
  • // Seleziona i nodi nel documento dal nodo corrente che corrispondono alla selezione, indipendentemente da dove si trovino
  • . Seleziona il nodo corrente
  • .. Seleziona il genitore del nodo corrente
  • @ Seleziona gli attributi

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

/ * Questo esempio utilizza una variabile XML con uno schema * /

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('/*')
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.