I need to extract all attributes from a long list of various elements. Therefore, I'm seeking to build a loop going through all of my elements and return their attributes.
Through several posts I have been able to write the code below. However, I have more than a 1000 elements why I would extremely glad if it somehow is possible to build a loop around the latter part rather than copying it for all elements.
with cte as
(
select cast(
'<schema fwRel="2">
<taxFormId isPrimeKey="true" fkRef="C1-TXFRM" mapField="TAX_FORM_ID" dataType="string"/>
<formType fkRef="C1-FRMTY" mapField="FORM_TYPE_CD" dataType="string"/>
<bo suppress="true" required="true" fkRef="F1-BUSOB" mapField="BUS_OBJ_CD" dataType="string"/>
<transferReason mdField="C1_TXF_TFRRSN_FLG" dataType="lookup" mapXML="BO_DATA_AREA" lookup="C1_TXF_TFRRSN_FLG"/>
</schema>' as xml) xml_col
)
select cte.xml_col.value('(/schema/taxFormId/#fkRef)[1]', 'varchar(100)') as Dummy1
cte.xml_col.value('(/schema/taxFormId/#mapField)[1]', 'varchar(100)') as Dummy2
cte.xml_col.value('(/schema/taxFormId/#dataType)[1]', 'varchar(100)') as Dummy3
cte.xml_col.value('(/schema/taxFormId/#mapXML)[1]', 'varchar(100)') as Dummy4
from cte
I hope I have provided enough information
An easy way to get attributes listed:
with cte as
(
select cast(
'<schema fwRel="2">
<taxFormId isPrimeKey="true" fkRef="C1-TXFRM" mapField="TAX_FORM_ID" dataType="string" />
<formType fkRef="C1-FRMTY" mapField="FORM_TYPE_CD" dataType="string" />
<bo suppress="true" required="true" fkRef="F1-BUSOB" mapField="BUS_OBJ_CD" dataType="string" />
<transferReason mdField="C1_TXF_TFRRSN_FLG" dataType="lookup" mapXML="BO_DATA_AREA" lookup="C1_TXF_TFRRSN_FLG" />
</schema>' as xml) xml_col
)
--The query uses .nodes(N'/schema/*') to list all nodes below <schema> and .nodes(N'#*') to list all attributes within this node:
select nd.value(N'local-name(.)',N'nvarchar(max)') AS NodeName
,attr.value(N'local-name(.)',N'nvarchar(max)') AS AttrName
,attr.value(N'.',N'nvarchar(max)') AS AttrValue
from cte
OUTER APPLY xml_col.nodes(N'/schema/*') AS A(nd)
OUTER APPLY A.nd.nodes(N'#*') AS B(attr)
The result:
taxFormId isPrimeKey true
taxFormId fkRef C1-TXFRM
taxFormId mapField TAX_FORM_ID
taxFormId dataType string
formType fkRef C1-FRMTY
formType mapField FORM_TYPE_CD
formType dataType string
bo suppress true
bo required true
bo fkRef F1-BUSOB
bo mapField BUS_OBJ_CD
bo dataType string
transferReason mdField C1_TXF_TFRRSN_FLG
transferReason dataType lookup
transferReason mapXML BO_DATA_AREA
transferReason lookup C1_TXF_TFRRSN_FLG
If you need a statement like the one in your example one could create it dynamically (as string) and use EXEC to execute this (dynamic SQL).
It may be a little over-kill, but I often use a TVF to parse and hash large XML files. The function will return the data in a parent/child hierarchy structure with range keys R1/R2.
For example:
Declare #XML xml='
<schema fwRel="2">
<taxFormId isPrimeKey="true" fkRef="C1-TXFRM" mapField="TAX_FORM_ID" dataType="string"/>
<formType fkRef="C1-FRMTY" mapField="FORM_TYPE_CD" dataType="string"/>
<bo suppress="true" required="true" fkRef="F1-BUSOB" mapField="BUS_OBJ_CD" dataType="string"/>
<transferReason mdField="C1_TXF_TFRRSN_FLG" dataType="lookup" mapXML="BO_DATA_AREA" lookup="C1_TXF_TFRRSN_FLG"/>
</schema>'
Select * from [dbo].[udf-XML-Hier](#XML) Order by R1
Returns
You'll notice there are columns for Element Name, Attribute Name, XPath, Title (optional), and Value. The R1,R2,Lvl,ID and PT are all derived.
Being a TVF, you could apply any WHERE or ORDER desired
The UDF (with original source) if interested
CREATE FUNCTION [dbo].[udf-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(10000001 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].[udf-XML-Hier](#XML) Order by R1
*/
Related
I am running into an issue with T-SQL code. There is a CSV file that I need to import and transform into a SQL Server table. The problem is that the CSV file is not correctly format and looks like this:
Recipe,Recipe,Recipe,Recipe,...
0,1,3,4,...
Data1,Data2,Data3,Data4,...
...
The final result would need to be at least like this:
Recipe,0,Data1,...
Recipe,1,Data2,...
Recipe,3,Data3,...
Recipe,4,Data4,...
...
I have used FOR XML PATH to get all rows into one single string but I did not end up with anything good.
The information I have :
I always know the number of rows and columns that the file has.
I am using SQL Server 2016
I do not have sysadmin rights
Any help to show me the right path would be greatly appreciated!
Thanks!
Example
Declare #S varchar(max) = 'Recipe,Recipe,Recipe,Recipe
0,1,3,4
Data1,Data2,Data3,Data4'
;with cte as (
Select CN=A.RetSeq
,RN=B.RetSeq
,Value=B.RetVal
From [dbo].[tvf-Str-Parse](#S,char(13)+char(10)) A
Cross Apply [dbo].[tvf-Str-Parse](A.RetVal,',') B
)
Select Str = Stuff((Select ',' +Value From cte Where RN=A.RN Order By CN For XML Path ('')),1,1,'')
From (Select Distinct RN from cte) A
Order By A.RN
Returns
Str
Recipe,0,Data1
Recipe,1,Data2
Recipe,3,Data3
Recipe,4,Data4
The Function 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 1/0)
,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)
);
EDIT - OPTION WITHOUT FUNCTION
Declare #S varchar(max) = 'Recipe,Recipe,Recipe,Recipe
0,1,3,4
Data1,Data2,Data3,Data4'
;with cte as (
Select CN=A.RetSeq
,RN=B.RetSeq
,Value=B.RetVal
From (
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(#S,char(13)+char(10),'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) 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(A.RetVal,',','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B
)
Select Str = Stuff((Select ',' +Value From cte Where RN=A.RN Order By CN For XML Path ('')),1,1,'')
From (Select Distinct RN from cte) A
Order By RN
Edit JSON OPTION -- Correcting for Double Quotes
Declare #S varchar(max) = 'Recipe,Recipe,Recipe,Recipe
1,,3,4
Data1,Data2,Data"3,Data4'
;with cte as (
Select CN = A.[key]
,RN = B.[Key]
,Value = replace(B.Value,'||','"')
From OpenJSON('["'+replace(replace(#S,'"','||'),char(13)+char(10),'","')+'"]') A
Cross Apply (
Select *
From OpenJSON('["'+replace(A.Value,',','","')+'"]')
) B
)
Select Str = Stuff((Select ',' +Value From cte Where RN=A.RN Order By CN For XML Path ('')),1,1,'')
From (Select Distinct RN from cte) A
Order By RN
Returns
Str
Recipe,1,Data1
Recipe,,Data2 -- null (2 is missing
Recipe,3,Data"3 -- has double quote
Recipe,4,Data4
First post - I am trying to pull ten different pieces of information from a single field. Let me start with this is not my table, just what I was given to work with. This is a varchar max field.
'3350|#|1234567|~|3351|#|8/1/2017|~|3352|#|Acme|~|3353|~|10000.00|~|3354|#||~|3355|#||~3356|#|Yes|~|3357|#|Doe,John|~|3358|#|CA|~|3359|#|5551212'
I know that the numbers that start with 33 are keys telling me what information is in that section. 3350 has the invoice #1234567. 3351 has the invoice date of 8/1/17. etc. 3354 and 3355 were left null. The keys are unchanging and will be the same for every record in the table.
I need to pull the data from between 3350|#| and |~|3351 to get my invoice# and between 3351|#| and |~|3352 to get my date, etc, but I am struggling with how to word this. Any help would be appreciated and any critiques on my first post will be taken constructively.
The #YourTable is just a table variable used for demonstration / illustration
For Rows - Example
Declare #YourTable table (ID int,SomeCol varchar(max))
Insert Into #YourTable values
(1,'3350|#|1234567|~|3351|#|8/1/2017|~|3352|#|Acme|~|3353|#|10000.00|~|3354|#||~|3355|#||~|3356|#|Yes|~|3357|#|Doe,John|~|3358|#|CA|~|3359|#|5551212')
Select A.ID
,Item = left(RetVal,charindex('|#|',RetVal+'|#|')-1)
,Value = right(RetVal,len(RetVal)-charindex('|#|',RetVal+'|#|')-2)
From #YourTable A
Cross Apply [dbo].[udf-Str-Parse](A.SomeCol,'|~|') B
Returns
For Columns - Example
Declare #YourTable table (ID int,SomeCol varchar(max))
Insert Into #YourTable values
(1,'3350|#|1234567|~|3351|#|8/1/2017|~|3352|#|Acme|~|3353|#|10000.00|~|3354|#||~|3355|#||~|3356|#|Yes|~|3357|#|Doe,John|~|3358|#|CA|~|3359|#|5551212')
Select *
From (
Select A.ID
,Item = left(RetVal,charindex('|#|',RetVal+'|#|')-1)
,Value = right(RetVal,len(RetVal)-charindex('|#|',RetVal+'|#|')-2)
From #YourTable A
Cross Apply [dbo].[udf-Str-Parse](A.SomeCol,'|~|') B
) A
Pivot (max([Value]) For [Item] in ([3350],[3351],[3352],[3353],[3354],[3355],[3356],[3357],[3358],[3359]) ) p
Returns
The UDF if Interested
CREATE FUNCTION [dbo].[udf-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].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
You can try a tally based splitter like below
declare #t table ( id int, col nvarchar(max));
insert into #t values
(1, '3350|#|1234567|~|3351|#|8/1/2017|~|3352|#|Acme|~|3353|~|10000.00|~|3354|#||~|3355|#||~3356|#|Yes|~|3357|#|Doe,John|~|3358|#|CA|~|3359|#|5551212')
,(2, '3350|#|123334567|~|3351|#|8/2/2017|~|3352|#|Acme|~|3353|~|10000.00|~|3354|#||~|3355|#||~3356|#|Yes|~|3357|#|Doe,John|~|3358|#|CA|~|3359|#|5551212');
select
id,
case
when split_values like '3350|#|%' then 'id'
when split_values like '3351|#|%' then 'date'
end as fieldname,
SUBSTRING(split_values,8,LEN(split_values)-7) as value
from
(
select
--t.col as col,
row_number() over (partition by t.col order by t1.N asc) as row_num,
t.id,
SUBSTRING( t.col, t1.N, ISNULL(NULLIF(CHARINDEX('|~|',t.col,t1.N),0)-t1.N,8000)) as split_values
from #t t
join
(
select
t.col,
1 as N
from #t t
UNION ALL
select
t.col,
t1.N + 3 as N
from #t t
join
(
select
top 8000
row_number() over(order by (select NULL)) as N
from
sys.objects s1
cross join
sys.objects s2
) t1
on SUBSTRING(t.col,t1.N,3) = '|~|'
) t1
on t1.col=t.col
)a
where
split_values like '3350|#|%' or
split_values like '3351|#|%'
Live demo
I'm trying to parse some comma separated values from a column in SQL Server 2012 while still keeping the data from the columns in the left and to the right.
I have seen some similar topic solutions but none seemed to be what I am looking for.
I have this:
FirstName LastName userid Regions ViewCosts HelpReviewCosts
---------------------------------------------------------------------
Darron Peters ya00003 All y y
John Davies ya30982 NA, EM, AP, LA n n
I am trying to parse the Regions column so that I can get this:
FirstName LastName userid Regions ViewCosts HelpReviewCosts
---------------------------------------------------------------------
Darron Peters ya00003 All y y
John Davies ya30982 NA n n
John Davies ya30982 EM n n
John Davies ya30982 AP n n
John Davies ya30982 LA n n
There are thousands of examples on how to split/parse strings. Below are two samples, one with a UDF and the other without. Both use a CROSS APPLY
With a UDF
Declare #Yourtable table (FirstName varchar(25) ,LastName varchar(25),userid varchar(25), Regions varchar(50), ViewCosts varchar(25), HelpReviewCosts varchar(25))
Insert Into #Yourtable values
('Darron','Peters','ya00003','All','y','y'),
('John','Davies','ya30982','NA, EM, AP, LA','n','n')
Select A.FirstName
,A.LastName
,A.userid
,Regions =B.RetVal
,A.ViewCosts
,A.HelpReviewCosts
From #Yourtable A
Cross Apply [dbo].[udf-Str-Parse](A.Regions,',') B
Without A UDF
Select A.FirstName
,A.LastName
,A.userid
,Regions =B.RetVal
,A.ViewCosts
,A.HelpReviewCosts
From #Yourtable A
Cross Apply (
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 A.Regions as [*] For XML Path('')),',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B
Both Returns
THE UDF if needed
CREATE FUNCTION [dbo].[udf-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 #String as [*] For XML Path('')),#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[udf-Str-Parse]('this,is,<test>,for,< & >',',')
I suggest you to use STRING_SPLIT function
WITH
CTE_Sample AS
(
SELECT 'All' AS txt
UNION ALL
SELECT 'NA, EM, AP, LA' AS txt
)
SELECT
txt,
value
FROM CTE_Sample
CROSS APPLY STRING_SPLIT(txt, ',');
If you don't want to 'udf' and 'string_split' function,then you can use this query.and it's suitable for large strings with comma separated and also much faster compared to others...
`CREATE TABLE TB (Number INT)
DECLARE #I INT=0
WHILE #I<1000
BEGIN
INSERT INTO TB VALUES (#I)
SET #I=#I+1
END
SELECT
FirstName
,LastName
,userid
,S_DATA
,ViewCosts
,HelpReviewCosts
FROM (
SELECT
FirstName
,LastName
,userid
,CASE WHEN LEN(LIST2)>0 THEN LTRIM(RTRIM(SUBSTRING(LIST2, NUMBER+1, CHARINDEX(',', LIST2, NUMBER+1)-NUMBER - 1)))
ELSE NULL
END AS S_DATA
,ViewCosts
,HelpReviewCosts
,NUMBER
FROM(
SELECT FirstName
,LastName
,userid
,','+Regions+',' LIST2
,ViewCosts
,HelpReviewCosts
FROM Tb1
)DT
LEFT OUTER JOIN TB N ON (N.NUMBER < LEN(DT.LIST2)) OR (N.NUMBER=1 AND DT.LIST2 IS NULL)
WHERE SUBSTRING(LIST2, NUMBER, 1) = ',' OR LIST2 IS NULL
) DT2
WHERE S_DATA<>''
this is my Output
In Microsoft SQL Server, the following works, but produces:
,Son,Dad,Granddad,Great Granddad
whereas I need it to say:
Great Granddad,Granddad,Dad,Son
declare #Family Table(
ID Int Identity(100,1) Primary Key
,Person varchar(128)
,ParentID Int default 0
)
insert into #Family(Person,ParentID) values('Great Granddad',0)
insert into #Family(Person,ParentID) values('Granddad',100)
insert into #Family(Person,ParentID) values('Dad',101)
insert into #Family(Person,ParentID) values('Son',102)
DECLARE #ID Int = 103
;with cte1 as (
select
#ID AS cteID
,ID
,ParentID
,Person as ctePerson
from #Family
where ID = #ID -- this is the starting point you want in your recursion
UNION ALL
select #ID, F.ID, F.ParentID, F.Person
from #Family F
join cte1 P on P.ParentID = F.ID -- this is the recursion
)
-- cte2 can reverse the sort order based on something built in (OVER?)
-- ROW_NUMBER() OVER(ORDER BY ? DESC) AS Row
,cte3 AS(
select ID as cte3ID,(
SELECT ',' + ctePerson
FROM cte1
WHERE cteID = F.ID
FOR XML PATH ('')
) as People
from #Family F
where ID=#ID
)
SELECT * FROM CTE3
I would not order the result of a recursive CTE by using another CTE, as the results of CTEs are semantically tables, and therfore the order is not guaranteed. Instead order when selecting from a CTE, just als like with normal tables.
I would suggest to insert a field representing the level or relationship and order by that:
;with cte1 as (
select
#ID AS cteID
,ID
,ParentID
,Person as ctePerson
,0 lvl -- starting level with 0
from #Family
where ID = #ID -- this is the starting point you want in your recursion
UNION ALL
select #ID, F.ID, F.ParentID, F.Person
, lvl + 1 -- increase level by 1
from #Family F
join cte1 P on P.ParentID = F.ID -- this is the recursion
)
,cte3 AS(
select ID as cte3ID,STUFF(( -- stuff removes the first ','
SELECT ',' + ctePerson
FROM cte1
WHERE cteID = F.ID
ORDER by lvl DESC -- order by level DESC to start with latest ancestor
FOR XML PATH ('')
), 1, 1, '') as People
from #Family F
where ID=#ID
)
SELECT * FROM CTE3
I have a table which have the following data
Item
......
xzypq
abdcfe
How can I sort the string in the column and get the following result?
Item
......
pqxyz
abcdef
May be try the below link which might help http://social.technet.microsoft.com/wiki/contents/articles/19492.sort-letters-in-a-phrase-using-t-sql.aspx
/*Create sample table*/
IF OBJECT_ID('tempdb..#Text', 'U') IS NOT NULL
DROP TABLE #Test;
CREATE TABLE #Test
(
ID INT IDENTITY(1, 1) ,
Phrase VARCHAR(255)
);
/*Populate the table with sample data*/
INSERT #Test
( Phrase )
VALUES
( 'CHICAGO' ),
( 'NEW YORK' ),
( 'HOUSTON' ),
( 'SAN FRANCISCO' );
/*This is the final solution*/;
WITH base
AS ( SELECT L.[char] ,
T.ID ,
T.Phrase
FROM #Test T
CROSS APPLY ( SELECT SUBSTRING(T.Phrase, 1 + Number, 1) [char]
FROM master..spt_values
WHERE Number < DATALENGTH(T.Phrase)
AND type = 'P'
) L
)
SELECT DISTINCT
b1.Phrase ,
REPLACE(( SELECT '' + [char]
FROM base b2
WHERE b1.Phrase = b2.Phrase
ORDER BY [char]
FOR
XML PATH('')
), ' ', ' ') AS columns2
FROM base AS b1;
Using Recursive CTE also you can do this.
SELECT 'xzypq' NAME
INTO #temp
UNION ALL
SELECT 'abdcfe'
Recursive CTE
;WITH cte
AS (SELECT Cast(NAME AS VARCHAR(50)) AS st,NAME AS name1,1 AS rn
FROM #temp
UNION ALL
SELECT Cast(Substring(NAME, rn, 1) AS VARCHAR(50)),name1,rn + 1
FROM cte a
JOIN #temp b
ON a.name1 = b.NAME
AND rn < Len(a.name1) + 1)
SELECT DISTINCT (SELECT '' + st
FROM cte b
WHERE a.name1 = b.name1
AND rn <> 1
ORDER BY st
FOR XML PATH ('')) AS Ordered_String
FROM cte a
WHERE rn <> 1
Result
Ordered_String
--------------
abcdef
pqxyz