Seleccionar valores del campo XML en SQL Server 2008

112

Con solo mirar mi campo XML, mis filas se ven así:

<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person>

Tenga en cuenta que estas son tres filas en mi tabla.

Me gustaría devolver un resultado de SQL como una tabla como en

Jon  | Johnson
Kathy| Carter
Bob  | Burns

¿Qué consulta logrará esto?

Larsenal
fuente
¿No hay forma de obtener TODOS los elementos en el xml? ¿Tienes que especificar uno por uno? Eso se vuelve realmente tedioso rápidamente. Puede hacer "seleccionar * de la tabla", parece que debería poder hacer "seleccionar xml. * De xml" sin tener que especificar cada elemento que desee.
Keith Tyler

Respuestas:

157

Dado que el campo XML se llama 'xmlField' ...

SELECT 
[xmlField].value('(/person//firstName/node())[1]', 'nvarchar(max)') as FirstName,
[xmlField].value('(/person//lastName/node())[1]', 'nvarchar(max)') as LastName
FROM [myTable]
Larsenal
fuente
16
Debe usar .nodes () y aplicar de forma cruzada si xmlField contiene más de un elemento <person>.
Remus Rusanu
SQL Server 2008 R2 Express, me devolvió este error con su solución The XQuery syntax '/function()' is not supported.:; Por otro lado @Remus Rusanu parece hacerlo :)
RMiranda
2
Extraño. Esto ha sido votado 102 veces, pero esta respuesta solo devuelve datos del primer registro XML. Y se refiere a alguna tabla [myTable] ... ¡¿de dónde vino eso ?!
Mike Gledhill
He intentado esto tantas veces y nunca funcionó. Mi XML es <BAM><Type>Electrical</Type><BaIds><a:int>7330</a:int></BaIds></BAM>, mi selección es select e.MessageData.value('(/BAM/Type)[1]', 'varchar(100)'). También he tratado de selección e.MessageData.value('(/BAM/Type/node())[1]', 'varchar(100)'), y '(//Type/node())[1]', '(./Type)[1]'y cualquier otra combinación que se me ocurre. Todo lo que obtengo es NULL.
JonathanPeel
1
@MikeGledhill devuelve valores de múltiples registros XML bien para mí. Además, el único nombre de la tabla que da el OP es "mi mesa" :)
Paul
123

Teniendo en cuenta que los datos XML provienen de una tabla 'tabla' y se almacenan en una columna 'campo': use los métodos XML , extraiga valores con xml.value(), proyecte nodos con xml.nodes(), use CROSS APPLYpara unir:

SELECT 
    p.value('(./firstName)[1]', 'VARCHAR(8000)') AS firstName,
    p.value('(./lastName)[1]', 'VARCHAR(8000)') AS lastName
FROM table 
    CROSS APPLY field.nodes('/person') t(p)

Puede deshacerse de nodes()y cross applysi cada campo contiene exactamente un elemento 'persona'. Si el XML es una variable que selecciona FROM @variable.nodes(...)y no necesita el cross apply.

Remus Rusanu
fuente
1
Me pregunto qué tan eficiente es este método y si hay una mejor manera. La combinación CROSS APPLY con los resultados de XPath parece que podría resultar en una consulta que consume muchos recursos.
redcalx
1
@thelocster: esto no es diferente del acceso a datos ordinario. Las técnicas para mejorar el rendimiento de XML están bien documentadas. msdn.microsoft.com/en-us/library/ms345118%28SQL.90%29.aspx
Remus Rusanu
2
tenga en cuenta que si su XML tiene espacios de nombres xmlns definidos, deberá definirlos en la expresión XQuery (XPath) anterior. Consulte stackoverflow.com/a/1302150/656010 para ver un ejemplo.
Tom Wayson
Ligeramente diferente de lo que necesitaba, pero esta era una solución perfecta para un problema que estaba teniendo, que eran varias filas con una columna XML: quería recorrer las filas y extraer los campos de datos de la columna XML y ponerlos en una declaración de inserción. Entonces, 5 filas, cada una para 3 columnas de datos en el campo XML = 15 inserciones, perfecto.
dan richardson
17

Esta publicación fue útil para resolver mi problema que tiene un formato XML un poco diferente ... mi XML contiene una lista de claves como el siguiente ejemplo y almaceno el XML en la columna SourceKeys en una tabla llamada DeleteBatch:

