Как запросить значения и атрибуты 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-запрос не работает:

Msg 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

Но это тоже не работает:

Msg 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в этом запросе?
Ian R. O'Brien
3
@ IanR.O'Brien m- это набор результатов, возвращаемый 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 и т. д. для увеличения производительности
Дэнни
Это самый простой способ. Спасибо, сработало безупречно.
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