Below is the stored procedure
;WITH cte AS (
SELECT
AgentId,
CAST('<r>' + REPLACE(States, ',', '</r><r>') + '</r>' AS XML) AS States,
CAST('<r>' + REPLACE(REPLACE(Products,'&','&'), ',', '</r><r>') + '</r>' AS XML) AS Products
FROM #tbVendor
)
,FinalList AS (
SELECT
AgentId,
RTRIM(LTRIM (sTable.sColumn.value('.', 'VARCHAR(MAX)'))) AS States,
RTRIM(LTRIM (PTable.PColumn.value('.', 'VARCHAR(MAX)'))) AS Products
FROM cte
CROSS APPLY States.nodes('//r') AS sTable(sColumn)
CROSS APPLY Products.nodes('//r') AS PTable(PColumn)
)
SELECT DISTINCT F.Products AS ProductName
,T.ProductId AS ProductId
FROM FinalList F
CROSS APPLY (SELECT ProductId FROM #tbProduct TP WHERE TP.ProductName = F.Products) AS T
WHERE F.States = 'New York'
AND F.AgentId = 1
ORDER BY T.ProductId ASC
This is the SQL fiddle
http://rextester.com/SVXKFH57654
It is working fine and perfectly but it eliminate the records with "-" character in ProductName feild For e.g Non-Stick Utensils... etc
I am not able to tackle this issue... Please help me!!!
For the splitting string you use XML. In this case, you will have problems with some characters. For example &, <, >. You can avoid this by using another method of splitting a string
Table function splitting strings:
CREATE FUNCTION [dbo].[SplitStr] (
#str varchar(MAX)
,#sep char(1)=','
)
RETURNS TABLE
AS
RETURN
(
WITH Split ( n1, n2)
AS
(
SELECT CAST(0 as bigint) as n1, CHARINDEX(#sep, #str + #sep) as n2
UNION ALL
SELECT n2 as n1, CHARINDEX(#sep, #str + #sep, n2 + 1) as n2
FROM Split
WHERE n2 < LEN(#str)
)
SELECT SUBSTRING(#str, n1+1, n2-n1-1) as Col FROM Split
)
GO
Using this function:
SELECT
tbVendor.AgentId
,States.Col as States
,Products.Col as Products
FROM #tbVendor as tbVendor
CROSS APPLY [dbo].[SplitStr](States, ',') as States
CROSS APPLY [dbo].[SplitStr](Products, ',') as Products
this is equivalent to your code
-- First convert all comma separated data into tabular form as:
;WITH cte AS (
SELECT
AgentId,
CAST('<r>' + REPLACE(States, ',', '</r><r>') + '</r>' AS XML) AS States,
CAST('<r>' + REPLACE(Products, ',', '</r><r>') + '</r>' AS XML) AS Products
FROM #tbVendor
)
SELECT
AgentId,
sTable.sColumn.value('.', 'VARCHAR(MAX)') AS States,
PTable.PColumn.value('.', 'VARCHAR(MAX)') AS Products
FROM cte
CROSS APPLY States.nodes('//r') AS sTable(sColumn)
CROSS APPLY Products.nodes('//r') AS PTable(PColumn)
Related
I need to get de line number and position (on that line) of a specific word in a text.
For example:
--
This is my first line.
This is my second line.
--
If I would check for 'second' I should get something back like: 2,12
Anyone any suggestion?
Assuming you are looking for the first occurrence per line, and assuming a LINE is delimited by char(13) and not punctuation.
Example
Declare #YourTable table (ID int,SomeText varchar(max))
Insert Into #YourTable values
(1,'This is my first line.
This is my second line.')
,(2,'This another but has a second note
Which not related to the prior "second" note')
Declare #Search varchar(100)='second'
Select A.ID
,Position=concat(RetSeq,',',charindex(#Search,RetVal))
From #YourTable A
Cross Apply (
Select RetSeq = row_number() over (order by 1/0)
,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(replace(SomeText,char(10),''),char(13),'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B
Where charindex(#Search,RetVal)>0
Returns
ID Position
1 2,12
2 1,24
2 2,33
EDIT - Requested EDIT
Select Top 1 with Ties
A.ID
,Position=concat(RetSeq,',',charindex(#Search,RetVal))
From #YourTable A
Cross Apply (
Select RetSeq = row_number() over (order by 1/0)
,RetVal = B.i.value('(./text())[1]', 'varchar(max)')
From (Select x = Cast('<x>' + replace((Select replace(replace(SomeText,char(10),''),char(13),'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B
Where charindex(#Search,RetVal)>0
Order by Row_Number() over (Partition By ID Order by RetSeq)
Returns
ID Position
1 2,12
2 1,24
Here is an alternative solution that does not make use of a subquery ; it relies on ,SQL Server string functions, such as CHARINDEX and REVERSE.
SELECT
CASE WHEN CHARINDEX( #match, t.value ) < CHARINDEX( CHAR(10), t.value )
THEN CONCAT( '1,', CHARINDEX( #match, t.value ) )
ELSE
CONCAT(
LEN(LEFT(t.value, CHARINDEX(#match, t.value)))
- LEN(REPLACE(LEFT(t.value, CHARINDEX(#match, t.value)), CHAR(10), '')) + 1,
',',
CHARINDEX (CHAR(10), REVERSE(LEFT(t.value, CHARINDEX(#match, t.value)))) - 1
)
END
from t;
The principle is to first find the position of the searched string (#match), and then compute the position of the previous carriage return (CHAR(10) - could be also CHAR(13) depending on your EOF settings), using REVERSE. With these two values at hand, we can compute the position of the match on the line, and the line number (for this, we compare the length of the substring until the match to its length without carriage returns). Special care has to be taken when the match is on the first line.
db<>fiddle here
Declare #match varchar(100) = 'second';
with t as (SELECT 'This is my first line.
This is my second line.
This is my third line.' value)
SELECT
CASE WHEN CHARINDEX( #match, t.value ) < CHARINDEX( CHAR(10), t.value )
THEN CONCAT( '1,', CHARINDEX( #match, t.value ) )
ELSE
CONCAT(
LEN(LEFT(t.value, CHARINDEX(#match, t.value)))
- LEN(REPLACE(LEFT(t.value, CHARINDEX(#match, t.value)), CHAR(10), '')) + 1,
',',
CHARINDEX (CHAR(10), REVERSE(LEFT(t.value, CHARINDEX(#match, t.value)))) - 1
)
END
from t;
GO
| (No column name) |
| :--------------- |
| 2,12 |
Let's say I have 2 tables where both has column called Brand. The value is comma delimited so for example if one of the table has
ACER,ASUS,HP
AMD,NVIDIA,SONY
as value. Then the other table has
HP,GIGABYTE
MICROSOFT
SAMSUNG,PHILIPS
as values.
I want to compare these table to get all matched record, in my example ACER,ASUS,HP and HP,GIGABYTE match because both has HP. Right now I'm using loop to achieve this, I'm wondering if it's possible to do this in a single query syntax.
You are correct in wanting to step away from the loop.
Since you are on 2012, String_Split() is off the table. However, there are any number of split/parse TVF functions in-the-wild.
Example 1 - without a TVF
Declare #T1 table (Brand varchar(50))
Insert Into #T1 values
('ACER,ASUS,HP'),
('AMD,NVIDIA,SONY')
Declare #T2 table (Brand varchar(50))
Insert Into #T2 values
('HP,GIGABYTE'),
('MICROSOFT'),
('SAMSUNG,PHILIPS')
Select Distinct
T1_Brand = A.Brand
,T2_Brand = B.Brand
From (
Select Brand,B.*
From #T1
Cross Apply (
Select RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(Brand,',','</x><x>')+'</x>' as xml)) as A
Cross Apply x.nodes('x') AS B(i)
) B
) A
Join (
Select Brand,B.*
From #T2
Cross Apply (
Select RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(Brand,',','</x><x>')+'</x>' as xml)) as A
Cross Apply x.nodes('x') AS B(i)
) B
) B
on A.RetVal=B.RetVal
Example 2 - with a TVF
Select Distinct
T1_Brand = A.Brand
,T2_Brand = B.Brand
From (
Select Brand,B.*
From #T1
Cross Apply [dbo].[tvf-Str-Parse](Brand,',') B
) A
Join (
Select Brand,B.*
From #T2
Cross Apply [dbo].[tvf-Str-Parse](Brand,',') B
) B
on A.RetVal=B.RetVal
Both Would Return
T1_Brand T2_Brand
ACER,ASUS,HP HP,GIGABYTE
The UDF if interested
CREATE FUNCTION [dbo].[tvf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[tvf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[tvf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[tvf-Str-Parse]('this,is,<test>,for,< & >',',')
Had the same problem with comparing "," delimited strings
you can use "XML" to do that and compare the outputs and return the same/different value:
declare #TestInput nvarchar(255)
, #TestInput2 nvarchar(255)
set #TestInput = 'ACER,ASUS,HP'
set #TestInput2 = 'HP,GIGABYTE'
;WITH FirstStringSplit(S1) AS
(
SELECT CAST('<x>' + REPLACE(#TestInput,',','</x><x>') + '</x>' AS XML)
)
,SecondStringSplit(S2) AS
(
SELECT CAST('<x>' + REPLACE(#TestInput2,',','</x><x>') + '</x>' AS XML)
)
SELECT STUFF(
(
SELECT ',' + part1.value('.','nvarchar(max)')
FROM FirstStringSplit
CROSS APPLY S1.nodes('/x') AS A(part1)
WHERE part1.value('.','nvarchar(max)') IN(SELECT B.part2.value('.','nvarchar(max)')
FROM SecondStringSplit
CROSS APPLY S2.nodes('/x') AS B(part2)
)
FOR XML PATH('')
),1,1,'') as [Same Value]
Edit:
Changed 'Stuff' to 'XML'
I have the following string in a varchar(max) column:
PREV - FirstName: John / LAST - FirstName: Johan; PREV- LastName: Crescot / LAST - LastName: Crescott;
After every semicolon can come endless amounts PREV values and LAST value mutations depending on the amount of changes done in the source system.
I need to write a query that returns ONLY the PREV values. In case of the string above, the desired result would be:
FirstName: John; LastName: Crescot
All the slash (/) delimiters and dashes need to be removed as well, as you can see in the required result.
Could anyone help me with this? Thank you all!
If open to a UDF, consider the following.
Tired of extracting strings (charindindex, patindex, left, right...), I modified a parse function to accept two non-like parameters. In this case a 'PREV' and '/'
Example
Declare #YourTable table (ID int,SomeCol varchar(max))
Insert Into #YourTable values
(1,'PREV - FirstName: John / LAST - FirstName: Johan; PREV- LastName: Crescot / LAST - LastName: Crescott;')
Select A.ID
,B.NewVal
From #YourTable A
Cross Apply (
Select NewVal = Stuff((Select '; '+ltrim(rtrim(replace(RetVal,'-','')))
From [dbo].[udf-Str-Extract](A.SomeCol,'PREV','/')
For XML Path ('')),1,2,'')
) B
Returns
ID NewVal
1 FirstName: John; LastName: Crescot
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Str-Extract] (#String varchar(max),#Delimiter1 varchar(100),#Delimiter2 varchar(100))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 N1,cte1 N2,cte1 N3,cte1 N4,cte1 N5,cte1 N6) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter1) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter1)) = #Delimiter1),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter1,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By N)
,RetPos = N
,RetVal = left(RetVal,charindex(#Delimiter2,RetVal)-1)
From (
Select *,RetVal = Substring(#String, N, L)
From cte4
) A
Where charindex(#Delimiter2,RetVal)>1
)
/*
Max Length of String 1MM characters
Declare #String varchar(max) = 'Dear [[FirstName]] [[LastName]], ...'
Select * From [dbo].[udf-Str-Extract] (#String,'[[',']]')
*/
create table #temp(val varchar(max))
Insert into #temp values('PREV - FirstName: John / LAST - FirstName: Johan; PREV - LastName: Crescot / LAST - LastName')
Select stuff(
(SELECT ';'+
Replace(stuff(Tbl.Col.value('./text()[1]','varchar(50)'),charindex('/',Tbl.Col.value('./text()[1]','varchar(50)')),len(Tbl.Col.value('./text()[1]','varchar(50)')),''),'PREV -','')as ColName
FROM
(Select cast('<a>'+ replace((SELECT val As [*] FOR XML PATH('')), ';', '</a><a>') + '</a>' as xml)as t
from #temp) tl
Cross apply
tl.t.nodes('/a') AS Tbl(Col) for xml path(''),type).value('.','NVARCHAR(MAX)'),1,2,'')
This method does not need any additional UDF.
Breaking down the above query for easy understanding:
1. Convert one row of string to multiple rows based on semicolon ';'
SELECT
Tbl.Col.value('./text()[1]','varchar(50)')
FROM
(Select cast('<a>'+ replace((SELECT val As [*] FOR XML PATH('')), ';', '</a><a>') + '</a>' as xml)as t
from #temp) tl
Cross apply
tl.t.nodes('/a') AS Tbl(Col)
2.Over the above extracted value ,use replace and stuff commands to remove unnecessary characters
SELECT
Replace(stuff(Tbl.Col.value('./text()[1]','varchar(50)'),charindex('/',Tbl.Col.value('./text()[1]','varchar(50)')),len(Tbl.Col.value('./text()[1]','varchar(50)')),''),'PREV -','')as ColName
FROM
(Select cast('<a>'+ replace((SELECT val As [*] FOR XML PATH('')), ';', '</a><a>') + '</a>' as xml)as t
from #temp) tl
Cross apply
tl.t.nodes('/a') AS Tbl(Col)
3. Use stuff and xml path to make the multiple rows back to a single row separated by semicolons as required
Select stuff(
(SELECT ';'+
Replace(stuff(Tbl.Col.value('./text()[1]','varchar(50)'),charindex('/',Tbl.Col.value('./text()[1]','varchar(50)')),len(Tbl.Col.value('./text()[1]','varchar(50)')),''),'PREV -','')as food_Name
FROM
(Select cast('<a>'+ replace((SELECT val As [*] FOR XML PATH('')), ';', '</a><a>') + '</a>' as xml)as t
from #temp) tl
Cross apply
tl.t.nodes('/a') AS Tbl(Col) for xml path(''),type).value('.','NVARCHAR(MAX)'),1,2,'')
I have comma-separated string like 675,899,343,294,988.
My table has values like,
ID Values
1 56,78,485
2 90,343,398
3 756,46774,45,4
4 536,394,988
Here i want result like : ID 2,values 343 and ID 4,values 988
Here you can split values to each row for each Id.
;WITH CTE AS
(
-- Convert CSV to rows
SELECT ID,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'VALUES'
FROM
(
-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
SELECT ID,CAST ('<M>' + REPLACE([Values], ',', '</M><M>') + '</M>' AS XML) AS Data
FROM YourTable
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
SELECT *
FROM CTE
WHERE (ID=2 AND [VALUES]='343') OR (ID=4 AND [VALUES]='988')
EDIT :
If you want to get matching Id, you can do the below
SAMPLE TABLE
SELECT * INTO #TEMP
FROM
(
SELECT 1 ID, '56,78,485' [Values]
UNION ALL
SELECT 2, '90,343,398'
UNION ALL
SELECT 3, '756,46774,45,4'
UNION ALL
SELECT 4, '536,394,988'
)TAB
QUERY
DECLARE #STR VARCHAR(100)='675,899,343,294,988'
;WITH CTE1 AS
(
SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'String'
FROM
(
-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
SELECT CAST ('<M>' + REPLACE(#STR, ',', '</M><M>') + '</M>' AS XML) AS Data
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
,CTE2 AS
(
-- Convert CSV to rows
SELECT ID,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Values'
FROM
(
-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
SELECT ID,CAST ('<M>' + REPLACE([Values], ',', '</M><M>') + '</M>' AS XML) AS Data
FROM #TEMP
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
SELECT C2.*
FROM CTE1 c1
JOIN CTE2 C2 ON C1.String=C2.[VALUES]
You can use
SELECT `id` FROM `table` WHERE `Values` IN(string)
I have a table with an XML column in SQL Server 2k8. The following SQL retrieves some XML:
SELECT TOP 1 my_xml_column FROM my_table
Let's say it returns me the following XML
<a>
<b />
<c>
<d />
<d />
<d />
</c>
</a>
What I would like to get is
/a
/a/b
/a/c
/a/c/d
/a/e
In other words, how can I get SQL Server to tell me the structure of my XML?
I can do the following to get all the names of the individual elemtns:
SELECT C1.query('fn:local-name(.)')
FROM my_table
CROSS APPLY my_xml_column.nodes('//*') AS T ( C1 )
Perhaps if there was an equivalent to "local-name()" that returned the whole path of the element that would do the trick?
You can do this cleanly with XQuery and a recursive CTE (no OPENXML):
DECLARE #xml xml
SET #xml = '<a><b /><c><d /><d /><d /></c></a>';
WITH Xml_CTE AS
(
SELECT
CAST('/' + node.value('fn:local-name(.)',
'varchar(100)') AS varchar(100)) AS name,
node.query('*') AS children
FROM #xml.nodes('/*') AS roots(node)
UNION ALL
SELECT
CAST(x.name + '/' +
node.value('fn:local-name(.)', 'varchar(100)') AS varchar(100)),
node.query('*') AS children
FROM Xml_CTE x
CROSS APPLY x.children.nodes('*') AS child(node)
)
SELECT DISTINCT name
FROM Xml_CTE
OPTION (MAXRECURSION 1000)
It's not really doing much XQuery magic, but at least it's all inline, doesn't require any stored procedures, special permissions, etc.
UDF for you.....
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
I suspect that SQL Server's XQuery implementation is not up to this task, but this is another way of doing it (inspired by this, adapt as required):
DECLARE #idoc INT, #xml XML
SET #xml = (SELECT TOP 1 my_xml_column FROM my_table)
EXEC sp_xml_preparedocument #idoc OUTPUT, #xml;
WITH
E AS (SELECT * FROM OPENXML(#idoc,'/',3)),
P AS
(
-- anchor member
SELECT id, parentid, localname AS [Path]
FROM E WHERE parentid IS NULL
UNION ALL
-- recursive member
SELECT E.id, E.parentid, P.[Path] + '/' + localname AS [Path]
FROM P INNER JOIN E ON E.parentid = P.id
)
SELECT [Path] FROM P
EXEC sp_xml_removedocument #idoc