Olá pessoal,
Boa noite!

Neste post vou demonstrar pra vocês como importar, ler, tratar e exportar dados entre uma tabela do SQL Server e um arquivo XML.

Essa integração entre o banco de dados e arquivos XML é um excelente recurso e grande diferencial para desenvolvedores que utilizam o SQL Server e podem facilmente ler e gerar arquivos nesse formato nativamente pelo banco de dados.

Conhecendo os arquivos XML de exemplo
Esses serãos os arquivos XML que vou importar para o banco de dados e transformá-los em tabelas para facilitar a manipulação das informações:

Base 1 (Informações como atributos)

<?xml version="1.0" encoding="ISO-8859-1"?> 
<Root>
   <Cliente Nome="Cliente Teste 1" Idade="29" CPF="11111111111" Email="[email protected]" Celular="99999999999">
     <Endereco Cidade="Vitoria" Estado="ES" Pais="Brasil" CEP="2920000">
       <Telefone Fixo="888888888888" Quintal="Sim" Quadra="Sim" />
     </Endereco>
     <Endereco Cidade="Vila Velha" Estado="ES" Pais="Brasil" CEP="2900000">
       <Telefone Fixo="777777777777" Piscina="Sim" />
     </Endereco>
	 <Endereco Cidade="Serra" Estado="ES" Pais="Brasil" CEP="2970000" />
   </Cliente>
   <Cliente Nome="Cliente Teste 2" Idade="30" CPF="222222222222" Email="[email protected]">
     <Endereco Cidade="Campos dos Goytacazes" Estado="RJ" Pais="Brasil" CEP="2825000">
       <Telefone Fixo="33333333333"/>
     </Endereco>
   </Cliente>
</Root>

Base 2 (Informações como conteúdo)

<?xml version="1.0" encoding="iso-8859-1"?> 
<Escola>
   <Turma Nome="Turma 1" Serie="1">
     <Aluno Apostolo="1" Traidor="0" Nota="7">Joao</Aluno>
	 <Aluno Apostolo="1" Traidor="0" Nota="7.5">Marcos</Aluno>
	 <Aluno Apostolo="1" Traidor="0" Nota="8">Mateus</Aluno>
	 <Aluno Apostolo="0" Traidor="0" Nota="10">Paulo</Aluno>
   </Turma>
   <Turma Nome="Turma 2" Serie="2">
     <Aluno Apostolo="1" Traidor="0" Nota="6.1">Andre</Aluno>
	 <Aluno Apostolo="1" Traidor="0" Nota="5.9">Simao</Aluno>
	 <Aluno Apostolo="1" Traidor="0" Nota="7.3">Pedro</Aluno>
	 <Aluno Apostolo="1" Traidor="0" Nota="7.1">Bartolomeu</Aluno>
	 <Aluno Apostolo="1" Traidor="1" Nota="0">Judas</Aluno>
	 <Aluno Apostolo="1" Traidor="0" Nota="6.4">Tiago</Aluno>
	 <Aluno Apostolo="1" Traidor="0" Nota="6.7">Felipe</Aluno>
	 <Aluno Apostolo="1" Traidor="0" Nota="6.3">Tome</Aluno>
	 <Aluno Apostolo="1" Traidor="0" Nota="6.5">Tadeu</Aluno>
   </Turma>
</Escola>

Vamos conferir agora, de forma fácil e prática, como fazer isso.

Como importar arquivos XML para o SQL Server

Como importar arquivos XML para o SQL Server

O primeiro passo para conseguirmos validar e trabalhar com os dados contidos em arquivos XML é importar esses dados para o nosso banco SQL Server. Caso você já tenha os dados do XML em uma tabela, pode ignorar esse passo.

Para a importação do XML a partir de um arquivo físico no disco ou em um diretório compartilhado da sua rede, vou utilizar a função fncArquivo_Ler_Retorna_String, do CLR (C#), por ser a mais fácil e prática de se utilizar.

Como eu já havia demonstrado no post SQL Server – Como importar arquivos de texto para o banco (OLE Automation, CLR, BCP, BULK INSERT), inclusive com o código-fonte da função fncArquivo_Ler_Retorna_String, você também pode utilizar outras formas de importação de arquivos texto, conforme sua vontade ou restrição na instância.

Para realizar a importação do arquivo, podemos fazer da seguinte forma:

DECLARE @XML_Bruto VARCHAR(MAX) = CLR.dbo.fncArquivo_Ler_Retorna_String('C:\Exemplo.xml')

IF (OBJECT_ID('tempdb..#XML') IS NOT NULL) DROP TABLE #XML
CREATE TABLE #XML (
    Ds_XML VARCHAR(MAX)
)

INSERT INTO #XML
SELECT @XML_Bruto


SET @XML_Bruto = CLR.dbo.fncArquivo_Ler_Retorna_String('C:\Aluno.xml')

IF (OBJECT_ID('tempdb..#XML2') IS NOT NULL) DROP TABLE #XML2
CREATE TABLE #XML2 (
    Ds_XML VARCHAR(MAX)
)

INSERT INTO #XML2
SELECT @XML_Bruto

Como tratar e ler os atributos de um XML no SQL Server

Como tratar e ler os dados de atributos de um XML no SQL Server

Uma vez que o XML já está em uma tabela, vamos iniciar o tratamento e a leitura das informações do XML. Nesse caso, precisaremos armazenar o conteúdo em uma variável do tipo XML.

Para isso, você pode fazer assim:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML)

Agora que conseguimos carregar nosso XML para uma variável, vamos iniciar o tratamento dos dados.

Leitura simples:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML)

