Як запитувати значення Xml та атрибути з таблиці в SQL Server?


88

У мене є таблиця, яка містить Xmlстовпець:

SELECT * 
FROM Sqm

введіть тут опис зображення

Прикладом xmlданих рядка буде:

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

У випадку цих даних я хотів би:

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

Зрештою , я на самому ділі виступати SUM(), MIN(), MAX()агрегацію. Але наразі я просто намагаюся запитати стовпець xml.

У псевдокоді я спробував би щось на зразок:

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

Але цей запит SQL не працює:

Повідомлення 2396, рівень 16, стан 1, рядок 2
XQuery [Sqm.data.query ()]: Атрибут може не відображатися поза елементом

Я полював, і дивно, наскільки погано задокументований, або прикладний, запит Xml. Більшість ресурсів замість запиту таблиці , запитують змінну ; що я не роблю. Більшість ресурсів використовують лише запити xml для фільтрації та вибору, а не для читання значень. Більшість ресурсів читають жорстко закодовані дочірні вузли (за індексом), а не фактичні значення.

Пов’язані ресурси, які я читав

Оновлення: .value, а не .query

Я спробував навмання використовувати .value, замість .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

Але це також не працює:

Повідомлення 2389, рівень 16, стан 1, рядок 3 XQuery [Sqm.data.value ()]:
'value ()' вимагає синглтон (або порожню послідовність), знайдений операнд типу 'xdt: untypedAtomic *'

Відповіді:


113

Насправді ви близькі до своєї мети, вам просто потрібно використовувати метод nodes (), щоб розділити ваші рядки, а потім отримати значення:

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
Як отримати "значення" самого вузла? Здається, немає можливості select m.*побачити секретний, чарівний, проміжний стіл, який він побудував. Який синтаксис запиту значення елемента? наприклад , значення <Metric>8675309</Metric>є «8675309»
Ian Boyd

1
@IanBoyd вибачте, пропустив це, дивіться оновлене. Ви можете використовувати '.' або текст, якщо можуть бути вкладені елементи
Роман Пекар

2
Що псевдоніми s, mі cпредставляють в цьому запиті?
Ян Р. О'Брайен

3
@ IanR.O'Brien mє ResultSet повертається nodes()функцією, sє sqmсама таблиця, cце стовпець з типом даних XML в результуючому повертається nodes()функції
Роман Пекар

11

Я намагався зробити щось дуже подібне, але не використовуючи вузли. Однак моя структура xml дещо інша.

У вас він такий:

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

Якби замість цього було так:

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

Тоді ви можете просто використати цей оператор 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

Для мене це набагато менш заплутане, ніж використання зовнішнього застосування або перехресного застосування.

Сподіваюся, це допоможе комусь іншому, хто шукає простішого рішення!


1
код пропускає відкриваючі дужки. також додати /text()після id тощо для підвищення продуктивності
Danny Rancher

Це найпростіший. Дякую, працював досконало.
SE

Як ми запитуємо таблицю зі стовпцем типу XML за допомогою цього підходу? Дякую.
FMFF

10

використовувати valueзамість query(потрібно вказати індекс вузла для повернення в XQuery, а також передачу типу даних sql для повернення як другий параметр):

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

Я не розумію, чому деякі люди пропонують використовувати cross applyабо outer applyперетворити xml у таблицю значень. Для мене це просто повернуло занадто багато даних.

Ось мій приклад того, як ви створили xmlоб’єкт, а потім перетворили його на таблицю.

(Я додав пробіли у своєму рядку xml, щоб полегшити його читання.)

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)

І ось результат:

введіть тут опис зображення


Цікаво ... чому, Varbinary(max)будь ласка, вкладений пристрій до xml?
EvilDr

Як ми запитуємо таблицю зі стовпцем типу XML за допомогою цього підходу? Дякую.
FMFF
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.