Selecione os valores do campo XML no SQL Server 2008

112

Olhando para o meu campo XML, minhas linhas se parecem com isto:

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

Observe que essas são três linhas em minha tabela.

Eu gostaria de retornar um resultado SQL como uma tabela como em

Jon  | Johnson
Kathy| Carter
Bob  | Burns

Que consulta fará isso?

Larsenal
fonte
Não há como apenas obter TODOS os elementos no xml? Você tem que especificar um por um? Isso se torna muito tedioso rápido. Você pode fazer "select * from table", parece que você deve ser capaz de fazer "select xml. * From xml" sem ter que especificar cada elemento que deseja.
Keith Tyler

Respostas:

157

Dado que o campo XML é denominado '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
fonte
16
Você deve usar .nodes () e aplicar cruzada se xmlField contiver mais de um elemento <person>.
Remus Rusanu
SQL Server 2008 R2 Express, retornou-me este erro com sua solução The XQuery syntax '/function()' is not supported.:; Por outro lado, @Remus Rusanu parece fazer isso :)
RMiranda
2
Bizarro. Isso foi votado 102 vezes, mas essa resposta retorna apenas dados do primeiro registro XML. E se refere a alguma tabela [minhaTabela] ... de onde veio isso?!
Mike Gledhill
Eu tentei isso tantas vezes e nunca tinha funcionado. Meu XML é <BAM><Type>Electrical</Type><BaIds><a:int>7330</a:int></BaIds></BAM>, meu select é select e.MessageData.value('(/BAM/Type)[1]', 'varchar(100)'). Eu também tentei select e.MessageData.value('(/BAM/Type/node())[1]', 'varchar(100)'), e '(//Type/node())[1]', '(./Type)[1]'e todas as outras combinações que eu posso pensar. Tudo que eu consigo é NULL.
JonathanPeel de
1
@MikeGledhill ele retorna valores de vários registros XML bem para mim. Além disso, o único nome para a mesa que o OP dá é "minha mesa" :)
Paulo
123

Considerando que os dados XML vêm de uma tabela 'tabela' e são armazenados em uma coluna 'campo': use os métodos XML , extraia valores com xml.value(), nós do projeto com xml.nodes(), use CROSS APPLYpara juntar:

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)

Você pode descartar o nodes()e cross applyse cada campo contiver exatamente um elemento 'pessoa'. Se o XML for uma variável que você seleciona FROM @variable.nodes(...)e não precisa do cross apply.

Remus Rusanu
fonte
1
Eu me pergunto o quão eficiente é esse método e se existe uma maneira melhor. Os resultados do CROSS APPLY combiend com XPath parecem que podem resultar em uma consulta que exige muitos recursos.
redcalx
1
@thelocster: isso não é diferente do acesso a dados comum. As técnicas para melhorar o desempenho do XML estão bem documentadas. msdn.microsoft.com/en-us/library/ms345118%28SQL.90%29.aspx
Remus Rusanu
2
tenha em mente que se o seu XML tiver namespaces xmlns definidos, você precisará defini-los na expressão XQuery (XPath) acima. Consulte stackoverflow.com/a/1302150/656010 para obter um exemplo.
Tom Wayson
Um pouco diferente do que eu estava precisando, mas essa era uma solução perfeita para um problema que eu estava tendo que era várias linhas com uma coluna XML - eu queria fazer um loop pelas linhas e puxar os campos de dados de dentro da coluna XML e colocá-los uma instrução de inserção. Portanto, 5 linhas, cada uma para 3 colunas de dados no campo XML = 15 inserções, perfeito.
dan richardson
17

Esta postagem foi útil para resolver meu problema que tem um formato XML um pouco diferente ... meu XML contém uma lista de chaves como o exemplo a seguir e eu armazeno o XML na coluna SourceKeys em uma tabela chamada DeleteBatch:

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

Crie a tabela e preencha-a com alguns dados:

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

Aqui está meu SQL para selecionar as chaves do XML:

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

Aqui estão os resultados da consulta ...

Chave ExecutionKey
1 1
1 2
1 3
2 100
2 101
Monte
fonte
9

Isso pode responder à sua pergunta:

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

Caramba. Este foi um tópico realmente útil para descobrir.

Ainda achei algumas dessas sugestões confusas. Sempre que usei valuecom [1]na string, ele recuperaria apenas o primeiro valor. E algumas sugestões recomendaram usar o cross applyque (em meus testes) apenas trouxe de volta muitos dados.

Portanto, aqui está meu exemplo simples de como você criaria um xmlobjeto e, em seguida, leria seus valores em uma tabela.

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

É uma sintaxe bizarra, mas com um exemplo decente, é fácil de adicionar às suas próprias funções do SQL Server.

Falando nisso, aqui está a resposta correta para essa pergunta.

Supondo que você tenha seus dados xml em uma @xmlvariável do tipo xml(conforme demonstrado no meu exemplo acima), veja como você retornaria as três linhas de dados do xml citado na pergunta:

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)

insira a descrição da imagem aqui

Mike Gledhill
fonte
Não vejo como esta é a resposta correta. O OP está solicitando a consulta de uma coluna de uma tabela do tipo XML e, nesse caso, você deve usar [1]o ordinal do índice para forçá-lo a retornar 1 linha ou aplicar a coluna nodes()para obter um estrutura que pode ter xpath executado contra ele. Seu código não se traduz nesse cenário sem muitas modificações. Você está usando uma variável, não uma coluna da tabela. Você também está usando demais a query()função que retorna xml. por exemplo, você poderia ter apenasx.Rec.value('(./firstName)[1]', 'nvarchar(2000)') AS FirstName
Davos
3

Se você for capaz de envolver seu XML em um elemento raiz - digamos que a solução a seguir seja:

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)

insira a descrição da imagem aqui

Moiz Tankiwala
fonte
3

MSSQL usa regras XPath regulares da seguinte maneira:

  • nodename Seleciona todos os nós com o nome "nodename"
  • / Seleciona a partir do nó raiz
  • // Seleciona nós no documento do nó atual que correspondem à seleção, não importa onde eles estejam
  • . Seleciona o nó atual
  • .. Seleciona o pai do nó atual
  • @ Seleciona atributos

W3Schools

Arthur
fonte
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
fonte
0

/ * Este exemplo usa uma variável XML com um 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('/*')
Overfilledwaistcoat
fonte