Come eseguire una query per valori e attributi Xml dalla tabella in SQL Server?


88

Ho una tabella che contiene una Xmlcolonna:

SELECT * 
FROM Sqm

inserisci qui la descrizione dell'immagine

Un campione dei xmldati di una riga sarebbe:

<Sqm version="1.2">
  <Metrics>
    <Metric id="TransactionCleanupThread.RecordUsedTransactionShift" type="timer" unit="µs" count="1" sum="21490"   average="21490"   minValue="73701"    maxValue="73701"                               >73701</Metric>
    <Metric id="TransactionCleanupThread.RefundOldTrans"             type="timer" unit="µs" count="1" sum="184487"  average="184487"  minValue="632704"   maxValue="632704"                              >632704</Metric>
    <Metric id="Database.CreateConnection_SaveContextUserGUID"       type="timer" unit="µs" count="2" sum="7562"    average="3781"    minValue="12928"    maxValue="13006"    standardDeviation="16"     >12967</Metric>
    <Metric id="Global.CurrentUser"                                  type="timer" unit="µs" count="6" sum="4022464" average="670411"  minValue="15"       maxValue="13794345" standardDeviation="1642047">2299194</Metric>
    <Metric id="Global.CurrentUser_FetchIdentityFromDatabase"        type="timer" unit="µs" count="1" sum="4010057" average="4010057" minValue="13752614" maxValue="13752614"                            >13752614</Metric>
  </Metrics>
</Sqm>

Nel caso di questi dati, vorrei:

SqmId  id                                                   type   unit  count  sum      minValue  maxValue  standardDeviation  Value
=====  ===================================================  =====  ====  =====  ======   ========  ========  =================  ======
1      TransactionCleanupThread.RecordUsedTransactionShift  timer  µs    1      21490    73701     73701     NULL               73701
1      TransactionCleanupThread.RefundOldTrans              timer  µs    1      184487   632704    632704    NULL               632704
1      Database.CreateConnection_SaveContextUserGUID        timer  µs    2      7562     12928     13006     16                 12967
1      Global.CurrentUser                                   timer  µs    6      4022464  15        13794345  1642047            2299194
1      Global.CurrentUser_FetchIdentityFromDatabase         timer  µs    1      4010057  13752614  13752614  NULL               13752614
2      ...

Alla fine sarò in realtà si esibirà SUM(), MIN(), MAX()aggregazione. Ma per ora sto solo cercando di interrogare una colonna xml.

In pseudo-codice, proverei qualcosa di simile:

SELECT
    SqmId,
    Data.query('/Sqm/Metrics/Metric/@id') AS id,
    Data.query('/Sqm/Metrics/Metric/@type') AS type,
    Data.query('/Sqm/Metrics/Metric/@unit') AS unit,
    Data.query('/Sqm/Metrics/Metric/@sum') AS sum,
    Data.query('/Sqm/Metrics/Metric/@count') AS count,
    Data.query('/Sqm/Metrics/Metric/@minValue') AS minValue,
    Data.query('/Sqm/Metrics/Metric/@maxValue') AS maxValue,
    Data.query('/Sqm/Metrics/Metric/@standardDeviation') AS standardDeviation,
    Data.query('/Sqm/Metrics/Metric') AS value
FROM Sqm

Ma quella query SQL non funziona:

Msg 2396, livello 16, stato 1, riga 2
XQuery [Sqm.data.query ()]: l'attributo potrebbe non essere visualizzato all'esterno di un elemento

Ho cercato, ed è incredibile quanto sia scarsamente documentato o esemplificato l'interrogazione Xml. La maggior parte delle risorse invece di interrogare una tabella , interroga una variabile ; che non sto facendo. La maggior parte delle risorse utilizza solo query xml per il filtraggio e la selezione, invece di leggere i valori. La maggior parte delle risorse legge i nodi figlio hard-coded (per indice), anziché i valori effettivi.

Risorse correlate che ho letto

Aggiornamento: .value anziché .query

Ho provato a usare in modo casuale .value, al posto di .query:

SELECT
    Sqm.SqmId,
    Data.value('/Sqm/Metrics/Metric/@id', 'varchar(max)') AS id,
    Data.value('/Sqm/Metrics/Metric/@type', 'varchar(max)') AS type,
    Data.value('/Sqm/Metrics/Metric/@unit', 'varchar(max)') AS unit,
    Data.value('/Sqm/Metrics/Metric/@sum', 'varchar(max)') AS sum,
    Data.value('/Sqm/Metrics/Metric/@count', 'varchar(max)') AS count,
    Data.value('/Sqm/Metrics/Metric/@minValue', 'varchar(max)') AS minValue,
    Data.value('/Sqm/Metrics/Metric/@maxValue', 'varchar(max)') AS maxValue,
    Data.value('/Sqm/Metrics/Metric/@standardDeviation', 'varchar(max)') AS standardDeviation,
    Data.value('/Sqm/Metrics/Metric', 'varchar(max)') AS value
