Wie frage ich in SQL Server nach XML-Werten und -Attributen aus einer Tabelle?

87

Ich habe eine Tabelle, die eine XmlSpalte enthält :

SELECT * 
FROM Sqm

Geben Sie hier die Bildbeschreibung ein

Ein Beispiel für die xmlDaten einer Zeile wäre:

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

Bei diesen Daten möchte ich:

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

Am Ende werde ich tatsächlich durchführen SUM(), MIN(), MAX()Aggregation. Aber im Moment versuche ich nur, eine XML-Spalte abzufragen .

Im Pseudocode würde ich etwas versuchen wie:

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

Diese SQL-Abfrage funktioniert jedoch nicht:

Meldung 2396, Ebene 16,
Status 1, Zeile 2 XQuery [Sqm.data.query ()]: Das Attribut darf nicht außerhalb eines Elements angezeigt werden

Ich habe gejagt und es ist erstaunlich, wie schlecht dokumentiert oder beispielhaft XML-Abfragen sind. Die meisten Ressourcen fragen eine Variable ab , anstatt eine Tabelle abzufragen . was ich nicht mache. Die meisten Ressourcen verwenden XML-Abfragen nur zum Filtern und Auswählen, anstatt Werte zu lesen. Die meisten Ressourcen lesen fest codierte untergeordnete Knoten (nach Index) anstelle von tatsächlichen Werten.

Verwandte Ressourcen, die ich gelesen habe

Update: .value statt .query

Ich habe zufällig versucht .value, anstelle von .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

Das funktioniert aber auch nicht:

Nachricht 2389, Ebene 16, Status 1, Zeile 3 XQuery [Sqm.data.value ()]:
'value ()' erfordert einen Singleton (oder eine leere Sequenz), gefundener Operand vom Typ 'xdt: untypedAtomic *'

Ian Boyd
quelle

Antworten:

111

Eigentlich sind Sie Ihrem Ziel nahe. Sie müssen nur die node () -Methode verwenden, um Ihre Zeilen zu teilen und dann Werte abzurufen:

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

Roman Pekar
quelle
1
Wie erhalte ich den "Wert" des Knotens selbst? Es scheint keine Möglichkeit zu geben select m.*, den geheimen, magischen Zwischentisch zu sehen, den er konstruiert hat. Wie lautet die Syntax zum Abfragen des Werts eines Elements? zB der Wert <Metric>8675309</Metric>ist „8675309“
Ian Boyd
1
@ IanBoyd sorry, habe das verpasst, siehe aktualisiert. Sie können '.' oder Text, wenn es verschachtelte Elemente geben könnte
Roman Pekar
2
Was die Aliase s, mund cin dieser Abfrage darstellen?
Ian R. O'Brien
3
@ IanR.O'Brien mist die von der nodes()Funktion zurückgegebene Ergebnismenge , sist die sqmTabelle selbst, cist eine Spalte mit dem Datentyp xml in der von der nodes()Funktion zurückgegebenen Ergebnismenge
Roman Pekar
10

Verwenden Sie valuestatt query(muss den Index des Knotens angeben, der in der XQuery zurückgegeben werden soll, und den SQL-Datentyp übergeben, der als zweiter Parameter zurückgegeben werden soll):

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

Ich habe versucht, etwas sehr Ähnliches zu tun, aber die Knoten nicht zu verwenden. Meine XML-Struktur ist jedoch etwas anders.

Du hast es so:

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

Wenn es stattdessen so wäre:

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

Dann könnten Sie einfach diese SQL-Anweisung verwenden.

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

Für mich ist dies viel weniger verwirrend als die Verwendung der äußeren Anwendung oder der Kreuzanwendung.

Ich hoffe, das hilft jemand anderem, der nach einer einfacheren Lösung sucht!

Ryan Dorendorf
quelle
1
Der Code verfehlt die öffnenden Klammern. auch /text()nach ID usw. für Leistungssteigerung anhängen
Danny Rancher
Dies ist am einfachsten. Danke, hat perfekt funktioniert.
SE
Wie fragen wir mit diesem Ansatz eine Tabelle mit einer XML-Typspalte ab? Danke dir.
FMFF
8

Ich verstehe nicht, warum einige Leute vorschlagen , die XML in eine Wertetabelle zu verwenden cross applyoder outer applyzu konvertieren. Für mich brachte das einfach viel zu viele Daten zurück.

Hier ist mein Beispiel, wie Sie ein xmlObjekt erstellen und es dann in eine Tabelle umwandeln.

(Ich habe meiner XML-Zeichenfolge Leerzeichen hinzugefügt, um das Lesen zu erleichtern.)

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)

Und hier ist die Ausgabe:

Geben Sie hier die Bildbeschreibung ein

Mike Gledhill
quelle
Neugierig ... warum Varbinary(max)bitte die verschachtelte Besetzung vor der XML-Besetzung?
EvilDr
Wie fragen wir mit diesem Ansatz eine Tabelle mit einer XML-Typspalte ab? Danke dir.
FMFF