<k>1</k>
<k>2</k>
<k>3</k>

Cree la tabla y rellénela con algunos datos:

CREATE TABLE dbo.DeleteBatch (
    ExecutionKey INT PRIMARY KEY,
    SourceKeys XML)

INSERT INTO dbo.DeleteBatch ( ExecutionKey, SourceKeys )
SELECT 1, 
    (CAST('<k>1</k><k>2</k><k>3</k>' AS XML))

INSERT INTO dbo.DeleteBatch ( ExecutionKey, SourceKeys )
SELECT 2, 
    (CAST('<k>100</k><k>101</k>' AS XML))

Aquí está mi SQL para seleccionar las claves del XML:

SELECT ExecutionKey, p.value('.', 'int') AS [Key]
FROM dbo.DeleteBatch
    CROSS APPLY SourceKeys.nodes('/k') t(p)

Aquí están los resultados de la consulta ...

Clave ExecutionKey
1 1
1 2
1 3
2 100
2 101
Monte
fuente
9

Esto puede responder a su pregunta:

select cast(xmlField as xml) xmlField into tmp from (
select '<person><firstName>Jon</firstName><lastName>Johnson</lastName></person>' xmlField
union select '<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>'
union select '<person><firstName>Bob</firstName><lastName>Burns</lastName></person>'
) tb

SELECT
    xmlField.value('(person/firstName)[1]', 'nvarchar(max)') as FirstName
    ,xmlField.value('(person/lastName)[1]', 'nvarchar(max)') as LastName
FROM tmp

drop table tmp
Marquinho Peli
fuente
6

Caray. Este fue un hilo realmente útil para descubrir.

Todavía encuentro algunas de estas sugerencias confusas. Siempre que usaba valuecon [1]en la cadena, solo recuperaba el primer valor. Y algunas sugerencias recomendaron usar el cross applycual (en mis pruebas) simplemente trajo demasiados datos.

Entonces, aquí está mi ejemplo simple de cómo crearía un xmlobjeto y luego leería sus valores en una tabla.

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á la salida:

ingrese la descripción de la imagen aquí

Es una sintaxis extraña, pero con un ejemplo decente, es bastante fácil de agregar a sus propias funciones de SQL Server.

Hablando de eso, aquí está la respuesta correcta a esta pregunta.

Suponiendo que tiene sus datos xml en una @xmlvariable de tipo xml(como se demostró en mi ejemplo anterior), así es como devolvería las tres filas de datos del xml citado en la pregunta:

SELECT 
    x.Rec.query('./firstName').value('.', 'nvarchar(2000)') AS 'FirstName',
    x.Rec.query('./lastName').value('.', 'nvarchar(2000)') AS 'LastName'
FROM @xml.nodes('/person') as x(Rec)

ingrese la descripción de la imagen aquí

Mike Gledhill
fuente
No veo cómo esta es la respuesta correcta. El OP solicita consultar una columna de una tabla que es de tipo XML, y en ese caso debe usar [1]el ordinal de índice para obligarlo a devolver 1 fila, o debe aplicar la columna de forma cruzada con nodes()para obtener un estructura que puede hacer que xpath se ejecute en su contra. Su código no se traduce en ese escenario sin muchas modificaciones. Estás usando una variable, no una columna de tabla. También está abusando de la query()función que devuelve xml. por ejemplo, podría haberlo hechox.Rec.value('(./firstName)[1]', 'nvarchar(2000)') AS FirstName
Davos
3

Si puede envolver su XML en un elemento raíz, digamos que la siguiente es su solución:

DECLARE @PersonsXml XML = '<persons><person><firstName>Jon</firstName><lastName>Johnson</lastName></person>
<person><firstName>Kathy</firstName><lastName>Carter</lastName></person>
<person><firstName>Bob</firstName><lastName>Burns</lastName></person></persons>'

SELECT  b.value('(./firstName/text())[1]','nvarchar(max)') as FirstName, b.value('(./lastName/text())[1]','nvarchar(max)') as LastName
FROM @PersonsXml.nodes('/persons/person') AS a(b)

ingrese la descripción de la imagen aquí

Moiz Tankiwala
fuente
3

