SQL XML path conversion results error - sql

Actually I m begineer to SQL XML path as so making me professional, Got a scenario...
I have a CTE Function That results as
Data Chars NumberOfOccurance
12 1 1 appears (1 ) times
12 2 2 appears (1 ) times
xx x x appears (2 ) times
and CTE function is :
;with cte as
(
select Data , SUBSTRING(Data,1,1) as Chars,1 as startpos from #t
union all
select Data, SUBSTRING(Data, startpos+1,1) as char,startpos+1 from cte where startpos+1<=LEN(data)
)
select Data,Chars,Cast(Chars as varchar(1)) + ' appears (' + cast(COUNT(*) as varchar(5))+ ' ) times' as 'NumberOfOccurance' from cte
group by data, chars
Actually I just want to make my answer into this :
data Number_of_occurances
12 1 appears (1) times 2 appears (1) times
xx x appears (2) times
I have tries this :
; With Ctea as
(
select Data , SUBSTRING(Data,1,1) as Chars,1 as startpos from #t
union all
select Data, SUBSTRING(Data, startpos+1,1) as char,startpos+1 from ctea where startpos+1<=LEN(data)
)
select Data,Chars,REPLACE((SELECT (Cast(Chars as varchar(1)) + ' appears (' + cast(COUNT(*) as varchar(5))+ ' ) times') AS [data()] FROM Ctea t2 WHERE t2.Data = t1.data FOR XML PATH('')), ' ', ' ;') As Number_of_occurances from ctea as t1
group by t1.data, t1.Chars
It says :
Column 'Ctea.Chars' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
when I use temp table and getting my exact answer , but cant do it CTE
Can anyone make my result ?

The problem has nothing to do with your CTE. It actually lies in the following subquery:
SELECT (Cast(Chars as varchar(1)) +
' appears (' +
cast(COUNT(*) as varchar(5)) +
' ) times') AS [data()]
FROM Ctea t2
WHERE t2.Data = t1.data
FOR XML PATH('')
Note how you are using the aggregate COUNT(*) here as well as the column Chars. You need to group by at least Chars here:
SELECT (Cast(Chars as varchar(1)) +
' appears (' +
cast(COUNT(*) as varchar(5)) +
' ) times') AS [data()]
FROM Ctea t2
WHERE t2.Data = t1.data
GROUP BY t2.Chars
FOR XML PATH('')
Furthermore, you do not want to select or group by t1.Chars in the outer query because it will result in one row per value of Chars:
data chars Number_of_occurances
12 1 1 appears (1) times 2 appears (1) times
12 2 1 appears (1) times 2 appears (1) times
xx x x appears (2) times
Finally you should most likely be using the STUFF function, and not REPLACE, as you are trying to create a space-delimited list ("1 appears (1) times 2 appears (1) times"), not replace all space characters with a space and a semicolon ("1 ;appears ;(1) ;times ;2 ;appears ;(1) ;times").
Therefore your final query should be:
; With Ctea as
(
select Data , SUBSTRING(Data,1,1) as Chars,1 as startpos from #t
union all
select Data, SUBSTRING(Data, startpos+1,1) as char,startpos+1 from ctea
where startpos+1<=LEN(data)
)
select Data,
STUFF((SELECT cast(' ' as varchar(max)) + (Cast(Chars as varchar(1)) + ' appears (' + cast(COUNT(*) as varchar(5))+ ' ) times') AS [data()]
FROM Ctea t2
WHERE t2.Data = t1.data
GROUP BY t2.Chars
FOR XML PATH('')), 1, 1, '') As Number_of_occurances
from ctea as t1
group by t1.data

