Bagaimana cara menanyakan nilai Xml dan atribut dari tabel di SQL Server?

89

Saya memiliki tabel yang berisi Xmlkolom:

SELECT * 
FROM Sqm

masukkan deskripsi gambar di sini

Contoh xmldata suatu baris adalah:

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

Dalam kasus data ini, saya ingin:

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

Pada akhirnya saya benar-benar akan melakukan SUM(), MIN(), MAX()agregasi. Tetapi untuk saat ini saya hanya mencoba untuk menanyakan kolom xml.

Dalam pseudo-code, saya akan mencoba sesuatu seperti:

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

Tetapi kueri SQL itu tidak berfungsi:

Psn 2396, Level 16,
Status 1, Baris 2 XQuery [Sqm.data.query ()]: Atribut mungkin tidak muncul di luar elemen

Saya telah berburu, dan sungguh menakjubkan betapa buruknya dokumentasi, atau contoh, kueri Xml. Sebagian besar sumber daya daripada membuat kueri tabel , kueri a variabel ; yang tidak saya lakukan. Sebagian besar sumber daya hanya menggunakan kueri xml untuk pemfilteran dan pemilihan, daripada membaca nilai. Sebagian besar resource membaca node turunan hard code (menurut indeks), bukan nilai sebenarnya.

Sumber terkait yang saya baca

Perbarui: .value daripada .query

Saya mencoba menggunakan secara acak .value, sebagai ganti .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

Tapi itu juga tidak berhasil:

MSG 2389, Level 16, Status 1, Baris 3 XQuery [Sqm.data.value ()]:
'value ()' memerlukan singleton (atau urutan kosong), operan yang ditemukan dari tipe 'xdt: untypedAtomic *'

Ian Boyd
sumber

Jawaban:

114

Sebenarnya Anda sudah dekat dengan tujuan Anda, Anda hanya perlu menggunakan metode node () untuk membagi baris Anda dan kemudian mendapatkan nilai:

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
sumber
1
Bagaimana cara mendapatkan "nilai" dari node itu sendiri? Sepertinya tidak ada cara select m.*untuk melihat tabel perantara, ajaib, dan rahasia yang dibangunnya. Apa sintaks untuk menanyakan nilai suatu elemen? misalnya nilai <Metric>8675309</Metric>adalah "8675309"
Ian Boyd
1
@IanBoyd maaf, ketinggalan itu, lihat diperbarui. Kamu dapat memakai '.' atau teks jika mungkin ada elemen bersarang
Roman Pekar
2
Apa alias s, mdan cmewakili dalam query ini?
Ian R. O'Brien
3
@ IanR.O'Brien madalah kumpulan hasil yang dikembalikan oleh nodes()fungsi, sadalah sqmtabel itu sendiri, cadalah kolom dengan tipe data xml dalam kumpulan hasil yang dikembalikan oleh nodes()fungsi
Roman Pekar
11

Saya telah mencoba melakukan sesuatu yang sangat mirip tetapi tidak menggunakan node. Namun, struktur xml saya sedikit berbeda.

Anda memilikinya seperti ini:

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

Jika sudah seperti ini sebagai gantinya:

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

Kemudian Anda cukup menggunakan pernyataan SQL ini.

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

Bagi saya ini jauh lebih membingungkan daripada menggunakan penerapan luar atau penerapan silang.

Saya harap ini membantu orang lain mencari solusi yang lebih sederhana!

Ryan Dorendorf
sumber
1
kode tersebut melewatkan tanda kurung buka. juga menambahkan /text()setelah id dll untuk peningkatan kinerja
Danny Rancher
Ini yang paling tegas. Terima kasih, bekerja dengan sempurna.
SE
Bagaimana kita membuat kueri tabel dengan kolom tipe XML dengan pendekatan ini? Terima kasih.
FMFF
10

gunakan valuesebagai ganti query(harus menentukan indeks node yang akan dikembalikan di XQuery serta meneruskan tipe data sql untuk dikembalikan sebagai parameter kedua):

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

Saya tidak mengerti mengapa beberapa orang menyarankan untuk menggunakan cross applyatau outer applymengubah xml menjadi tabel nilai. Bagi saya, itu hanya membawa terlalu banyak data.

Inilah contoh saya bagaimana Anda akan membuat sebuah xmlobjek, lalu mengubahnya menjadi tabel.

(Saya telah menambahkan spasi di string xml saya, hanya untuk membuatnya lebih mudah dibaca.)

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)

Dan inilah hasilnya:

masukkan deskripsi gambar di sini

Mike Gledhill
sumber
Penasaran ... mengapa pemeran bersarang Varbinary(max)sebelum pemeran xml?
EvilDr
Bagaimana kita membuat kueri tabel dengan kolom tipe XML dengan pendekatan ini? Terima kasih.
FMFF
@FMFF metode ini sebenarnya identik dengan yang lain menggunakan penerapan silang.
SgtWilko