it-swarm.com.de

Abfragen von XML-Werten und -Attributen aus einer Tabelle in SQL Server

Ich habe eine Tabelle, die eine Xml -Spalte enthält:

SELECT * 
FROM Sqm

enter image description here

Ein Beispiel für die xml Daten 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>

Im Falle dieser Daten würde ich wünschen:

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 SUM(), MIN(), MAX()-Aggregation durchführen. Aber jetzt versuche ich nur, query eine XML-Spalte.

Im Pseudocode würde ich so etwas versuchen:

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 wird möglicherweise nicht außerhalb eines Elements angezeigt

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

Verwandte Ressourcen, die ich gelesen habe

Update: .value statt .query

Ich habe zufällig versucht, .value Anstelle von .query Zu verwenden:

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 geht aber auch nicht:

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

70
Ian Boyd

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

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

93
Roman Pekar

verwenden Sie value anstelle von query (Sie müssen den Index des zurückzugebenden Knotens in der XQuery angeben und den zurückzugebenden SQL-Datentyp als zweiten Parameter übergeben):

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

Ich habe versucht, etwas sehr ähnliches zu tun, aber nicht die Knoten zu verwenden. Allerdings ist meine XML-Struktur 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önnen 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 weniger verwirrend als das äußere Anwenden oder das Querverwenden.

Ich hoffe das hilft jemand anderem bei der Suche nach einer einfacheren Lösung!

7
Ryan Dorendorf

Ich verstehe nicht, warum einige Leute vorschlagen, cross apply Oder outer apply Zu verwenden, um die XML in eine Wertetabelle zu konvertieren. Für mich hat das einfach viel zu viele Daten zurückgebracht.

Hier ist mein Beispiel, wie Sie ein xml -Objekt erstellen und es dann in eine Tabelle verwandeln.

(Ich habe Leerzeichen in meine XML-Zeichenfolge eingefügt, um das Lesen zu vereinfachen.)

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:

enter image description here

6
Mike Gledhill