You can use FOR XML PATH like this for concatenation
;with cte as
(
select Data , SUBSTRING(Data,1,1) as Chars,1 as startpos from #t
union all
select Data, SUBSTRING(Data, startpos+1,1) as char,startpos+1 from cte where startpos+1<=LEN(data)
), CTE2 AS
(
select Data,Chars,Cast(Chars as varchar(1)) + ' appears (' + cast(COUNT(*) as varchar(5))+ ' ) times' as 'NumberOfOccurance' from cte
group by data, chars
)
SELECT Data,(SELECT NumberOfOccurance + ' ' FROM CTE2 c2 WHERE c2.Data = C1.Data FOR XML PATH(''),type).value('.','VARCHAR(MAX)') as Number_of_occurances
FROM CTE2 C1
GROUP BY Data

Related

How to make a line break in a stuff function when using DISTINCT

I have a stuff function that concatenates multiple records and I put a line break after every second record and its works fine with this query:
STUFF((
SELECT CASE WHEN ROW_NUMBER() OVER (order by new_name) % 2 = 1 THEN CHAR(10) ELSE ',' END + new_name
FROM new_subcatagories
FOR XML PATH('')), 1, 1, '')
and the result is
Auditory,Kinesthetic vestibular
Multitasking,Planning & organization
Proprioception,Tactile
Vestibular tactile,Visual
But I want now to make this with a other column that I need to DISTINCT and I can't get it work my query is:
STUFF((
SELECT distinct (CASE WHEN ROW_NUMBER() OVER (order by new_maincatgoriesname) % 2 = 1 THEN CHAR(10) ELSE ',' END
+ new_maincatgoriesname)
FOR XML PATH('')), 1, 1, '')
and I get the result is in multiple not expected ways for example
Executive Function
Sensory Discrimination
Sensory modulation ,Multitasking,Sensory Discrimination,Sensory modulation
or other not expected ways, and I want the result to be
Executive Function,Sensory Discrimination
Sensory modulation,Multitasking
If someone can help my it will be really appreciated.
DISTINCT applies to the entire row so having an extra column populated with unneeded data (such as ROW_NUMBER()) would give invalid results.
To fix it you need to add another query nesting level.
DECLARE #Blah TABLE( new_maincatgoriesname VARCHAR( 200 ))
INSERT INTO #Blah
VALUES( 'Executive Function' ), ( 'Sensory Discrimination' ), ( 'Multitasking' ),
( 'Sensory Discrimination' ), ( 'Executive Function' ), ( 'Sensory modulation' )
SELECT
STUFF( CAST((
-- Step 2: manipulate result of Step 1
SELECT (CASE WHEN ROW_NUMBER() OVER (order by new_maincatgoriesname) % 2 = 1 THEN CHAR(10) ELSE ',' END + new_maincatgoriesname )
FROM
-- Step 1: Get distinct values
( SELECT DISTINCT new_maincatgoriesname
FROM #Blah ) AS MainQuery
FOR XML PATH('') ) AS VARCHAR( 2000 )), 1, 1, '' )
Output:
Executive Function,Multitasking
Sensory Discrimination,Sensory modulation

SQL split string (all possible combination)

I would like to transform this string:
A1+A2+A3.B1+B2.C1
into
A1.B1.C1
A1.B2.C1
A2.B1.C1
A2.B2.C1
A3.B1.C1
A3.B2.C1
How can I do that? (note that each dimension(= a group separate by .), could have x values, I mean it can be A1+A2.B1.C1 or A1+A2.B1+B2+B3+B4+B5.C1+C2)
Thanks
If you have only 3 columns, then just use STRING_SPLIT: number your groups from first split and then do a join 3 times and select each group on corresponding join.
with a as (
select s2.value as v, dense_rank() over(order by s1.value) as rn
from STRING_SPLIT('A1+A2+A3.B1+B2.C1', '.') as s1
cross apply STRING_SPLIT(s1.value, '+') as s2
)
select
a1.v + '.' + a2.v + '.' + a3.v as val
from a as a1
cross join a as a2
cross join a as a3
where a1.rn = 1
and a2.rn = 2
and a3.rn = 3
| val |
----------
|A1.B1.C1|
|A2.B1.C1|
|A3.B1.C1|
|A1.B2.C1|
|A2.B2.C1|
|A3.B2.C1|
If you have indefinite number of groups, then it's better to use recursive CTE instead of dynamic SQL. What you should do:
Start with all the values from the first group.
On recursion step crossjoin all the values of the next group (i.e. step group number is current group number + 1).
Select the last recursion step where you'll have the result.
Code is below:
with a as (
select s2.value as v, dense_rank() over(order by s1.value) as rn
from STRING_SPLIT('A1+A2+A3.B1+B2+B3+B4.C1+C2.D1+D2+D3', '.') as s1
cross apply STRING_SPLIT(s1.value, '+') as s2
)
, b (val, lvl) as (
/*Recursion base*/
select cast(v as nvarchar(1000)) as val, rn as lvl
from a
where rn = 1
union all
/*Increase concatenation on each iteration*/
select cast(concat(b.val, '.', a.v) as nvarchar(1000)) as val, b.lvl + 1 as lvl
from b
join a
on b.lvl + 1 = a.rn /*Recursion step*/
)
select *
from b
where lvl = (select max(rn) from a) /*You need the last step*/
order by val
I won't add a tabular result since it is quite big. But try it by yourself.
Here is SQL server version and fiddle:
with lst(s) as (select * from STRING_SPLIT('A1+A2.B1+B2+B3+B4+B5.C1+C2','.'))
select t1+'.'+t2+'.'+t3 as res from
(select * from STRING_SPLIT((select s from lst where s like 'A%'), '+')) s1(t1) cross join
(select * from STRING_SPLIT((select s from lst where s like 'B%'), '+')) s2(t2) cross join
(select * from STRING_SPLIT((select s from lst where s like 'C%'), '+')) s3(t3);
Of course you can grow it in a regular fashion if the number of dimensions grows.
Here is a Postgresql solution:
with x(s) as (select string_to_array('A1+A2.B1+B2+B3+B4+B5.C1+C2','.'))
select t1||'.'||t2||'.'||t3 as res from
unnest((select string_to_array(s[1],'+') from x)) t1 cross join
unnest((select string_to_array(s[2],'+') from x)) t2 cross join
unnest((select string_to_array(s[3],'+') from x)) t3;
result:
res |
--------|
A1.B1.C1|
A1.B2.C1|
A1.B3.C1|
A1.B4.C1|
A1.B5.C1|
A2.B1.C1|
A2.B2.C1|
A2.B3.C1|
A2.B4.C1|
A2.B5.C1|
A1.B1.C2|
A1.B2.C2|
A1.B3.C2|
A1.B4.C2|
A1.B5.C2|
A2.B1.C2|
A2.B2.C2|
A2.B3.C2|
A2.B4.C2|
A2.B5.C2|
Here my code with your help. I didn't mention, but I can also have more or less than 3 parts, so I'm using a dynamic SQL for this:
declare #FILTER varchar(max)='B+C+D.A+G.T+Y+R.E'
-- Works also with A.B.C
-- Works also with A+B+C.D.E+F
-- Works also with A+B+C.D+E+F+G+H
declare #NB int
declare #SQL varchar(max)=''
select #NB=count(*) from STRING_SPLIT(#FILTER,'.')
set #SQL='
;with T(A,B) as
(select *, row_number() over (order by (select NULL))
from STRING_SPLIT(''' + #FILTER + ''',''.'')
)
select '
;with T(V,N) as (
select *, row_number() over (order by (select NULL))
from STRING_SPLIT(#FILTER,'.')
)
select #SQL=#SQL + 'T' + cast(N as varchar(max)) + ' + ''.'' + ' from T
set #SQL=left(#SQL,len(#SQL)-1) + ' as res from'
;with T(V,N) as (
select *, row_number() over (order by (select NULL))
from STRING_SPLIT(#FILTER,'.')
)
select #SQL=#SQL + '
(select * from STRING_SPLIT((select A from T where B=' + cast(N as varchar(max)) + '), ''+'')) s' + cast(N as varchar(max)) + '(t' + cast(N as varchar(max)) + ') cross join'
from T
set #SQL=left(#SQL,len(#SQL)-len('cross join'))
exec(#SQL)

MS SQL Server - concatenating in a particular way

I've got a particular table that I need to concatenate using something like substring, but in a particular way. There are going to be lots of nulls but we still need to pay attention to them.
Basically, I've got something like...
PID Date Flag1 Flag2 Code
11 01/01/2014 1 0 16
11 25/12/2014 1 1 48
11 16/07/2016 0 1 9
12 07/01/2014 0 16
12 08/01/2014 1
12 09/01/2014 16
13 01/10/2014 1 4
13 01/11/2014 1 0 16
13 01/12/2014 0 48
Would result in (very long)...
PID Date Flag1 Flag2 Code
11 01/01/2014,25/12/2014,16/07/2014, 1,1,0, 0,1,1, 16,48,9,
12 07/01/2014,08/01/2014,09/01/2014, ,1,, 0,,, 16,,16,
13 01/10/2014,01/11/2014,01/12/2014, 1,1,, ,0,0, 4,16,48,
This way, in some code I would use later, I would be able to tell which date each flag belongs to.
Any ideas? So far I've just been using the regular substring commands which do put things into the correct fields, but I can't tell what belongs with what.
SELECT DISTINCT PS2.PID, substring
((SELECT ',' + CAST(CONVERT(VARCHAR(10), PS1.Date, 111) AS NVARCHAR) AS [text()]
FROM dbo.PS PS1
WHERE PS1.PID = PS2.PID
ORDER BY PS1.PID, PS1.Date FOR XML PATH('')), 2, 9999) + ',' [Date], substring
((SELECT ',' + LEFT(CAST(LUC.Code AS NVARCHAR), 2) AS [text()]
FROM dbo.PS PS1 INNER JOIN
dbo.MyCodes LUC ON PS1.Code = LUC.Id
WHERE PS1.PID = PS2.PID
ORDER BY PS1.PID, PS1.Date FOR XML PATH('')), 2, 9999) + ',' [Code], substring
((SELECT ',' + LEFT(CAST(PS1.Flag1 AS NVARCHAR), 1) AS [text()]
FROM dbo.PS PS1
WHERE PS1.PID = PS2.PID
ORDER BY PS1.PID, PS1.Date FOR XML PATH('')), 2, 9999) + ',' [Flag1], substring
((SELECT ',' + LEFT(CAST(PS1.Flag2 AS NVARCHAR), 1) AS [text()]
FROM dbo.PS PS1
WHERE PS1.PID = PS2.PID
ORDER BY PS1.PID, PS1.Date FOR XML PATH('')), 2, 9999) + ',' [Flag2]
FROM dbo.PS PS2
Should also note, we will always have a Date. That will not be null. Same with the PID (as that's what they're grouped on).
Please try this, I have used Date column as Edate so please replace that column and table name with your original one:
SELECT t1.PID,
STUFF(
(SELECT ',' + cast(EDate AS varchar)
FROM #tmpone t WHERE t.PID = t1.PID
FOR XML PATH(''))
, 1, 1, '') Edate,
STUFF(
(SELECT ',' + cast(Flag1 AS varchar)
FROM #tmpone t WHERE t.PID = t1.PID
FOR XML PATH(''))
, 1, 1, '') Flag1,
STUFF(
(SELECT ',' + cast(Flag2 AS varchar)
FROM #tmpone t WHERE t.PID = t1.PID
FOR XML PATH(''))
, 1, 1, '') Flag2,
STUFF(
(SELECT ',' + cast(Code AS varchar)
FROM #tmpone t WHERE t.PID = t1.PID
FOR XML PATH(''))
, 1, 1, '') Code
FROM #tmpone t1
GROUP BY t1.PID

Aggregate multiple rows into new single long row of data

I have a database with many rows of data per "case". Each "case" has a unique ID, but each row has a "multiple-choice-element" and a "value". Obviously there is a new row every time the user selects one of the multiple choice elements(mce) and the new value too. The unique ID is like a linchpin holding all of the rows together as a common element for this instance
The data is as follows:
UniqueID Value Text Username Contact
--------------------------------------------------
123456 No Sound Horn Johnson 0788
123456 Broken Headlight Johnson 0788
123456 Broken Windscreen Johnson 0788
I am looking to keep just one row of data, their user details, the key (unique ID), and then have multiple columns for each mce and each value.
UniqueID Username Contact Text Value Text Value Text Value
---------------------------------------------------------------------------------
123456 Johnson 0788 Horn No Sound Headlight Broken Windscreen Broken
I have done this using an update statement for each mce based on the Unique ID so far, but it's a bit clunky and long winded as a stored-procedure and can take quite a bit of time to run.
Can anyone suggest a better way please.
Thank you.
EDIT 2: I wasn't even sure this was possible, but I think I've found a way. Unfortunately, it's extremely hacky, and since you never posted your RDBMS tag, I don't even know if it will work for you.
I don't particularly like the solution, as I said - it feels clumsy to me, but it does work and I don't have any more time to dedicate to this issue. Unless someone else can figure out a way to make it work more efficiently, this is the best I can do for you. Note that I create and reference a temp table called #Numbers - you'll need this to stay in your query. Also note that you might end up with more columns than you want, but that will have to stay.
The following script will allow you to pivot two columns, preserving the relationship between a distinct value in column #1 (Txt) and multiple values in column #2 (Value)
CREATE TABLE #Data (UniqueID INT, Value varchar(10), Txt varchar(10), Username varchar(10), Contact INT)
INSERT INTO #Data (UniqueID, Value, Txt, Username, Contact)
SELECT 123456, 'No Sound', 'Horn', 'Johnson', 0788 UNION
SELECT 123456, 'Broken', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Smashed', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Shattered', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Busted', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Inop', 'Brake', 'Johnson', 0788
DECLARE #sql AS varchar(max)
DECLARE #vpivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #vselect_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #tpivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #tselect_list AS varchar(max) -- Leave NULL for COALESCE technique
CREATE TABLE #Numbers (Number INT)
;WITH NumberSequence( Number ) AS
(
SELECT 1 as Number
UNION ALL
SELECT Number + 1
FROM NumberSequence
WHERE Number < 100
)
INSERT INTO #Numbers (Number)
SELECT Number FROM NumberSequence
SELECT
#vpivot_list = COALESCE(#vpivot_list + ', ', '') + '[' + TxtValCombinations + ']'
FROM
(
SELECT
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
INNER JOIN
#Numbers Val ON
Val.Number <
(
SELECT MAX(CountValue) MaxCountValue
FROM
(
SELECT COUNT(Value) CountValue
FROM #Data
GROUP BY UniqueID, Txt
) cv
)
) PossibleValues
SELECT
#tpivot_list = COALESCE(#tpivot_list + ', ', '') + '[' + TxtCombinations + ']'
FROM
(
SELECT
'T' + CAST(Txt.Number AS VARCHAR(5)) AS TxtCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
) PossibleValues
SELECT #vselect_list = STUFF(
(
SELECT',' +
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) --AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
INNER JOIN
#Numbers Val ON
Val.Number <
(
SELECT MAX(CountValue) MaxCountValue
FROM
(
SELECT COUNT(Value) CountValue
FROM #Data
GROUP BY UniqueID, Txt
) cv
)
ORDER BY Txt.Number, Val.Number
FOR XML PATH(''), type
).value('.', 'varchar(max)'), 1, 1, '')
SELECT #tselect_list = STUFF(
(
SELECT TxtValCombinations
FROM
(
SELECT',MAX(' +
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) + ') AS '+
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
INNER JOIN
#Numbers Val ON
Val.Number <
(
SELECT MAX(CountValue) MaxCountValue
FROM
(
SELECT COUNT(Value) CountValue
FROM #Data
GROUP BY UniqueID, Txt
) cv
)
UNION ALL
SELECT',MAX(' +
'T' + CAST(Txt.Number AS VARCHAR(5)) +') AS T' + CAST(Txt.Number AS VARCHAR(5)) --AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
) s
ORDER BY TxtValCombinations
FOR XML PATH(''), type
).value('.', 'varchar(max)'), 1, 1, '')
SET #sql = '
SELECT UniqueID, Username, Contact, ' + #tselect_list + '
FROM
(
SELECT UniqueID, Username, Contact, Txt, tPIVOT_CODE, ' + #vselect_list + '
FROM
(
SELECT b.UniqueID, Value, Username, Contact, Txt, tPIVOT_CODE, vPIVOT_CODE
FROM
(
SELECT
Data.UniqueID,
Data.Username,
Data.Contact,
Data.Txt,
''T'' + CAST(grpTxt.TxtNum AS VARCHAR(5)) AS tPIVOT_CODE,
Data.Value,
''T'' + CAST(grpTxt.TxtNum AS VARCHAR(5)) + ''_V'' + CAST(ROW_NUMBER() OVER (PARTITION BY Data.UniqueID, Data.Txt ORDER BY Data.Value) AS VARCHAR(4)) vPIVOT_CODE
FROM
#Data Data
INNER JOIN
(
SELECT UniqueID, Txt, ROW_NUMBER() OVER (PARTITION BY UniqueID ORDER BY Txt) AS TxtNum
FROM #Data
GROUP BY UniqueID, Txt
) grpTxt ON
Data.UniqueID = grpTxt.UniqueID AND
Data.Txt = grpTxt.Txt
) b
) vp
PIVOT (
MIN(Value)
FOR vPIVOT_CODE IN (
' + #vpivot_list + '
)
) AS vpvt
) tp
PIVOT (
MIN(Txt)
FOR tPIVOT_CODE IN (
' + #tpivot_list + '
)
) AS tpvt
GROUP BY UniqueID, UserName, Contact
ORDER BY UniqueID, UserName, Contact
'
--PRINT #sql
--PRINT #tselect_list
--PRINT #vpivot_list
EXEC (#sql)
DROP TABLE #Data
DROP TABLE #Numbers

sql server(find the count of table base on condition)

i have a table like following
RequestNo Facility status
1 BDC1 Active
1 BDC2 Active
1 BDC3 Active
2 BDC1 Active
2 BDC2 Active
i want like this
RequestNo Facilty Count
1 BDC (1,2,3) 1
2 BDC(1,2) 1
the count should display based on Status with facilty.Fcilityv should take as BDC only
Try this, (assuming that your facility is fixed 4 character code)
SELECT RequestNo, Fname + '(' + FnoList + ')' Facilty, count(*) cnt
FROM
(
SELECT distinct RequestNo,
SUBSTRING(Facility,1,3) Fname,
stuff((
select ',' + SUBSTRING(Facility,4,4)
from Dummy
where RequestNo = A.RequestNo AND
SUBSTRING(Facility,1,3) = SUBSTRING(A.Facility,1,3)
for xml path('')
) ,
1, 1, '') as FnoList
FROM Dummy A
) x
group by RequestNo, Fname, FnoList;
SQL DEMO
This doesn't put any constraints on the length of the Facility field. It strips out the chars from the beginning and the numeric numbers from the ending:
SELECT RequestNo, FacNameNumbers, COUNT(Status) as StatusCount
FROM
(
SELECT DISTINCT
t1.RequestNo,
t1.Status,
substring(facility, 1, patindex('%[^a-zA-Z ]%',facility) - 1) +
'(' +
STUFF((
SELECT DISTINCT ', ' + t2.fac_number
FROM (
select distinct
requestno,
substring(facility, 2 + len(facility) - patindex('%[^0-9 ]%',reverse(facility)), 9999) as fac_number
from facility
) t2
WHERE t2.RequestNo = t1.RequestNo
FOR XML PATH (''))
,1,2,'') + ')' AS FacNameNumbers
FROM Facility t1
) final
GROUP BY RequestNo, FacNameNumbers
And the SQL Fiddle