SELECT
    @XML.value('(/Root/Cliente[1]/@Nome)[1]','varchar(100)') AS Cliente1_Nome,
    @XML.value('(/Root/Cliente[1]/@Idade)[1]','int') AS Cliente1_Idade,
    @XML.value('(/Root/Cliente[1]/@CPF)[1]','varchar(100)') AS Cliente1_CPF,
    @XML.value('(/Root/Cliente[1]/@Email)[1]','varchar(100)') AS Cliente1_Email,
    @XML.value('(/Root/Cliente[1]/@Celular)[1]','varchar(100)') AS Cliente1_Celular,

    @XML.value('(/Root/Cliente[1]/Endereco[1]/@Cidade)[1]','varchar(100)') AS Cliente1_Endereco1_Cidade,
    @XML.value('(/Root/Cliente[1]/Endereco[2]/@Cidade)[1]','varchar(100)') AS Cliente1_Endereco2_Cidade


SELECT
    @XML.value('(/Root/Cliente[2]/@Nome)[1]','varchar(100)') AS Cliente2_Nome,
    @XML.value('(/Root/Cliente[2]/@Idade)[1]','int') AS Cliente2_Idade,
    @XML.value('(/Root/Cliente[2]/@CPF)[1]','varchar(100)') AS Cliente2_CPF,
    @XML.value('(/Root/Cliente[2]/@Email)[1]','varchar(100)') AS Cliente2_Email,
    @XML.value('(/Root/Cliente[2]/@Celular)[1]','varchar(100)') AS Cliente2_Celular,

    @XML.value('(/Root/Cliente[2]/Endereco[1]/@Cidade)[1]','varchar(100)') AS Cliente1_Endereco1_Cidade,
    @XML.value('(/Root/Cliente[2]/Endereco[2]/@Cidade)[1]','varchar(100)') AS Cliente1_Endereco2_Cidade

SQL Server - Read XML Attributes
SQL Server - Read XML Attributes

Como vimos no exemplo acima, precisamos definir manualmente a linha que queremos retornar informações do nosso XML. Mas e se o arquivo tiver, 100, 1.000 ou mais linhas? Teremos que utilizar a instrução WHILE para percorrer todas as linhas do XML? Não. Para isso, podemos utilizar a função NODES, que aplica os filtros do nosso SELECT em todos os nós do nosso seletor e os retorna em forma de linhas de uma tabela:

Utilizando a função NODES para retornar todos os nós:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML)

SELECT
    Clientes.linha.value('@Nome','varchar(100)') AS Nome,
    Clientes.linha.value('@Idade','int') AS Idade,
    Clientes.linha.value('@CPF','varchar(14)') AS CPF,
    Clientes.linha.value('@Email','varchar(200)') AS Email,
    Clientes.linha.value('@Celular','varchar(20)') AS Celular
FROM   
    @XML.nodes('/Root/Cliente') Clientes(linha)

SQL Server - Read XML Attributes with Nodes
SQL Server - Read XML Attributes with Nodes

Ao utilizarmos a função nodes, estamos informando que para cada nó da árvore do XML que seja na estrutura “/Root/Cliente” seja retornado um registro da nossa tabela. Esse registro é identificado utilizando a nomenclatura Clientes (elemento pai, que seria como se fosse a tabela) e linha (cada registro do elemento pai).

Após quebrar os nós em registros, precisamos recuperar as informações de cada atributo. Para isso, utilizamos a função value, seguido por @NomeDoAtributo (deve estar igual ao nome do atributo do XML) e o seu respectivo tipo de retorno.

Utilizando a função EXIST

Em determinadas situações, precisamos saber se um determinado nó existe no nosso XML. Para isso, o SQL Server nos disponibiliza a função exist, que nos permite realizar esse tipo de verificação:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML)

SELECT
    @XML.exist('(/Root/Cliente[1]/@Nome)[1]') AS Cliente1_Existe,
    @XML.exist('(/Root/Cliente[2]/@Nome)[1]') AS Cliente2_Existe,
    @XML.exist('(/Root/Cliente[3]/@Nome)[1]') AS Cliente3_Existe

SQL Server - Verify Check if XML Attribute exist function
SQL Server - Verify Check if XML Attribute exist function

Utilizando exist junto com o value:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML)

SELECT

    (CASE WHEN @XML.exist('(/Root/Cliente[1]/@Nome)[1]') = 1 
        THEN @XML.value('(/Root/Cliente[1]/@Nome)[1]', 'varchar(100)') 
        ELSE 'Não existe' 
    END) AS Cliente1_Nome,

    (CASE WHEN @XML.exist('(/Root/Cliente[2]/@Nome)[1]') = 1 
        THEN @XML.value('(/Root/Cliente[2]/@Nome)[1]', 'varchar(100)') 
        ELSE 'Não existe' 
    END) AS Cliente2_Nome,

    (CASE WHEN @XML.exist('(/Root/Cliente[3]/@Nome)[1]') = 1 
        THEN @XML.value('(/Root/Cliente[3]/@Nome)[1]', 'varchar(100)') 
        ELSE 'Não existe' 
    END) AS Cliente3_Nome

SQL Server - Verify Check if XML Attribute exist function with value
SQL Server - Verify Check if XML Attribute exist function with value

Recuperando os nós filhos

A primeira parte foi concluída: Já conseguimos realizar uma importação simples no nosso XML. Mas ainda temos muito a avançar: Nosso XML possui N nós filhos, com mais 2 níveis de hierarquia para trabalharmos (Endereco e Telefone).

Importando o 1º nível de hierarquia: Endereco

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML)

SELECT
    Clientes.linha.value('@Nome','varchar(100)') AS Nome,
    Clientes.linha.value('@Idade','int') AS Idade,
    Clientes.linha.value('@CPF','varchar(14)') AS CPF,
    Clientes.linha.value('@Email','varchar(200)') AS Email,
    Clientes.linha.value('@Celular','varchar(20)') AS Celular,

    Enderecos.linha.value('@Cidade','varchar(60)') AS Cidade,
    Enderecos.linha.value('@Estado','varchar(2)') AS UF,
    Enderecos.linha.value('@Pais','varchar(50)') AS Pais,
    Enderecos.linha.value('@CEP','varchar(10)') AS CEP
