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'
Related
I have a table Connection
KEY Base_TBL Connected_table Base_tbl_colmn Connected_table_colmn
---- ------ ------------- ------------ ------------------
PRM Table1 Table2,Table3 colm1 Colm2,colm3
FRN Table4 table5 colm4 colm5
I need to generate a dynamic Query which should give an output something like this
select * from table1
INNER JOIN Table 2
ON table1.colm1 =table2.colm2
INNER JOIN Table3
on Table1.colm1=tabl3.colm3
number of tables in Connected_table can be anything and i have to join on the basis of that.
I have tried REPLACE Function within The dynamic query but not getting the desired result.
CREATE PROCEDURE [dbo].jointables
[Key] nvarchar(10)
AS
BEGIN
SET NOCOUNT ON
SET ROWCOUNT 0
DECLARE #sql as nvarchar(4000)
select #sql= 'select * from '+ Base_TBL + 'inner join ' +
Please guide me how to proceed further
CREATE FUNCTION [dbo].[FN_SPLIT](#Long_str varchar(max),#split_str varchar(100))
RETURNS #tmp TABLE(
ID inT IDENTITY PRIMARY KEY,
SPLIT varchar(max)
)
AS
BEGIN
DECLARE #sxml XML
SET #sxml='<root><node>'+REPLACE(#Long_str,#split_str,'</node><node>')+'</node></root>'
INSERT INTO #tmp([SPLIT])
SELECT b.value('.','varchar(max)') FROM #sxml.nodes('root/node') AS s(b)
RETURN
END
GO
DECLARE #sql as nvarchar(4000)
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
;WITH testdata AS
(
select 'PRM' AS [Key],'Table1' AS Base_TBL,'Table2,Table3' AS Connected_table,'colm1' AS Base_tbl_colmn,'Colm2,colm3' AS Connected_table_colmn
)
SELECT * INTO #temp FROM testdata
select #sql= 'select * from '+ Base_TBL +' ' +t.joinstring
FROM #temp
CROSS APPLY (
SELECT STUFF((
SELECT ' INNER JOIN '+ ft.[SPLIT] + ' ON ' + Base_TBL+'.'+Base_tbl_colmn +'='+ft.[SPLIT]+'.'+fc.[SPLIT]
FROM
dbo.FN_SPLIT(Connected_table,',') AS ft
INNER JOIN dbo.FN_SPLIT(Connected_table_colmn,',') AS fc ON fc.ID=ft.ID
FOR XML PATH('')),1,1,'') AS joinstring
) AS t
SELECT #sql
Below example just process one line
when process multiple lines, you can use another stuff function combine multiple lines
SELECT STUFF((
select ' select * from '+ Base_TBL +' ' +t.joinstring
FROM #temp
CROSS APPLY (
SELECT STUFF((
SELECT ' INNER JOIN '+ ft.[SPLIT] + ' ON ' + Base_TBL+'.'+Base_tbl_colmn +'='+ft.[SPLIT]+'.'+fc.[SPLIT]
FROM
dbo.FN_SPLIT(Connected_table,',') AS ft
INNER JOIN dbo.FN_SPLIT(Connected_table_colmn,',') AS fc ON fc.ID=ft.ID
FOR XML PATH('')),1,1,'') AS joinstring
) AS t FOR XML PATH('')
),1,1,'')
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
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;
I am trying to store the results of a complex query inside a temporary table but I keep getting an error. Below is the code from my stored procedure:
DECLARE #TempItems TABLE
(
ID int IDENTITY, ForumThreadID int, ForumID int, ParentID int, title NVARCHAR(MAX), title_path NVARCHAR(MAX),
level_id NVARCHAR(MAX), level_id_path NVARCHAR(MAX), PostBody NVARCHAR(MAX), CreatedBy int, UserName NVARCHAR(50), Created DateTime
)
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = 'WITH TreeList (ForumThreadID, ForumID, ParentID, title, title_path, level_id, level_id_path) as
(
SELECT p.ForumThreadID,
p.ForumID,
p.ParentID,
p.PostSubject,
CONVERT(nvarchar(max), p.ForumThreadID),
ROW_NUMBER() OVER(PARTITION BY ParentID ORDER BY p.ForumThreadID),
RIGHT(''0000'' + CAST(ROW_NUMBER() OVER(PARTITION BY ParentID ORDER BY p.ForumThreadID) AS varchar(max)),4)
FROM ForumThreads p
WHERE (p.ParentID = ' + #ParentID + ') AND (p.Deleted IS NULL)
UNION ALL
SELECT c.ForumThreadID,
c.ForumID,
c.ParentID,
c.PostSubject,
r.title_path + ''/'' + CAST(c.ForumThreadID AS VARCHAR(MAX)),
ROW_NUMBER() OVER(PARTITION BY c.ParentID ORDER BY c.ForumThreadID),
CONVERT(varchar(max), r.level_id_path + ''.'' + RIGHT(''0000'' + CAST(ROW_NUMBER() OVER(PARTITION BY c.ParentID ORDER BY c.ForumThreadID) AS VARCHAR),4))
FROM ForumThreads AS c
INNER JOIN treelist AS r
ON c.ParentID = r.ForumThreadID
WHERE (c.Deleted IS NULL))
SELECT TOP 100 TreeList.*, d.PostBody, d.CreatedBy, Members.UserName, COALESCE(d.Created,''1-JAN-1900'') AS Created
FROM TreeList INNER JOIN ForumThreads AS d ON TreeList.ForumThreadID = d.ForumThreadID INNER JOIN
Members ON d.CreatedBy = Members.MemberID
WHERE (d.Deleted IS NULL)
ORDER BY level_id_path;'
INSERT INTO #TempItems (ForumThreadID, ForumID, ParentID, title, title_path, level_id, level_id_path, PostBody, CreatedBy, UserName, Created) EXEC #SQL
SELECT * FROM #TempItems
The error I am getting is:
Msg 203, Level 16, State 2, Procedure spPagedForumThreads, Line 53
The name 'WITH TreeList (ForumThreadID, ForumID, ParentID, title, title_path, level_id, level_id_path) as
(
SELECT p.ForumThreadID,
p.ForumID,
p.ParentID,
p.PostSubject,
CONVERT(nvarchar(max), p.ForumThreadID),
ROW_NUMBER() OVER(PARTITION BY ParentID ORDER BY p.ForumThreadID),
RIGHT('0000' + CAST(ROW_NUMBER() OVER(PARTITION BY ParentID ORDER BY p.ForumThreadID) AS varchar(max)),4)
FROM ForumThreads p
WHERE (p.ParentID = 10720) AND (p.Deleted IS NULL)
UNION ALL
SELECT c.ForumThreadID,
c.ForumID,
c.ParentID,
c.PostSubject,
r.title_path + '/' + ' is not a valid identifier.
What am I doing wrong?
Is there another way to create a temporary table, one that does not require saving the query as a string?
Thanks!
What purpose does executing dynamic SQL serve? After your CTE, you should just be able to modify your SELECT statement to be INSERT INTO #TempItems SELECT TOP 100 TreeList.* .....
This should just work without needing dynamic SQL (i.e. EXEC). Note the statement prior to the WITH needs to be terminated with a semi-colon in order to be syntactically valid.
DECLARE #TempItems TABLE
(
ID int IDENTITY, ForumThreadID int, ForumID int, ParentID int, title NVARCHAR(MAX), title_path NVARCHAR(MAX),
level_id NVARCHAR(MAX), level_id_path NVARCHAR(MAX), PostBody NVARCHAR(MAX), CreatedBy int, UserName NVARCHAR(50), Created DateTime
);
WITH TreeList (ForumThreadID, ForumID, ParentID, title, title_path, level_id, level_id_path) as
(
SELECT p.ForumThreadID,
p.ForumID,
p.ParentID,
p.PostSubject,
CONVERT(nvarchar(max), p.ForumThreadID),
ROW_NUMBER() OVER(PARTITION BY ParentID ORDER BY p.ForumThreadID),
RIGHT('0000' + CAST(ROW_NUMBER() OVER(PARTITION BY ParentID ORDER BY p.ForumThreadID) AS varchar(max)),4)
FROM ForumThreads p
WHERE (p.ParentID = ' + #ParentID + ') AND (p.Deleted IS NULL)
UNION ALL
SELECT c.ForumThreadID,
c.ForumID,
c.ParentID,
c.PostSubject,
r.title_path + '/' + CAST(c.ForumThreadID AS VARCHAR(MAX)),
ROW_NUMBER() OVER(PARTITION BY c.ParentID ORDER BY c.ForumThreadID),
CONVERT(varchar(max), r.level_id_path + '.' + RIGHT('0000' + CAST(ROW_NUMBER() OVER(PARTITION BY c.ParentID ORDER BY c.ForumThreadID) AS VARCHAR),4))
FROM ForumThreads AS c
INNER JOIN treelist AS r
ON c.ParentID = r.ForumThreadID
WHERE (c.Deleted IS NULL))
INSERT INTO #TempItems (ForumThreadID, ForumID, ParentID, title, title_path, level_id, level_id_path, PostBody, CreatedBy, UserName, Created)
SELECT TOP 100 TreeList.*, d.PostBody, d.CreatedBy, Members.UserName, COALESCE(d.Created,'1-JAN-1900') AS Created
FROM TreeList INNER JOIN ForumThreads AS d ON TreeList.ForumThreadID = d.ForumThreadID INNER JOIN
Members ON d.CreatedBy = Members.MemberID
WHERE (d.Deleted IS NULL)
ORDER BY level_id_path;
SELECT * FROM #TempItems
EXEC(#SQL) instead of just EXEC #SQL.
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