MSSQL usa reglas XPath regulares de la siguiente manera:

  • nodename Selecciona todos los nodos con el nombre "nodename"
  • / Selecciona del nodo raíz
  • // Selecciona nodos en el documento del nodo actual que coinciden con la selección sin importar dónde se encuentren
  • . Selecciona el nodo actual
  • .. Selecciona el padre del nodo actual
  • @ Selecciona atributos

W3Schools

Arturo
fuente
2
SELECT 
cast(xmlField as xml).value('(/person//firstName/node())[1]', 'nvarchar(max)') as FirstName,
cast(xmlField as xml).value('(/person//lastName/node())[1]', 'nvarchar(max)') as LastName
FROM [myTable]
shaheer
fuente
0

/ * Este ejemplo usa una variable XML con un esquema * /

IF EXISTS (SELECT * FROM sys.xml_schema_collections 
           WHERE name = 'OrderingAfternoonTea')
BEGIN
    DROP XML SCHEMA COLLECTION dbo.OrderingAfternoonTea 
END
GO

CREATE XML SCHEMA COLLECTION dbo.OrderingAfternoonTea AS
N'<?xml version="1.0" encoding="UTF-16" ?>
  <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     targetNamespace="http://Tfor2.com/schemas/actions/orderAfternoonTea"
     xmlns="http://Tfor2.com/schemas/actions/orderAfternoonTea"
     xmlns:TFor2="http://Tfor2.com/schemas/actions/orderAfternoonTea"
     elementFormDefault="qualified"
     version="0.10"
   > 
    <xsd:complexType name="AfternoonTeaOrderType">
       <xsd:sequence>
         <xsd:element name="potsOfTea" type="xsd:int"/>
         <xsd:element name="cakes" type="xsd:int"/>
         <xsd:element name="fruitedSconesWithCream" type="xsd:int"/>
         <xsd:element name="jams" type="xsd:string"/>
      </xsd:sequence>
      <xsd:attribute name="schemaVersion" type="xsd:long" use="required"/>
    </xsd:complexType>

    <xsd:element name="afternoonTeaOrder"
                 type="TFor2:AfternoonTeaOrderType"/>

  </xsd:schema>' ;
GO

DECLARE @potsOfTea int;
DECLARE @cakes int;
DECLARE @fruitedSconesWithCream int;
DECLARE @jams nvarchar(128);

DECLARE @RequestMsg NVARCHAR(2048);
DECLARE @RequestXml XML(dbo.OrderingAfternoonTea);

set @potsOfTea = 5;
set @cakes = 7;
set @fruitedSconesWithCream = 25;
set @jams = N'medlar jelly, quince and mulberry';

SELECT @RequestMsg = N'<?xml version="1.0" encoding="utf-16" ?>
<TFor2:afternoonTeaOrder schemaVersion="10"
    xmlns:TFor2="http://Tfor2.com/schemas/actions/orderAfternoonTea">
    <TFor2:potsOfTea>' + CAST(@potsOfTea as NVARCHAR(20)) 
        + '</TFor2:potsOfTea>
    <TFor2:cakes>' + CAST(@cakes as NVARCHAR(20)) + '</TFor2:cakes>
    <TFor2:fruitedSconesWithCream>' 
        + CAST(@fruitedSconesWithCream as NVARCHAR(20))
        + '</TFor2:fruitedSconesWithCream>
    <TFor2:jams>' + @jams + '</TFor2:jams>
</TFor2:afternoonTeaOrder>';

SELECT @RequestXml  = CAST(CAST(@RequestMsg AS VARBINARY(MAX)) AS XML) ;

with xmlnamespaces('http://Tfor2.com/schemas/actions/orderAfternoonTea'
                    as tea)
select
    cast( x.Rec.value('.[1]/@schemaVersion','nvarchar(20)') as bigint )
        as schemaVersion,
    cast( x.Rec.query('./tea:potsOfTea')
               .value('.','nvarchar(20)') as bigint ) as potsOfTea,
    cast( x.Rec.query('./tea:cakes')
               .value('.','nvarchar(20)') as bigint )  as cakes,
    cast( x.Rec.query('./tea:fruitedSconesWithCream')
               .value('.','nvarchar(20)') as bigint ) 
      as fruitedSconesWithCream,
    x.Rec.query('./tea:jams').value('.','nvarchar(50)')  as jams
from @RequestXml.nodes('/tea:afternoonTeaOrder')  as x(Rec);

select @RequestXml.query('/*')
Abrigo de cintura
fuente