FROM
    @XML.nodes('/Root/Cliente') Clientes(linha)
    CROSS APPLY Clientes.linha.nodes('Endereco') Enderecos(linha)

SQL Server - Read Parse XML data from file 2
SQL Server - Read Parse XML data from file 2

Reparem que para pegar os subníveis da hierarquia eu utilizo um CROSS APPLY do elemento pai (Clientes) e para cada registro (linha) eu faço um cruzamento desses dados com os nós filhos (do tipo Endereco).

Importando o 2º nível de hierarquia: Telefone

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML)

SELECT
    Clientes.linha.value('@Nome','varchar(100)') AS Nome,
    Clientes.linha.value('@Idade','int') AS Idade,
    Clientes.linha.value('@CPF','varchar(14)') AS CPF,
    Clientes.linha.value('@Email','varchar(200)') AS Email,
    Clientes.linha.value('@Celular','varchar(20)') AS Celular,

    Enderecos.linha.value('@Cidade','varchar(60)') AS Cidade,
    Enderecos.linha.value('@Estado','varchar(2)') AS UF,
    Enderecos.linha.value('@Pais','varchar(50)') AS Pais,
    Enderecos.linha.value('@CEP','varchar(10)') AS CEP,

    Telefones.linha.value('@Fixo','varchar(20)') AS Telefone_Fixo,
    Telefones.linha.value('@Piscina','varchar(20)') AS Tem_Piscina,
    Telefones.linha.value('@Quintal','varchar(20)') AS Tem_Quintal,
    Telefones.linha.value('@Quadra','varchar(20)') AS Tem_Quadra,
    Telefones.linha.value('@NaoExiste','varchar(20)') AS Atributo_Nao_Existe
FROM
    @XML.nodes('/Root/Cliente') Clientes(linha)
    CROSS APPLY Clientes.linha.nodes('Endereco') Enderecos(linha)
    CROSS APPLY Enderecos.linha.nodes('Telefone') Telefones(linha)

SQL Server - Read Parse XML data from file 3
SQL Server - Read Parse XML data from file 3

E aí conseguimos finalmente importar os dados. Mas tem um erro aí..

SQL Server - Read Parse XML data from file 4
SQL Server - Read Parse XML data from file 4

Se analisarmos melhor nosso XML, vamos ver que um dos endereços não foi importado, porque ele não tinha telefone e como fizemos um CROSS APPLY, esse registro foi ignorado. Vamos precisar tratar isso:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML)

SELECT
    Clientes.linha.value('@Nome','varchar(100)') AS Nome,
    Clientes.linha.value('@Idade','int') AS Idade,
    Clientes.linha.value('@CPF','varchar(14)') AS CPF,
    Clientes.linha.value('@Email','varchar(200)') AS Email,
    Clientes.linha.value('@Celular','varchar(20)') AS Celular,

    Enderecos.linha.value('@Cidade','varchar(60)') AS Cidade,
    Enderecos.linha.value('@Estado','varchar(2)') AS UF,
    Enderecos.linha.value('@Pais','varchar(50)') AS Pais,
    Enderecos.linha.value('@CEP','varchar(10)') AS CEP,

    Telefones.linha.value('@Fixo','varchar(20)') AS Telefone_Fixo,
    Telefones.linha.value('@Piscina','varchar(20)') AS Tem_Piscina,
    Telefones.linha.value('@Quintal','varchar(20)') AS Tem_Quintal,
    Telefones.linha.value('@Quadra','varchar(20)') AS Tem_Quadra,
    Telefones.linha.value('@NaoExiste','varchar(20)') AS Atributo_Nao_Existe
FROM
    @XML.nodes('/Root/Cliente') Clientes(linha)
    CROSS APPLY Clientes.linha.nodes('Endereco') Enderecos(linha)
    OUTER APPLY Enderecos.linha.nodes('Telefone') Telefones(linha)

Para resolver essa situação, substituí o último CROSS APPLY por um OUTER APPLY, que possui um comportamento semelhante a um LEFT JOIN nesse caso, onde os registros que não tiverem um nó filho continuam aparecendo na nossa tabela, e as colunas destinadas aos nós filhos ficarão vazias. Sempre que houver uma situação onde você não tem certeza que o elemento terá filhos ou não, utilize o OUTER APPLY.

SQL Server - Read Parse XML data from file 5
SQL Server - Read Parse XML data from file 5

Como tratar e ler os dados de um XML no SQL Server

Como tratar e ler os dados de um XML no SQL Server

Diferente da base 1, onde as informações eram todas guardadas em forma de atributos, vamos agora utilizar o XML da base 2, que é predominantemente formado por dados.

Leitura simples dos dados

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT 
    @XML.value('(Escola/Turma/Aluno)[1]', 'varchar(60)') AS Aluno1,
    @XML.value('(Escola/Turma/Aluno)[2]', 'varchar(60)') AS Aluno2,
    @XML.value('(Escola/Turma/Aluno)[3]', 'varchar(60)') AS Aluno3,
    @XML.value('(Escola/Turma/Aluno)[4]', 'varchar(60)') AS Aluno4,
    @XML.value('(Escola/Turma/Aluno)[5]', 'varchar(60)') AS Aluno5,
    @XML.value('(Escola/Turma/Aluno)[6]', 'varchar(60)') AS Aluno6,
    @XML.value('(Escola/Turma/Aluno)[14]', 'varchar(60)') AS Aluno14_Nao_Existe

SQL Server - Read XML Data
SQL Server - Read XML Data

Assim como fiz com os atributos, quis demonstrar como retornar individualmente cada nó do arquivo, especificando a índice da sua posição em relação à raiz. Agora vou demonstrar novamente como utilizar a função NODES para retornar todos os nós como registros de uma tabela:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT
    Alunos.linha.value('.','varchar(100)') AS Nome
FROM
    @XML.nodes('/Escola/Turma/Aluno') Alunos(linha)

SQL Server - Read XML Data with Nodes function
SQL Server - Read XML Data with Nodes function

