Como consultar valores e atributos Xml da tabela no SQL Server?

89

Eu tenho uma tabela que contém uma Xmlcoluna:

SELECT * 
FROM Sqm

insira a descrição da imagem aqui

Uma amostra dos xmldados de uma linha seria:

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

No caso desses dados, eu gostaria de:

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

No final, eu realmente vai estar realizando SUM(), MIN(), MAX()agregação. Mas, por enquanto, estou apenas tentando consultar uma coluna xml.

Em pseudocódigo, eu tentaria algo como:

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

Mas essa consulta SQL não funciona:

Msg 2396, nível 16, estado 1, linha 2
XQuery [Sqm.data.query ()]: o atributo pode não aparecer fora de um elemento

Eu cacei, e é incrível como as consultas Xml são mal documentadas ou exemplificadas. A maioria dos recursos, em vez de consultar uma tabela , consulta uma variável ; o que não estou fazendo. A maioria dos recursos usa apenas consultas xml para filtragem e seleção, em vez de ler valores. A maioria dos recursos lê nós filho codificados permanentemente (por índice), em vez de valores reais.

Recursos relacionados que li

Atualização: .value em vez de .query

Tentei usar aleatoriamente .value, no lugar de .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

Mas isso também não funciona:

Msg 2389, Nível 16, Estado 1, Linha 3 XQuery [Sqm.data.value ()]:
'valor ()' requer um singleton (ou sequência vazia), operando encontrado do tipo 'xdt: untypedAtomic *'

Ian Boyd
fonte

Respostas:

114

Na verdade, você está perto de seu objetivo, você só precisa usar o método nodes () para dividir suas linhas e obter os valores:

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
fonte
1
Como obtenho o "valor" do próprio nó? Parece não haver maneira de select m.*ver a mesa secreta, mágica e intermediária que ela construiu. Qual é a sintaxe para consultar o valor de um elemento? por exemplo, o valor de <Metric>8675309</Metric>é "8675309"
Ian Boyd,
1
@IanBoyd desculpe, perdi isso, veja atualizado. Você pode usar '.' ou texto, se pudesse haver elementos aninhados
Roman Pekar,
2
O que os aliases s, me crepresentam nesta consulta?
Ian R. O'Brien
3
@ IanR.O'Brien mé o conjunto de resultados retornado pela nodes()função, sé a sqmprópria tabela, cé a coluna com o tipo de dados xml no conjunto de resultados retornado pela nodes()função
Roman Pekar
11

Tenho tentado fazer algo muito semelhante, mas não estou usando os nós. No entanto, minha estrutura xml é um pouco diferente.

Você tem assim:

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

Se fosse assim:

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

Então, você pode simplesmente usar esta instrução 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

Para mim, isso é muito menos confuso do que usar a aplicação externa ou a aplicação cruzada.

Espero que isso ajude alguém que esteja procurando uma solução mais simples!

Ryan Dorendorf
fonte
1
o código perde os colchetes de abertura. também anexar /text()após id etc para aumento de desempenho
Danny Rancher,
Este é o mais simples. Obrigado, funcionou perfeitamente.
SE de
Como consultamos uma tabela com uma coluna do tipo XML com essa abordagem? Obrigado.
FMFF
10

use em valuevez de query(deve especificar o índice do nó para retornar no XQuery, bem como passar o tipo de dados sql para retornar como o segundo parâmetro):

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

Não entendo por que algumas pessoas estão sugerindo usar cross applyou outer applyconverter o xml em uma tabela de valores. Para mim, isso trouxe de volta muitos dados.

Aqui está meu exemplo de como você criaria um xmlobjeto e o transformaria em uma mesa.

(Eu adicionei espaços na minha string xml, apenas para torná-la mais fácil de ler.)

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)

E aqui está o resultado:

insira a descrição da imagem aqui

Mike Gledhill
fonte
Curioso ... por que o elenco aninhado Varbinary(max)antes do elenco xml, por favor?
EvilDr
Como consultamos uma tabela com uma coluna do tipo XML com essa abordagem? Obrigado.
FMFF
@FMFF este método é realmente idêntico a todos os outros que usam aplicação cruzada.
SgtWilko