¿Cómo consultar valores y atributos Xml de la tabla en SQL Server?

88

Tengo una tabla que contiene una Xmlcolumna:

SELECT * 
FROM Sqm

ingrese la descripción de la imagen aquí

Una muestra de los xmldatos de una fila sería:

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

En el caso de estos datos, querría:

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

Al final he hecho estarán realizando SUM(), MIN(), MAX()agregación. Pero por ahora solo intento consultar una columna xml.

En pseudocódigo, probaría 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

Pero esa consulta SQL no funciona:

Msg 2396, nivel 16, estado 1, línea 2
XQuery [Sqm.data.query ()]: el atributo no puede aparecer fuera de un elemento

He cazado, y es sorprendente lo mal documentado o ilustrado que está la consulta XML. La mayoría de los recursos, en lugar de consultar una tabla , consulta una variable ; que no estoy haciendo. La mayoría de los recursos solo usan consultas xml para filtrar y seleccionar, en lugar de leer valores. La mayoría de los recursos leen nodos secundarios codificados (por índice), en lugar de valores reales.

Recursos relacionados que leí

Actualización: .value en lugar de .query

Intenté usar aleatoriamente .value, en 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

Pero eso tampoco funciona:

Msg 2389, nivel 16, estado 1, línea 3 XQuery [Sqm.data.value ()]:
'value ()' requiere un singleton (o secuencia vacía), operando encontrado de tipo 'xdt: untypedAtomic *'

Ian Boyd
fuente

Respuestas:

113

En realidad, está cerca de su objetivo, solo necesita usar el método nodes () para dividir sus filas y luego obtener los 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
fuente
1
¿Cómo obtengo el "valor" del nodo en sí? Parece que no hay forma de select m.*ver la mesa secreta, mágica e intermedia que construyó. ¿Cuál es la sintaxis para consultar el valor de un elemento? por ejemplo, el valor de <Metric>8675309</Metric>es "8675309"
Ian Boyd
1
@IanBoyd lo siento, me perdí eso, ver actualizado. Puedes usar '.' o texto si pudiera haber elementos anidados
Roman Pekar
2
¿Qué representan los alias s, my cen esta consulta?
Ian R. O'Brien
3
@ IanR.O'Brien mes el conjunto de resultados devuelto por la nodes()función, ses la sqmtabla en sí, ces una columna con el tipo de datos xml en el conjunto de resultados devuelto por la nodes()función
Roman Pekar
11

He estado tratando de hacer algo muy similar pero sin usar los nodos. Sin embargo, mi estructura xml es un poco diferente.

Lo tienes así:

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

Si fuera así en su lugar:

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

Entonces podría simplemente usar esta declaración 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 mí, esto es mucho menos confuso que usar la aplicación externa o la aplicación cruzada.

¡Espero que esto ayude a alguien más a buscar una solución más simple!

Ryan Dorendorf
fuente
1
el código pierde los corchetes de apertura. también se agrega /text()después de la identificación, etc. para aumentar el rendimiento
Danny Rancher
Este es el más sencillo. Gracias, funcionó perfectamente.
SE
¿Cómo consultamos una tabla con una columna de tipo XML con este enfoque? Gracias.
FMFF
10

use en valuelugar de query(debe especificar el índice del nodo para devolver en XQuery, así como pasar el tipo de datos sql para devolver como 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
fuente
8

No entiendo por qué algunas personas sugieren usar cross applyo outer applyconvertir el xml en una tabla de valores. Para mí, eso trajo demasiados datos.

Aquí está mi ejemplo de cómo crearía un xmlobjeto y luego lo convertiría en una tabla.

(He agregado espacios en mi cadena xml, solo para que sea más fácil de leer).

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)

Y aquí está el resultado:

ingrese la descripción de la imagen aquí

Mike Gledhill
fuente
Curioso ... ¿por qué el elenco anidado Varbinary(max)antes del elenco xml, por favor?
EvilDr
¿Cómo consultamos una tabla con una columna de tipo XML con este enfoque? Gracias.
FMFF