Exemplo mais completo, misturando dados e atributos:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT
    Alunos.linha.value('.', 'varchar(60)') AS Aluno,

    Turmas.linha.value('@Nome','varchar(100)') AS Turma,
    Turmas.linha.value('@Serie','int') AS Serie,

    (CASE WHEN Alunos.linha.value('@Apostolo','varchar(60)') = '1' THEN 'SIM' ELSE 'NÃO' END) AS Apostolo,
    (CASE WHEN Alunos.linha.value('@Traidor','varchar(2)') = '1' THEN 'SIM' ELSE 'NÃO' END) AS Traidor
FROM
    @XML.nodes('/Escola/Turma') Turmas(linha)
    CROSS APPLY Turmas.linha.nodes('Aluno') Alunos(linha)

SQL Server - Read XML Data with Nodes function 2
SQL Server - Read XML Data with Nodes function 2

Utilizando XQuery (função XML.query)

Utilizando XQuery (função XML.query)

Muitas vezes ao manusear arquivos XML, sentimos a necessidade de realizar queries ou filtros mais avançados para retornar a informação que desejamos ao invés de todo o arquivo. Para essa necessidade, temos a função query:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT
    @XML.query('.') AS XML_Completo,
    @XML.query('Escola/Turma[1]/Aluno[1]') AS XML_Turma1_Aluno1,
    @XML.query('Escola/Turma[1]/Aluno[1]/text()') AS XML_Turma1_Aluno1_Nome,
    @XML.query('Escola/Turma[1]/Aluno[1]').value('.', 'varchar(100)') AS Turma1_Aluno1_Nome

SQL Server - XML XQuery query function
SQL Server - XML XQuery query function

Utilizando o XQuery para filtrar resultados:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT
    @XML.query('Escola/Turma[@Serie=''2'']') AS XML_Turma_2a_Serie,
    @XML.query('Escola/Turma[@Nome=''Turma 1'']') AS XML_Turma1,
    @XML.query('Escola/Turma/Aluno[@Traidor=''1'']') AS XML_Judas,
    @XML.query('Escola/Turma/Aluno[@Apostolo=''0'']') AS XML_Paulo,
    @XML.query('Escola/Turma/Aluno[.=''Pedro'']') AS XML_Pedro

SQL Server - XML XQuery query function filter data
SQL Server - XML XQuery query function filter data

Retornando as informações do aluno Pedro:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)
DECLARE @XML2 XML = (SELECT @XML.query('Escola/Turma/Aluno[.=''Pedro'']'))

SELECT 
    Alunos.linhas.value('.', 'varchar(100)') AS Nome,
    Alunos.linhas.value('@Nota', 'float') AS Nota,
    Alunos.linhas.value('@Apostolo', 'bit') AS Apostolo,
    Alunos.linhas.value('@Traidor', 'bit') AS Apostolo
FROM 
    @XML2.nodes('/Aluno') AS Alunos(linhas)

SQL Server - XML XQuery query function filter data 2
SQL Server - XML XQuery query function filter data 2

Trabalhando com valores:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

DECLARE @XML_Aprovados XML = (SELECT @XML.query('//Aluno[@Nota>=7]'))
DECLARE @XML_Reprovados XML = (SELECT @XML.query('//Aluno[@Nota<7]'))

SELECT 
    Alunos.linhas.value('.', 'varchar(100)') AS Nome,
    Alunos.linhas.value('@Nota', 'float') AS Nota,
    Alunos.linhas.value('@Apostolo', 'bit') AS Apostolo,
    Alunos.linhas.value('@Traidor', 'bit') AS Apostolo
FROM 
    @XML_Aprovados.nodes('/Aluno') AS Alunos(linhas)
ORDER BY
    Nota DESC

SELECT 
    Alunos.linhas.value('.', 'varchar(100)') AS Nome,
    Alunos.linhas.value('@Nota', 'float') AS Nota,
    Alunos.linhas.value('@Apostolo', 'bit') AS Apostolo,
    Alunos.linhas.value('@Traidor', 'bit') AS Apostolo
FROM 
    @XML_Reprovados.nodes('/Aluno') AS Alunos(linhas)
ORDER BY
    Nota

SQL Server - XML XQuery query function filter data 3
SQL Server - XML XQuery query function filter data 3

Uma coisa que se deve observar sobre a função query, é que ela não permite selecionar dados de atributos que estão no mesmo nível do nó atual. Exemplo, se você está utilizando um seletor de aluno, você não consegue retornar um atributo desse nó Aluno que você está selecionando. Caso você tente fazer isso, irá encontrar uma dessas mensagens de erro:

XQuery [Sqm.data.query()]: Attribute may not appear outside of an element

XQuery [value()]: Top-level attribute nodes are not supported

Para contornar isso, utilize a função value e aplique seus filtros de outra forma.

Utilizando funções no XML

Utilizando funções no XML

Um outro recurso muito bacana do XML é a possibilidade de utilizar funções para filtrar ou obter mais informações através dos dados do nosso XML. Vou demonstrar agora como aplicar isso aos nossos arquivos XML de exemplo.

Utilizando funções numéricas:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT 
    @XML.value('max(//Aluno/@Nota)', 'float') AS Maior_Nota,
    @XML.value('min(//Aluno/@Nota)', 'float') AS Menor_Nota,
    @XML.value('avg(//Aluno/@Nota)', 'float') AS Media_Nota,
    @XML.value('sum(//Aluno/@Nota)', 'float') AS Soma_Nota,
    @XML.value('count(//Aluno/@Nota)', 'int') AS Qtde_Nota,
    @XML.value('count(//Aluno[@Nota>=7]/@Nota)', 'int') AS Qtde_Nota_Maior7,
    @XML.value('count(//Aluno[@Nota<7]/@Nota)', 'int') AS Qtde_Nota_Menor7

SQL Server - XML XQuery query function filter data 4
SQL Server - XML XQuery query function filter data 4

