Print XML Node Name + XPATH - sql-server

Is it possible in SQL SERVER to Query the XML in such a way that if the input XML was the one below:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<genRetrieve xmlns:v1="http://xxxxxxxxxxxxxxxxxxxxx">
<checkRetrieve>
<party>
<user>
<first>BLA</first>
<last>last</last>
</user>
<media /> --This element will also need to be picked in output
</party>
</checkRetrieve>
</genRetrieve>
</soapenv:Body>
</soapenv:Envelope>
Produce a table that has the text nodes/elements and their corresponding XPATH in a table?
TEXT NODE XPATH
--------- ---------
first /soapenv:Envelope/soapenv:Body/genRetrieve/checkRetrieve/party/user/first
last /soapenv:Envelope/soapenv:Body/genRetrieve/checkRetrieve/party/user/last
media /soapenv:Envelope/soapenv:Body/genRetrieve/checkRetrieve/party/media

Solution via openxml.
declare #idoc int, #doc varchar(max);
set #doc =
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<genRetrieve xmlns:v1="http://xxxxxxxxxxxxxxxxxxxxx">
<checkRetrieve>
<party>
<user>
<first>BLA</first>
<last>last</last>
</user>
<media>none</media>
</party>
</checkRetrieve>
</genRetrieve>
</soapenv:Body>
</soapenv:Envelope>'
exec sp_xml_preparedocument #idoc output, #doc;
;with map as (
select *
from openxml (#idoc, '//*')
), rcte as (
select localname, parentid, '/' + isnull (prefix + ':', '') + localname as XPATH
from openxml (#idoc, '//*[text()]')
where nodetype = 1 and [text] is not null -- localname <> '#text'
union all
select r.localname, m.parentid, '/' + isnull (prefix + ':', '') + m.localname + XPATH
from rcte r
inner join map m on r.parentid = m.id
)
select localname as [TEXT NODE], XPATH
from rcte
where parentid is null;
exec sp_xml_removedocument #idoc;
UPD. Solution with nodes numeration.
exec sp_xml_preparedocument #idoc output, #doc;
;with map as (
select id, parentid, nodetype, localname, prefix, row_number() over(partition by parentid, prefix, localname order by id) as num
from openxml (#idoc, '//*')
where nodetype = 1 or (nodetype = 3 and [text] is not null)
), rcte as (
select p.localname, p.parentid, '/' + isnull (p.prefix + ':', '') + p.localname + '[' + cast (p.num as varchar(50)) + ']' as XPATH
from map c
inner join map p on c.parentid = p.id
where c.nodetype = 3
union all
select r.localname, m.parentid, '/' + isnull (prefix + ':', '') + m.localname + '[' + cast (m.num as varchar(50)) + ']' + XPATH
from rcte r
inner join map m on r.parentid = m.id
)
select localname as [TEXT NODE], XPATH
from rcte
where parentid is null;
exec sp_xml_removedocument #idoc;
UPD 2. Yet another solution showing VALUE and nodes without text.
exec sp_xml_preparedocument #idoc output, #doc;
;with map as (
select id, parentid, nodetype, localname, prefix, [text]
, row_number() over(partition by parentid, prefix, localname order by id) as num
from openxml (#idoc, '//*')
where nodetype = 1 or (nodetype = 3 and [text] is not null)
)
, rcte as (
select localname, parentid, '/' + isnull (prefix + ':', '') + localname + '[' + cast (num as varchar(50)) + ']' as XPATH, VALUE
from (
select p.localname, p.parentid, p.prefix, p.num
, min (c.nodetype) as min_nodetype
, min (case when c.nodetype = 3 then cast (c.[text] as nvarchar(max)) end) as VALUE
from map p
left join map c on p.id = c.parentid
where p.nodetype = 1
group by p.localname, p.parentid, p.prefix, p.num
) t
where min_nodetype = 3 or min_nodetype is null
union all
select r.localname, m.parentid, '/' + isnull (prefix + ':', '') + m.localname + '[' + cast (m.num as varchar(50)) + ']' + XPATH, VALUE
from rcte r
inner join map m on r.parentid = m.id
)
select localname as [TEXT NODE], XPATH, VALUE
from rcte
where parentid is null;
exec sp_xml_removedocument #idoc;

Related

Converting highly complex XML structure to table

I have a stored procedure that spits out two columns. One is an ID and the other column is a highly complex XML format for that ID. My requirement is to convert this XML to table format. The XML has 300 nodes and sub nodes. The structure looks like:
<Main>
<Version>1.0</Version>
<CId>459876569</CId>
<Overview>
<Type>Y</Type>
<CreateDate>20180505</CreateDate>
<PlanType>A</PlanType>
<EffectiveDate>20171201</EffectiveDate>
<EndDate>20181130</EndDate>
<Comments>No other comments</Comments>
</Overview>
<EssentialInfo>
<ContactI>
<LastName>Doe</LastName>
<MiddleName>A</MiddleName>
<FirstName>John</FirstName>
<DateOfBirth>19500808</DateOfBirth>
<Gender>F</Gender>
<Address>
<AddressLine1>dfsfsdf</AddressLine1>
<AddressLine2>dsfsdfa</AddressLine2>
<City>gdfgdfg</City>
</Address>
<HomePhone>98745632148</HomePhone>
</Contact>
</EssentialInfo>
</Main>
I am aware of the OPENXML method but naming the 300 columns make it tedious. Any other method to resolve this?
I am trying to implement Workflow suggestion - XML & SQL this functionality and to do so I am trying to convert my huge XML to SQL table
If open to a TVF as a helper function, and assuming the XML is to be pivoted to ONE record.
Clearly the fully declared SQL would be more performant.
Example
Declare #XML xml ='
<Main>
<Version>1.0</Version>
<CId>459876569</CId>
<Overview>
<Type>Y</Type>
<CreateDate>20180505</CreateDate>
<PlanType>A</PlanType>
<EffectiveDate>20171201</EffectiveDate>
<EndDate>20181130</EndDate>
<Comments>No other comments</Comments>
</Overview>
<EssentialInfo>
<Contact>
<LastName>Doe</LastName>
<MiddleName>A</MiddleName>
<FirstName>John</FirstName>
<DateOfBirth>19500808</DateOfBirth>
<Gender>F</Gender>
<Address>
<AddressLine1>dfsfsdf</AddressLine1>
<AddressLine2>dsfsdfa</AddressLine2>
<City>gdfgdfg</City>
</Address>
<HomePhone>98745632148</HomePhone>
</Contact>
</EssentialInfo>
</Main>
'
Select * Into #Temp from [dbo].[tvf-XML-Hier](#XML) Order by R1
Declare #SQL varchar(max) = '
Select *
From (
Select Item = concat(Element,IIF(Attribute='''','''',''_''+Attribute))
,Value
From #Temp
) A
Pivot (max([Value]) For [Item] in (' + Stuff((Select ','+QuoteName(concat(Element,IIF(Attribute='','','_'+Attribute)))
From #Temp
Where Value is not null
Order by R1
For XML Path('')),1,1,'') + ') ) p'
Exec(#SQL);
Returns
The TVF if Interested
ALTER FUNCTION [dbo].[tvf-XML-Hier](#XML xml)
Returns Table
As Return
with cte0 as (
Select Lvl = 1
,ID = Cast(1 as int)
,Pt = Cast(NULL as int)
,Element = x.value('local-name(.)','varchar(150)')
,Attribute = cast('' as varchar(150))
,Value = x.value('text()[1]','varchar(max)')
,XPath = cast(concat(x.value('local-name(.)','varchar(max)'),'[' ,cast(Row_Number() Over(Order By (Select 1)) as int),']') as varchar(max))
,Seq = cast(1000000+Row_Number() over(Order By (Select 1)) as varchar(max))
,AttData = x.query('.')
,XMLData = x.query('*')
From #XML.nodes('/*') a(x)
Union All
Select Lvl = p.Lvl + 1
,ID = Cast( (Lvl + 1) * 1024 + (Row_Number() Over(Order By (Select 1)) * 2) as int ) * 10
,Pt = p.ID
,Element = c.value('local-name(.)','varchar(150)')
,Attribute = cast('' as varchar(150))
,Value = cast( c.value('text()[1]','varchar(max)') as varchar(max) )
,XPath = cast(concat(p.XPath,'/',c.value('local-name(.)','varchar(max)'),'[',cast(Row_Number() Over(PARTITION BY c.value('local-name(.)','varchar(max)') Order By (Select 1)) as int),']') as varchar(max) )
,Seq = cast(concat(p.Seq,' ',10000000+Cast( (Lvl + 1) * 1024 + (Row_Number() Over(Order By (Select 1)) * 2) as int ) * 10) as varchar(max))
,AttData = c.query('.')
,XMLData = c.query('*')
From cte0 p
Cross Apply p.XMLData.nodes('*') b(c)
)
, cte1 as (
Select R1 = Row_Number() over (Order By Seq),A.*
From (
Select Lvl,ID,Pt,Element,Attribute,Value,XPath,Seq From cte0
Union All
Select Lvl = p.Lvl+1
,ID = p.ID + Row_Number() over (Order By (Select NULL))
,Pt = p.ID
,Element = p.Element
,Attribute = x.value('local-name(.)','varchar(150)')
,Value = x.value('.','varchar(max)')
,XPath = p.XPath + '/#' + x.value('local-name(.)','varchar(max)')
,Seq = cast(concat(p.Seq,' ',10000000+p.ID + Row_Number() over (Order By (Select NULL)) ) as varchar(max))
From cte0 p
Cross Apply AttData.nodes('/*/#*') a(x)
) A
)
Select A.R1
,R2 = IsNull((Select max(R1) From cte1 Where Seq Like A.Seq+'%'),A.R1)
,A.Lvl
,A.ID
,A.Pt
,A.Element
,A.Attribute
,A.XPath
,Title = Replicate('|---',Lvl-1)+Element+IIF(Attribute='','','#'+Attribute)
,A.Value
From cte1 A
/*
Source: http://beyondrelational.com/modules/2/blogs/28/posts/10495/xquery-lab-58-select-from-xml.aspx
Declare #XML xml='<person><firstname preferred="Annie" nickname="BeBe">Annabelle</firstname><lastname>Smith</lastname></person>'
Select * from [dbo].[tvf-XML-Hier](#XML) Order by R1
*/
If it helps with the Visualization, the TVF returns
Clearly this can be thinned down if you don't need all the columns

T-SQL: Compare 2 XML and return nodes with different values

I have 2 xml variables (#xml1 & #xml2) and I need to compare the values in each node and return an xml that only contains the nodes that are different.
So say, I have these 2 XML variables:
declare #xml1 xml = N'<row>
<id>1</id>
<name>record title</name>
<description>sample description</description>
</row>'
declare #xml2 xml = N'<row>
<id>1</id>
<name>record title</name>
<description>updated sample description</description>
</row>'
I'd like it to return:
SELECT #xml1 = N'<row>
<description>sample description</description>
</row>', #xml2 = N'<row>
<description>updated sample description</description>
</row>'
I have the following query that returns sort of what I need, but its UNpivoted & doesn't have the xml nodes, etc.:
SELECT
NV.NodeName,
OV.NodeValue OldValue,
NV.NodeValue NewValue
FROM
(SELECT T.N.value('local-name(.)', 'nvarchar(100)') NodeName,
T.N.value('.', 'nvarchar(100)') NodeValue
FROM #xml1.nodes('/row/*') T(N)) NV
CROSS APPLY (SELECT T.N.value('local-name(.)', 'nvarchar(100)') NodeName,
T.N.value('.', 'nvarchar(100)') NodeValue
FROM #xml2.nodes('/row/*') T(N)) OV
WHERE OV.NodeName = NV.NodeName AND OV.NodeValue <> NV.NodeValue
PERFORMANCE IS KEY: This code is in a trigger, so it needs to run fast and I'm afraid of using PIVOT since it tends to be slow. I'm hoping there's a better way to compare and return the values already as an xml value.
Any ideas?
Thanks in advance!
Nevermind - figured it out =)
In case this helps anyone down the road:
DECLARE #XMLString1 VARCHAR(MAX) = '',
#XMLString2 VARCHAR(MAX) = '';
SELECT #XMLString1 += '<' + OV.NodeName + '>' + LEFT(OV.NodeValue, 8000) + '</' + OV.NodeName + '>',
#XMLString2 += '<' + NV.NodeName + '>' + LEFT(NV.NodeValue, 8000) + '</' + NV.NodeName + '>'
FROM
(SELECT T.N.value('local-name(.)', 'varchar(255)') NodeName,
T.N.value('.', 'varchar(8000)') NodeValue
FROM #xml1.nodes('/row/*') T(N)) NV
CROSS APPLY
(SELECT T.N.value('local-name(.)', 'varchar(255)') NodeName,
T.N.value('.', 'varchar(8000)') NodeValue
FROM #xml2.nodes('/row/*') T(N)) OV
WHERE OV.NodeName = NV.NodeName AND OV.NodeValue <> NV.NodeValue
SET #XMLString1 = '<row>' + ISNULL(#XMLString1, '') + '</row>';
SET #XMLString2 = '<row>' + ISNULL(#XMLString2, '') + '</row>';
SELECT CONVERT(XML, #XMLString1) xml1, CONVERT(XML, #XMLString2) xml2

How to split required text from SQL Server table columns

I used a query which splits the texts in a column in one table and retrieves the corresponding id's from another table.
Here is the query:
DECLARE #X XML
DECLARE #STR VARCHAR(MAX) = ''
SELECT #STR = #STR + ';' + P_AUTHOR
FROM sub_aminer_paper
WHERE PID = 4
ORDER BY PID
SELECT #STR = substring(#STR,2,len(#STR))
SELECT #X = CONVERT(xml,' <root> <s>' + REPLACE(#STR,';','</s> <s>') + '</s> </root> ')
SELECT aid as [Author_id], name as [Author_Name]
FROM sub_aminer_author s
INNER JOIN
(SELECT
row_number() OVER(ORDER BY (SELECT null)) AS rn,
T.c.value('.','varchar(max)') AS value
FROM
#X.nodes('/root/s') T(c) ) t ON t.value = s.name
ORDER BY rn
If column p_author contains this value i.e.
Sushil Jajodia;Peter A. Ng;Frederick N. Springsteel
Then this query gives following output i.e.
Author_id Author_Name
578328 Sushil Jajodia
865779 Peter A. Ng
669143 Frederick N. Springsteel
Now I want to have Author_id as skipping first author id e.g. I need output like this:
Author_id Author_Name
865779 Peter A. Ng
669143 Frederick N. Springsteel
Then also I have to insert these Author_id's in another table. Please help regarding this query.
Just add WHERE rn>1
DECLARE #X XML
DECLARE #STR VARCHAR(MAX)=''
SELECT #STR = #STR+';'+P_AUTHOR
FROM sub_aminer_paper
WHERE PID = 4
ORDER BY PID
SELECT #STR = substring(#STR,2,len(#STR))
SELECT #X = CONVERT(xml,' <root> <s>' + REPLACE(#STR,';','</s> <s>') + '</s> </root> ')
SELECT aid as [Author_id], name as [Author_Name]
FROM sub_aminer_author s
INNER JOIN (SELECT row_number()
OVER(ORDER BY (SELECT null)) AS rn, T.c.value('.','varchar(max)') AS value
FROM #X.nodes('/root/s') T(c)
) t ON t.value = s.name
WHERE rn>1
ORDER BY rn

How to recursively get XPath of a node using SQL Server?

I'm tired of looking at possibly the ugliest SQL statement I've ever built and need your help. I am searching through an XML document for various elements and want to see their XPaths. The query below works by brute force, but I cannot come up with a way to create a function or CTE that will properly support N levels.
declare #article xml = '<article>
<front>
<article-meta>
<title-group>
<article-title>Update on ...</article-title>
</title-group>
</article-meta>
</front>
<back>
<ref-list>
<ref id="R1">
<citation citation-type="journal">
<article-title>Retrospective study of ...</article-title>
</citation>
</ref>
</ref-list>
</back>
</article>'
SELECT
Cast(T.r.query('local-name(parent::*/parent::*/parent::*/parent::*/parent::*/parent::*)') AS varchar(max)) + '/' +
Cast(T.r.query('local-name(parent::*/parent::*/parent::*/parent::*/parent::*)') AS varchar(max)) + '/' +
Cast(T.r.query('local-name(parent::*/parent::*/parent::*/parent::*)') AS varchar(max)) + '/' +
Cast(T.r.query('local-name(parent::*/parent::*/parent::*)') AS varchar(max)) + '/' +
Cast(T.r.query('local-name(parent::*/parent::*)') AS varchar(max)) + '/' +
Cast(T.r.query('local-name(parent::*)') AS varchar(max)) AS ThePath,
Cast(T.r.query('local-name(.)') AS varchar(max)) AS TheElement,
T.r.query('.') AS TheXml
FROM #article.nodes('//article-title') T(r)
Result:
ThePath TheElement
//article/front/article-meta/title-group article-title
/article/back/ref-list/ref/citation article-title
What I really want:
SELECT
x.RowId,
dbo.GetXPath(T.r.query('.')) AS ThePath, -- <---- Magic function goes here
T.r.query('.') AS TheXml
FROM dbo.InputFormatXml x
JOIN dbo.InputFormat f
ON F.InputFormatId = x.InputFormatId
CROSS APPLY TheData.nodes('//article-title') T(r)
WHERE F.Description = 'NLM';
DECLARE #idoc int;
EXEC sp_xml_preparedocument #idoc OUTPUT, #article;
SELECT ISNULL(id,'') id, parentid, localname
INTO #nodetree
FROM OPENXML(#idoc,'/',3)
WHERE nodetype = 1;
EXEC sp_xml_removedocument #idoc;
ALTER TABLE #nodetree ADD PRIMARY KEY (id);
WITH cte AS (
SELECT
parentid
,CAST('/' AS varchar(max)) + localname AS xpath
FROM #nodetree WHERE localname = 'article-title'
UNION ALL
SELECT
parent.parentid
,CAST('/' AS varchar(max)) + localname + xpath
FROM cte AS node
INNER JOIN #nodetree parent on parent.id = node.parentid
)
SELECT xpath
FROM cte
WHERE parentid IS NULL
Recursing down instead of up:
WITH cte AS (
SELECT
node = x.query('.')
,name = x.value('local-name(.)','varchar(max)')
,xpath = CAST('' AS varchar(max))
FROM (SELECT #article AS node) parent
CROSS APPLY node.nodes('/*') T(x)
UNION ALL
SELECT
node = x.query('.')
,name = x.value('local-name(.)','varchar(max)')
,xpath = parent.xpath + '/' + parent.name
FROM cte parent
CROSS APPLY node.nodes('/*/*') T(x)
)
SELECT
xpath
,name
FROM cte
WHERE name = 'article-title'

SQL Server XML Query Advice

I'm writing a user-defined function to extract values from an XML column in SQL Server which represents a simple dictionary of string key-value pairs. The only way I've made it work so far seems overly complex. Do you have any simplifying suggestions or tips for the DictValue function below?
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[DictValue]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
DROP FUNCTION [dbo].[DictValue]
go
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TableWithXmlColumn]') AND type in (N'U'))
DROP TABLE [dbo].[TableWithXmlColumn]
go
create table TableWithXmlColumn (
Id int identity primary key
,Dict xml
)
go
create function DictValue(
#id int
,#key nvarchar(max)
) returns nvarchar(max) as begin
declare #d xml -- string Dictionary
select #d = Dict from TableWithXmlColumn where Id = #id
declare #value xml
select
#value = d.Pair.value('data(.)', 'nvarchar(max)')
from
#d.nodes('/StringDictionary/Pair') as d(Pair)
where
#key = d.Pair.value('./#Key', 'nvarchar(max)')
return convert(nvarchar(max), #value)
end
go
declare #xmlId int
insert TableWithXmlColumn (Dict) values (
N'<?xml version="1.0" encoding="utf-16"?>
<StringDictionary>
<Pair Key="color">red</Pair>
<Pair Key="count">123</Pair>
</StringDictionary>')
set #xmlId = scope_identity()
select
dbo.DictValue(#xmlId, 'color') as color
,dbo.DictValue(#xmlId, 'count') as [count]
I find the following variable-bound XQuery approach easier to understand:
create function DictValue(
#id int,
#key nvarchar(max)
)
returns nvarchar(max) as
begin
declare #value nvarchar(max)
select
#value = Dict.value(
'(StringDictionary/Pair[#Key=sql:variable("#key")])[1]',
'nvarchar(max)'
)
from TableWithXmlColumn
where Id = #id
return #value
end
I didn't verify it, but this might perform a bit better, too, as it avoids context switching on the T-SQL and XQuery engines and requires only one XQuery.
I just realized that you didn't specify whether the Dict XML for one Id might contain multiple Pair elements with the same Key:
insert TableWithXmlColumn (Dict) values (
N'<?xml version="1.0" encoding="utf-16"?>
<StringDictionary>
<Pair Key="color">red</Pair>
<Pair Key="color">blue</Pair>
<Pair Key="count">123</Pair>
</StringDictionary>')
If that's the case then consider this slightly modified function, which uses a FLWOR XQuery to enumerate and combine multiple values, if they exist. Note, though, that in this case the function will only return NULL when an #id is not found, not when there's no matching #key Pair element.
create function DictValue(
#id int,
#key nvarchar(max)
)
returns nvarchar(max) as
begin
declare #value nvarchar(max)
select
#value = Cast(
Dict.query('
for $i in StringDictionary/Pair[#Key=sql:variable("#key")]
return string($i)
') as nvarchar(max))
from TableWithXmlColumn
where Id = #id
return #value
end
Good luck!
Personally I don't see much that you can do, the code that you have is structured in a way that is very readable, and you are querying the XML to get the result set first, then grabbing the values.
You might be able to get around the use of the #d variable, however, I believe the readability of the code will suffer greatly.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[XMLTable](#x XML)
RETURNS TABLE
AS RETURN
WITH cte AS (
SELECT
1 AS lvl,
x.value('local-name(.)','NVARCHAR(MAX)') AS Name,
CAST(NULL AS NVARCHAR(MAX)) AS ParentName,
CAST(1 AS INT) AS ParentPosition,
CAST(N'Element' AS NVARCHAR(20)) AS NodeType,
x.value('local-name(.)','NVARCHAR(MAX)') AS FullPath,
x.value('local-name(.)','NVARCHAR(MAX)')
+ N'['
+ CAST(ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS NVARCHAR)
+ N']' AS XPath,
ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS Position,
x.value('local-name(.)','NVARCHAR(MAX)') AS Tree,
x.value('text()[1]','NVARCHAR(MAX)') AS Value,
x.query('.') AS this,
x.query('*') AS t,
CAST(CAST(1 AS VARBINARY(4)) AS VARBINARY(MAX)) AS Sort,
CAST(1 AS INT) AS ID
FROM #x.nodes('/*') a(x)
UNION ALL
SELECT
p.lvl + 1 AS lvl,
c.value('local-name(.)','NVARCHAR(MAX)') AS Name,
CAST(p.Name AS NVARCHAR(MAX)) AS ParentName,
CAST(p.Position AS INT) AS ParentPosition,
CAST(N'Element' AS NVARCHAR(20)) AS NodeType,
CAST(p.FullPath + N'/' + c.value('local-name(.)','NVARCHAR(MAX)') AS NVARCHAR(MAX)) AS FullPath,
CAST(p.XPath + N'/'+ c.value('local-name(.)','NVARCHAR(MAX)')+ N'['+ CAST(ROW_NUMBER() OVER(PARTITION BY c.value('local-name(.)','NVARCHAR(MAX)')
ORDER BY (SELECT 1)) AS NVARCHAR)+ N']' AS NVARCHAR(MAX)) AS XPath,
ROW_NUMBER() OVER(PARTITION BY c.value('local-name(.)','NVARCHAR(MAX)')
ORDER BY (SELECT 1)) AS Position,
CAST( SPACE(2 * p.lvl - 1) + N'|' + REPLICATE(N'-', 1) + c.value('local-name(.)','NVARCHAR(MAX)') AS NVARCHAR(MAX)) AS Tree,
CAST( c.value('text()[1]','NVARCHAR(MAX)') AS NVARCHAR(MAX) ) AS Value, c.query('.') AS this,
c.query('*') AS t,
CAST(p.Sort + CAST( (lvl + 1) * 1024 + (ROW_NUMBER() OVER(ORDER BY (SELECT 1)) * 2) AS VARBINARY(4)) AS VARBINARY(MAX) ) AS Sort,
CAST((lvl + 1) * 1024 + (ROW_NUMBER() OVER(ORDER BY (SELECT 1)) * 2) AS INT)
FROM cte p
CROSS APPLY p.t.nodes('*') b(c)), cte2 AS (
SELECT
lvl AS Depth,
Name AS NodeName,
ParentName,
ParentPosition,
NodeType,
FullPath,
XPath,
Position,
Tree AS TreeView,
Value,
this AS XMLData,
Sort, ID
FROM cte
UNION ALL
SELECT
p.lvl,
x.value('local-name(.)','NVARCHAR(MAX)'),
p.Name,
p.Position,
CAST(N'Attribute' AS NVARCHAR(20)),
p.FullPath + N'/#' + x.value('local-name(.)','NVARCHAR(MAX)'),
p.XPath + N'/#' + x.value('local-name(.)','NVARCHAR(MAX)'),
1,
SPACE(2 * p.lvl - 1) + N'|' + REPLICATE('-', 1)
+ N'#' + x.value('local-name(.)','NVARCHAR(MAX)'),
x.value('.','NVARCHAR(MAX)'),
NULL,
p.Sort,
p.ID + 1
FROM cte p
CROSS APPLY this.nodes('/*/#*') a(x)
)
SELECT
ROW_NUMBER() OVER(ORDER BY Sort, ID) AS ID,
ParentName, ParentPosition,Depth, NodeName, Position,
NodeType, FullPath, XPath, TreeView, Value, XMLData
FROM cte2

Resources