FROM Sqm

Ma anche questo non funziona:

Msg 2389, livello 16, stato 1, riga 3 XQuery [Sqm.data.value ()]:
"value ()" richiede un singleton (o sequenza vuota), operando trovato di tipo "xdt: untypedAtomic *"

Risposte:


113

In realtà sei vicino al tuo obiettivo, devi solo usare il metodo nodes () per dividere le tue righe e quindi ottenere i valori:

select
    s.SqmId,
    m.c.value('@id', 'varchar(max)') as id,
    m.c.value('@type', 'varchar(max)') as type,
    m.c.value('@unit', 'varchar(max)') as unit,
    m.c.value('@sum', 'varchar(max)') as [sum],
    m.c.value('@count', 'varchar(max)') as [count],
    m.c.value('@minValue', 'varchar(max)') as minValue,
    m.c.value('@maxValue', 'varchar(max)') as maxValue,
    m.c.value('.', 'nvarchar(max)') as Value,
    m.c.value('(text())[1]', 'nvarchar(max)') as Value2
from sqm as s
    outer apply s.data.nodes('Sqm/Metrics/Metric') as m(c)

sql fiddle demo


1
Come ottengo il "valore" del nodo stesso? Sembra che non ci sia modo di select m.*vedere il tavolo segreto, magico e intermedio che ha costruito. Qual è la sintassi per interrogare il valore di un elemento? ad esempio, il valore di <Metric>8675309</Metric>è "8675309"
Ian Boyd

1
@IanBoyd mi dispiace, l'ho perso, vedi aggiornato. Puoi usare '.' o testo se potrebbero esserci elementi annidati
Roman Pekar

2
Cosa rappresentano gli alias s, me cin questa query?
Ian R. O'Brien

3
@ IanR.O'Brien mè il set di risultati restituito dalla nodes()funzione, sè la sqmtabella stessa, cè una colonna con il tipo di dati xml nel nodes()
Roman Pekar

11

Ho provato a fare qualcosa di molto simile ma non ho usato i nodi. Tuttavia, la mia struttura xml è leggermente diversa.

Ce l'hai così:

<Metrics>
    <Metric id="TransactionCleanupThread.RefundOldTrans" type="timer" ...>

Se invece fosse così:

<Metrics>
    <Metric>
        <id>TransactionCleanupThread.RefundOldTrans</id>
        <type>timer</type>
        .
        .
        .

Quindi potresti semplicemente usare questa istruzione SQL.

SELECT
    Sqm.SqmId,
    Data.value('(/Sqm/Metrics/Metric/id)[1]', 'varchar(max)') as id,
    Data.value('(/Sqm/Metrics/Metric/type)[1]', 'varchar(max)') AS type,
    Data.value('(/Sqm/Metrics/Metric/unit)[1]', 'varchar(max)') AS unit,
    Data.value('(/Sqm/Metrics/Metric/sum)[1]', 'varchar(max)') AS sum,
    Data.value('(/Sqm/Metrics/Metric/count)[1]', 'varchar(max)') AS count,
    Data.value('(/Sqm/Metrics/Metric/minValue)[1]', 'varchar(max)') AS minValue,
    Data.value('(/Sqm/Metrics/Metric/maxValue)[1]', 'varchar(max)') AS maxValue,
    Data.value('(/Sqm/Metrics/Metric/stdDeviation)[1]', 'varchar(max)') AS stdDeviation,
FROM Sqm

Per me questo è molto meno confuso rispetto all'utilizzo dell'applicazione esterna o dell'applicazione incrociata.

Spero che questo aiuti qualcun altro alla ricerca di una soluzione più semplice!


1
il codice manca le parentesi di apertura. aggiungi anche /text()dopo ID ecc. per aumentare le prestazioni
Danny Rancher

Questo è il più diretto. Grazie, ha funzionato perfettamente.
SE

Come interroghiamo una tabella con una colonna di tipo XML con questo approccio? Grazie.
FMFF

10

utilizzare valueinvece di query(è necessario specificare l'indice del nodo da restituire in XQuery oltre a passare il tipo di dati sql da restituire come secondo parametro):

select
    xt.Id
    , x.m.value( '@id[1]', 'varchar(max)' ) MetricId
from
    XmlTest xt
    cross apply xt.XmlData.nodes( '/Sqm/Metrics/Metric' ) x(m)

8

Non capisco perché alcune persone suggeriscano di utilizzare cross applyo outer applyconvertire l'xml in una tabella di valori. Per me, questo ha portato indietro troppi dati.

Ecco il mio esempio di come creeresti un file xml oggetto e poi lo trasformeresti in una tabella.

(Ho aggiunto spazi nella mia stringa xml, solo per renderlo più facile da leggere.)

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


Curioso ... perché il cast annidato Varbinary(max)prima del cast xml per favore?
EvilDr

Come interroghiamo una tabella con una colonna di tipo XML con questo approccio? Grazie.
FMFF
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.