Funções de string:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT 
    @XML.value('(//Aluno[.=''Pedro''])[1]', 'varchar(100)') AS Pedro,
    @XML.value('string-length((//Aluno[.=''Pedro''])[1])', 'varchar(100)') AS Tamanho_Nome_Pedro,
    @XML.value('concat((//Aluno[.=''Pedro''])[1], '' Teste'')', 'varchar(100)') AS [Concat],
    @XML.value('substring((//Aluno[.=''Pedro''])[1], 1, 1)', 'varchar(100)') AS [Primeira_Letra]

ou podemos utilizar o contains:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT 
    Alunos.linha.value('.', 'varchar(100)') AS Pedro,
    Alunos.linha.value('string-length((.))', 'varchar(100)') AS Tamanho_Nome_Pedro,
    Alunos.linha.value('concat((.), '' Teste'')', 'varchar(100)') AS [Concat],
    Alunos.linha.value('substring((.), 1, 1)', 'varchar(100)') AS [Primeira_Letra]
FROM
    @XML.nodes('//Aluno') AS Alunos(linha)
WHERE
    Alunos.linha.value('contains((.), "Pedro")', 'bit') = 1

e até mesmo operações simples de Transact-SQL:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT 
    Alunos.linha.value('.', 'varchar(100)') AS Pedro,
    Alunos.linha.value('string-length((.))', 'varchar(100)') AS Tamanho_Nome_Pedro,
    Alunos.linha.value('concat((.), '' Teste'')', 'varchar(100)') AS [Concat],
    Alunos.linha.value('substring((.), 1, 1)', 'varchar(100)') AS [Primeira_Letra]
FROM
    @XML.nodes('//Aluno') AS Alunos(linha)
WHERE
    Alunos.linha.value('.', 'varchar(100)') = 'Pedro'

SQL Server - XML XQuery query function filter data string
SQL Server - XML XQuery query function filter data string

Manipulando o XML com a função modify

Manipulando o XML com a função modify

Quando estamos utilizando a função modify, temos mais uma prova do quão avançado é o tratamento de XML com o SQL Server. Essa função nos permite modificar os dados armazenados na variável XML em tempo de execução, e ela nos permite inserir dados (insert), substituir dados (replace value of) e apagar dados (delete).

Para a utilização da função modify(), deve-se realizar UPDATE, DELETE ou SET @Variavel. A utilização dessa função durante um SELECT não é permitido.

Como substituir valores utilizando a função replace value of:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT @XML.value('(//Aluno[.="Pedro"]/@Nota)[1]', 'float') AS Nota_Antes

------------------------------------------------------
-- ALTERA O VALOR DA NOTA DO ALUNO "PEDRO"
------------------------------------------------------

DECLARE @Tabela_XML TABLE ( Dados XML )

INSERT INTO @Tabela_XML
SELECT Ds_Xml FROM #XML2

UPDATE @Tabela_XML
SET Dados.modify('replace value of (//Aluno[.="Pedro"]/@Nota)[1] with("8.5")')

SET @XML = (SELECT TOP 1 Dados FROM @Tabela_XML)

SELECT @XML.value('(//Aluno[.="Pedro"]/@Nota)[1]', 'float') AS Nota_Depois

SQL Server - XML XQuery replace value of modify function
SQL Server - XML XQuery replace value of modify function

Remover um elemento do XML utilizando a função delete:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SELECT Alunos.linha.value('.', 'varchar(100)') AS Todos_os_Alunos
FROM @XML.nodes('//Aluno') Alunos(linha)

------------------------------------------------------
-- REMOVE OS ALUNOS COM NOTA MENOR QUE 7
------------------------------------------------------

DECLARE @Tabela_XML TABLE ( Dados XML )

INSERT INTO @Tabela_XML
SELECT Ds_Xml FROM #XML2

UPDATE @Tabela_XML
SET Dados.modify('delete (//Aluno[@Nota<7])')

SET @XML = (SELECT TOP 1 Dados FROM @Tabela_XML)

SELECT 
    Alunos.linha.value('.', 'varchar(100)') AS Alunos_Aprovados,
    Alunos.linha.value('@Nota', 'float') AS Nota
FROM @XML.nodes('//Aluno') Alunos(linha)

SQL Server - XML XQuery delete modify function
SQL Server - XML XQuery delete modify function

Inserir nós no XML utilizando a função insert:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

SET @XML.modify('insert <Aluno Apostolo="0" Traidor="0" Nota="2.6">Dirceu Turma 1</Aluno>
into (//Turma)[1]')

SET @XML.modify('insert <Aluno Apostolo="0" Traidor="0" Nota="2.9">Dirceu Turma 2</Aluno>
into (//Turma)[2]')

SELECT 
    Alunos.linha.value('.', 'varchar(100)') AS Alunos_Aprovados,
    Alunos.linha.value('@Nota', 'float') AS Nota
FROM @XML.nodes('//Aluno') Alunos(linha)

SQL Server - XML XQuery insert modify function
SQL Server - XML XQuery insert modify function

Inserir vários nós no XML a partir de uma variável SQL:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

DECLARE @Inserir XML = '
<Turma Nome="Turma 3" Serie="3">
    <Aluno Apostolo="0" Traidor="0" Nota="2.6">Dirceu 1</Aluno>
    <Aluno Apostolo="0" Traidor="0" Nota="2.6">Dirceu 2</Aluno>
</Turma>'

SET @XML.modify('insert sql:variable("@Inserir")
into (//Escola)[1]')

SELECT 
    Alunos.linha.value('.', 'varchar(100)') AS Alunos,
    Alunos.linha.value('@Nota', 'float') AS Nota,
    Alunos.linha.value('../@Nome', 'varchar(100)') AS Turma
FROM 
    @XML.nodes('//Aluno') Alunos(linha)

SQL Server - XML XQuery insert sql variable modify function
SQL Server - XML XQuery insert sql variable modify function

Utilizando expressões FLOWR do XQuery no XML

Utilizando expressões FLOWR do XQuery no XML

O recurso FLOWR (se pronuncia “flower”) é uma poderosa ferramenta e extensão da função QUERY, que permite realizar iterações em arquivos XML e realizar uma série de operações. Os comandos são FOR, LET, ORDER BY, WHERE e RETURN. Vamos ver abaixo como utilizá-los.

Utilizando a iteração FOR:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

DECLARE @XML2 XML = (SELECT @XML.query('for $A in //Aluno[@Nota>=7] return $A'))

SELECT
    Alunos.linha.value('.', 'varchar(100)') AS Nome,
    Alunos.linha.value('@Nota', 'float') AS Nota
FROM
    @XML2.nodes('//Aluno') Alunos(linha)

SQL Server - XML XQuery FLOWR expression query function for return
SQL Server - XML XQuery FLOWR expression query function for return

Utilizando a iteração FOR, WHERE e ORDER BY:

DECLARE @XML XML = (SELECT TOP 1 Ds_XML FROM #XML2)

DECLARE @XML2 XML = (SELECT @XML.query('
for $A 
in //Aluno
where ($A = "Pedro" or $A = "Marcos" or $A = "Andre" or $A = "Joao")
order by $A descending
return $A'))

SELECT
    Alunos.linha.value('.', 'varchar(100)') AS Nome,
    Alunos.linha.value('@Nota', 'float') AS Nota
FROM
    @XML2.nodes('//Aluno') Alunos(linha)

SQL Server - XML XQuery FLOWR expression query function for return where order by
SQL Server - XML XQuery FLOWR expression query function for return where order by

Utilizando o comando LET para alterar o valor de uma informação e dar um aumento de 20% para dois funcionários:
Nesse exemplo específico, eu crio o XML em tempo real, pois os meus 2 XML de exemplo não possuíam valores em dados, apenas em atributos, o que impossibilita a utilização junto com a função query.

DECLARE @XML XML = '
<Empresa>
    <Funcionario>
        <Nome>Joao</Nome>
        <Salario>1250.28</Salario>
    </Funcionario>
    <Funcionario>
        <Nome>Pedro</Nome>
        <Salario>1754.54</Salario>
    </Funcionario>
    <Funcionario>
        <Nome>Paulo</Nome>
        <Salario>5487.99</Salario>
    </Funcionario>
</Empresa>'


DECLARE @XML_Modificado XML = (SELECT @XML.query('
for $A
in //Funcionario
let $T := 1.20
where $A/Nome != "Pedro"
return 
<Funcionario>
    <Nome>{data($A/Nome)}</Nome>
    <NovoSalario>{data($A/Salario/text())[1] * data($T)}</NovoSalario>
</Funcionario>'))

SELECT
    Funcionarios.linha.value('Nome[1]', 'varchar(100)') AS Nome,
    Funcionarios.linha.value('NovoSalario[1]', 'numeric(18,2)') AS NovoSalario
FROM
    @XML_Modificado.nodes('//Funcionario') Funcionarios(linha)

SQL Server - XML XQuery FLOWR expression query function for return where order by let
SQL Server - XML XQuery FLOWR expression query function for return where order by let

Exportando dados do banco para XML - FOR XML

Exportando dados do banco para XML – FOR XML RAW, AUTO, EXPLICIT, PATH

Depois de muito se falar sobre importação e tratamento de arquivos XML, finalmente chegou a hora de fazer o caminho inverso. Como transformar dados de uma tabela do SQL Server para uma string XML.

Para facilitar essa tarefa, o SQL Server disponibiliza 4 formas de se fazer isso com o FOR XML: RAW, AUTO, EXPLICI e PATH. Vamos descobrir agora para que serve e como utilizá-los.

Criação da tabela de testes:

IF (OBJECT_ID('dbo.Teste_XML') IS NOT NULL) DROP TABLE dbo.Teste_XML
CREATE TABLE dbo.Teste_XML (
    Categoria VARCHAR(100),
    Descricao VARCHAR(100)
)

INSERT INTO dbo.Teste_XML ( Categoria, Descricao )
VALUES ('Brinquedo', 'Bola'),
('Brinquedo', 'Carrinho'),
('Brinquedo', 'Boneco'),
('Brinquedo', 'Jogo'),
('Cama e Mesa', 'Toalha'),
('Cama e Mesa', 'Edredom'),
('Informatica', 'Teclado'),
('Informatica', 'Mouse'),
('Informatica', 'HD'),
('Informatica', 'CPU'),
('Informatica', 'Memoria'),
('Informatica', 'Placa-Mae'),
(NULL, 'TV')

FOR XML RAW

O método RAW do FOR XML gera um XML da nossa tabela onde cada coluna se transforma em atributo do XML gerado e cada linha irá representar um nó (elemento).

SELECT *
FROM dbo.Teste_XML
FOR XML RAW

XML gerado:

<row Categoria="Brinquedo" Descricao="Bola" />
<row Categoria="Brinquedo" Descricao="Carrinho" />
<row Categoria="Brinquedo" Descricao="Boneco" />
<row Categoria="Brinquedo" Descricao="Jogo" />
<row Categoria="Cama e Mesa" Descricao="Toalha" />
<row Categoria="Cama e Mesa" Descricao="Edredom" />
<row Categoria="Informatica" Descricao="Teclado" />
<row Categoria="Informatica" Descricao="Mouse" />
<row Categoria="Informatica" Descricao="HD" />
<row Categoria="Informatica" Descricao="CPU" />
<row Categoria="Informatica" Descricao="Memoria" />
<row Categoria="Informatica" Descricao="Placa-Mae" />
<row Descricao="TV" />

No exemplo abaixo, vou acrescer a opção ROOT após o RAW(), para adicionar um elemento raiz que irá ser o nó pai dos nós criados. Isso é opcional, se você não utilizar, a única diferença é que o elemento <root> não será criado.

SELECT *
FROM dbo.Teste_XML
FOR XML RAW('Produto'), ROOT('Produtos')

XML gerado:

<Produtos>
  <Produto Categoria="Brinquedo" Descricao="Bola" />
  <Produto Categoria="Brinquedo" Descricao="Carrinho" />
  <Produto Categoria="Brinquedo" Descricao="Boneco" />
  <Produto Categoria="Brinquedo" Descricao="Jogo" />
  <Produto Categoria="Cama e Mesa" Descricao="Toalha" />
  <Produto Categoria="Cama e Mesa" Descricao="Edredom" />
  <Produto Categoria="Informatica" Descricao="Teclado" />
  <Produto Categoria="Informatica" Descricao="Mouse" />
  <Produto Categoria="Informatica" Descricao="HD" />
  <Produto Categoria="Informatica" Descricao="CPU" />
  <Produto Categoria="Informatica" Descricao="Memoria" />
  <Produto Categoria="Informatica" Descricao="Placa-Mae" />
  <Produto Descricao="TV" />
</Produtos>

Mesmo no XML RAW podemos fazer com que as colunas sejam retornadas como elementos do XML. Para isso, basta incluir a opção ELEMENTS:

SELECT *
FROM dbo.Teste_XML
FOR XML RAW('Produto'), ROOT('Produtos'), ELEMENTS

XML gerado (apenas um trecho para não ficar muito grande):

<Produtos>
  <Produto>
    <Categoria>Brinquedo</Categoria>
    <Descricao>Bola</Descricao>
  </Produto>
  <Produto>
    <Categoria>Brinquedo</Categoria>
    <Descricao>Carrinho</Descricao>
  </Produto>
  <Produto>
    <Categoria>Brinquedo</Categoria>
    <Descricao>Boneco</Descricao>
  </Produto>
</Produtos>

Uma outra opção interessante do FOR XML é quando tratamos de dados vazios (NULL). Quando não realizamos nenhum tratamento, eles simplesmente são ignorados e não são gerados, como é o caso da categoria do produto “TV”. Para tratar isso, podemos utilizar a opção XSINIL após a opção ELEMENTS, que vai adicionar o elemento vazio e criar um atributo informando isso (xsi:nil=”true”):

SELECT *
FROM dbo.Teste_XML
FOR XML RAW('Produto'), ROOT('Produtos'), ELEMENTS XSINIL

E aí o XML com o produto é gerado assim:

<Produto>
    <Categoria xsi:nil="true" />
    <Descricao>TV</Descricao>
</Produto>

Além disso, podemos utilizar a opção XMLSCHEMA para transformar nosso XML em um XSD completo:

SELECT *
FROM dbo.Teste_XML
FOR XML RAW('Produto'), ROOT('Produtos'), ELEMENTS XSINIL, XMLSCHEMA

SQL Server - FOR XML AUTO XMLSCHEMA
SQL Server - FOR XML AUTO XMLSCHEMA

FOR XML AUTO

O modo AUTO do FOR XML é bem parecido com o RAW, mas com a diferença que na sua utilização padrão, o nome da tabela é o nome padrão de cada elemento.

SELECT *
FROM dbo.Teste_XML
FOR XML AUTO

XML gerado:

<dbo.Teste_XML Categoria="Brinquedo" Descricao="Bola" />
<dbo.Teste_XML Categoria="Brinquedo" Descricao="Carrinho" />
<dbo.Teste_XML Categoria="Brinquedo" Descricao="Boneco" />
<dbo.Teste_XML Categoria="Brinquedo" Descricao="Jogo" />
<dbo.Teste_XML Categoria="Cama e Mesa" Descricao="Toalha" />
<dbo.Teste_XML Categoria="Cama e Mesa" Descricao="Edredom" />
<dbo.Teste_XML Categoria="Informatica" Descricao="Teclado" />
<dbo.Teste_XML Categoria="Informatica" Descricao="Mouse" />
<dbo.Teste_XML Categoria="Informatica" Descricao="HD" />
<dbo.Teste_XML Categoria="Informatica" Descricao="CPU" />
<dbo.Teste_XML Categoria="Informatica" Descricao="Memoria" />
<dbo.Teste_XML Categoria="Informatica" Descricao="Placa-Mae" />
<dbo.Teste_XML Descricao="TV" />

FOR XML PATH

O XML PATH é um pouco diferente dos outros dois exemplos, pois o nome de colunas e aliases são tratados como elementos XPATH. Quando você gera um XML comum, sem personalizar, ele inclui um elemento raiz (row), onde cada linha é um elemento filho, um nível de hierarquia abaixo e cada coluna é também um elemento do XML, mais outro nível abaixo:

SQL Server - FOR XML PATH 1
SQL Server - FOR XML PATH 1

Assim como o XML AUTO e XML RAW, podemos utilizar ROOT(‘NomeDaRaiz’) para criar o elemento raiz, podemos utilizar também a opção ELEMENTS com XSINIL também (para retornar elementos mesmo nulos).

Obs: Como o XML PATH sempre retorna as colunas como elementos, utilizar apenas a opção ELEMENTS não irá fazer efeito, apenas se utilizada em conjunto com a XSINIL.

Criando hierarquias com o XML PATH:

SELECT 
    Id AS '@Id_Produto', -- Atributo
    Categoria AS 'DadosProduto/Categoria', -- Elemento
    Descricao AS 'DadosProduto/Descricao'-- Elemento
FROM 
    dbo.Teste_XML
FOR XML PATH('Produto'), ROOT('Produtos'), ELEMENTS

XML gerado:

SQL Server - FOR XML PATH 2
SQL Server - FOR XML PATH 2

FOR XML EXPLICIT

O modo EXPLICIT do FOR XML já tende a ser bem diferente dos outros modos. Isso ocorre porque ele exige um cabeçalho em um formato específico, definindo hierarquia e estruturas. Esse cabeçalho deve ter unido aos dados utilizando UNION ALL.

O SELECT do cabeçalho deve possuir a seguinte estrutura:

  • Primeira coluna: É um número que define o nível da hierarqua. O nome da coluna obrigatoriamente deve ser Tag.
  • Segunda coluna: É um número que define o nível da hierarqua do elemento pai (ou NULL, caso não tenha e seja a raiz). O nome da coluna obrigatoriamente deve ser Parent.
  • Terceira coluna em diante: Sâo os dados que farão parte do XML e serão retornados pelo seu XML.

Note que precisamos desde o início definir todas as colunas que farão parte do XML já no cabeçalho.

O formato padrão para definição de campo é definido da seguinte forma:

<NomeDoElemento>!<NúmeroDaTag>!<NomeDoAtributo>[!<InformacoesAdicionais>]

Onde:

  • NomeDoElemento: É o nome do elemento pai que estamos gerando (No caso do exemplo, Produtos)
  • NúmeroDaTag: É o número do nível da hierarquia dos elementos filhos
  • NomeDoAtributo: É o nome de cada atributo/coluna dos dados que estamos exportando para XML (no caso do exemplo, Categoria e Descricao)
  • InformacoesAdicionais: Dados adicionais que podem ser utilizados na construção do XML

Vamos ver agora como isso funciona na prática:

-- Cabeçalho
SELECT 
    1 AS Tag,
   NULL AS Parent,
   NULL AS [Produtos!1!Id],
   NULL AS [Produto!2!Categoria!ELEMENT],
   NULL AS [Produto!2!Descricao!ELEMENT]

UNION ALL

-- Conteúdo
SELECT 
    2 AS Tag,
    1 AS Parent,
    NULL,
    Categoria,
    Descricao
FROM 
    dbo.Teste_XML
FOR	
    XML EXPLICIT

Trecho do XML retornado:

SQL Server - FOR XML EXPLICIT
SQL Server - FOR XML EXPLICIT

Pro exemplo ficar mais legal, vou adicionar uma nova coluna na nossa tabela:

ALTER TABLE dbo.Teste_XML ADD Id INT IDENTITY(1,1)

Gerando novamente o XML:

-- Cabeçalho
SELECT 
    1 AS Tag,
    NULL AS Parent,
    Id AS [Produtos!1!Id_Produto],
    NULL AS [Produto!2!Categoria!ELEMENT],
    NULL AS [Produto!2!Descricao!ELEMENT]
FROM
    dbo.Teste_XML

UNION ALL

-- Conteúdo
SELECT 
    2 AS Tag,
    1 AS Parent,
    Id AS Id_Produto,
    ISNULL(Categoria, ''),
    Descricao
FROM 
    dbo.Teste_XML
ORDER BY 
    [Produtos!1!Id_Produto], 
    [Produto!2!Categoria!ELEMENT]
FOR	
    XML EXPLICIT

Nosso resultado será este:

SQL Server - FOR XML EXPLICIT 2
SQL Server - FOR XML EXPLICIT 2

Vale ressaltar que esse ORDER BY utilizado é necessário para que os resultados sejam exibidos na forma correta. Caso contrário, os elementos serão gerados na ordem errada e o XML não terá o mesmo resultado.

Como podemos observar, o novo campo adicionado (ID) é um atributo do XML. Caso você queira transformá-lo em um atributo, basta adicionar !ELEMENT no seu cabeçalho:

-- Cabeçalho
SELECT 
    1 AS Tag,
    NULL AS Parent,
    Id AS [Produtos!1!Id_Produto!ELEMENT],
    NULL AS [Produto!2!Categoria!ELEMENT],
    NULL AS [Produto!2!Descricao!ELEMENT]
FROM
    dbo.Teste_XML

UNION ALL

-- Conteúdo
SELECT 
    2 AS Tag,
    1 AS Parent,
    Id AS Id_Produto,
    ISNULL(Categoria, ''),
    Descricao
FROM 
    dbo.Teste_XML
ORDER BY 
    [Produtos!1!Id_Produto!ELEMENT], 
    [Produto!2!Categoria!ELEMENT]
FOR	
    XML EXPLICIT

Ficando dessa forma:

SQL Server - FOR XML EXPLICIT 3
SQL Server - FOR XML EXPLICIT 3

Nos dois exemplos acima, eu adicionei uma cláusula ISNULL() na categoria, uma vez que o produto “TV” não tem categoria definida. Quando isso ocorre no modo EXPLICIT, e a categoria é utilizada para ordenar os resultados, os elementos acabam se perdendo e os sem categoria ficam juntos com elementos de outras categorias.

SQL Server - FOR XML EXPLICIT 5
SQL Server - FOR XML EXPLICIT 5

Uma outra forma de resolver este problema, além de adicionar os ISNULL(), é definindo o tipo de atributo ELEMENTXSINIL no cabeçalho da estrutura do nosso SELECT e não utilizando essa coluna que tem possíveis valores NULL no ORDER BY:

-- Cabeçalho
SELECT 
    1 AS Tag,
    NULL AS Parent,
    Id AS [Produtos!1!Id_Produto!ELEMENT],
    NULL AS [Produto!2!Categoria!ELEMENTXSINIL],
    NULL AS [Produto!2!Descricao!ELEMENT]
FROM
    dbo.Teste_XML

UNION ALL

-- Conteúdo
SELECT 
    2 AS Tag,
    1 AS Parent,
    Id AS Id_Produto,
    Categoria,
    Descricao
FROM 
    dbo.Teste_XML
ORDER BY 
    [Produtos!1!Id_Produto!ELEMENT], 
    [Produto!2!Descricao!ELEMENT]
FOR	
    XML EXPLICIT

XML gerado:

SQL Server - FOR XML EXPLICIT 4
SQL Server - FOR XML EXPLICIT 4

Caso você tenha alguma dúvida ou sugestão, deixe aqui nos comentários.
Obrigado pela visita e até a próxima!

sql server como aprender trabalhar usar ler importar tratar xml string arquivo learning

sql server como aprender trabalhar usar ler importar tratar xml string arquivo learning