Query to generate string based on values in multiple columns - sql

I have a table in database that has structure and data as follows:
+-----+--------+--------+--------+--------+--------+
| ID | Col1 | Col2 | Col3 | Col4 | Col5 |
+-----+--------+--------+--------+--------+--------+
| 1 | MALE | MALE | FEMALE | NULL | NULL |
| 2 | FEMALE | MALE | NULL | NULL | NULL |
| 3 | FEMALE | NULL | NULL | NULL | NULL |
| 4 | MALE | OTHER | FEMALE | FEMALE | NULL |
| 5 | MALE | OTHER | FEMALE | MALE | FEMALE |
+-----+--------+--------+--------+--------+--------+
The order of data has to be in order of first appearance in the columns, from Col1 to Col5, to get the following output:
+-----+--------------------------------------------+
| ID | Remarks |
+-----+--------------------------------------------+
| 1 | 2 Male and 1 Female |
| 2 | 1 Female and 1 Male |
| 3 | 1 Female |
| 4 | 1 Male, 1 Other and 2 Female |
| 5 | 2 Male, 1 Other and 2 Female |
+-----+--------------------------------------------+

I would do this use apply.
select t.*,
(case when num_male = 0 and num_female = 0 and num_other = 0
then ''
when num_male = 0 and num_female = 0
then replace('num_other OTHER', 'num_other', num_other)
when num_male = 0 and num_other = 0
then replace('num_female FEMALE', 'num_female', num_female)
when num_male = 0 and num_female = 0
then replace('num_male MALE', 'num_male', num_male)
when num_male = 0
then replace(replace(replace('num_other OTHER AND num_female FEMALE'), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
when num_other = 0
then replace(replace(replace('num_male MALE AND num_female FEMALE), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
when num_female = 0
then replace(replace(replace('num_male MALE AND num_other OTHER), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
else replace(replace(replace('num_male MALE, num_other OTHER AND num_female FEMALE), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
end) as remarks
from t cross apply
(select sum(case when col = 'FEMALE' then 1 else 0 end) as num_females,
sum(case when col = 'MALE' then 1 else 0 end) as num_males,
sum(case when col = 'OTHER' then 1 else 0 end) as num_other
from (values (col1), (col2), (col3), (col4), (col5)) v(col)
) v;
I don't see an advantage to cleverness in calculating the remarks structure.

This is a very unusual requirement and I would imagine that you don't really need to have the values in the order you are asking for, but to answer the question as asked:
-- Build data
declare #t table(ID int,Col1 varchar(10),Col2 varchar(10),Col3 varchar(10),Col4 varchar(10),Col5 varchar(10));
insert into #t values
(1,'MALE','MALE','FEMALE',null,null)
,(2,'FEMALE','MALE',null,null,null)
,(3,'FEMALE',null,null,null,null)
,(4,'MALE','OTHER','FEMALE','FEMALE',null)
,(5,'MALE','OTHER','FEMALE','MALE','FEMALE')
;
-- Query
with d as
( -- Manually unpivot the 5 Cols and add a row number
select 1 as r
,ID
,Col1 as Col
from #t
union all
select 2
,ID
,Col2 as Col
from #t
union all
select 3
,ID
,Col3 as Col
from #t
union all
select 4
,ID
,Col4 as Col
from #t
union all
select 5
,ID
,Col5 as Col
from #t
)
,c as
( -- Replace Col values for presentation and add in aggregated row numbers, ranks and counts for each value
select ID
,case Col when 'MALE' then 'Male'
when 'FEMALE' then 'Female'
when 'OTHER' then 'Other'
end as Col
,row_number() over (partition by ID order by r) as rn
,dense_rank() over (partition by ID order by r) as drk
,rank() over (partition by ID, Col order by r) as rk
,count(Col) over (partition by ID, Col) as c
from d
where Col is not null
)
,o as
( -- Filter rows down to just the first instance of that Col value to get the presentation order
select ID
,row_number() over (partition by ID order by rn) as o
,cast(c as varchar(10)) + ' ' + Col as c
from c
where rk = 1
)
-- Collate the data per presentation rules
select o.ID
,o.c
+ isnull(case when o3.ID is null
then ' and ' + o2.c
else ', ' + o2.c + ' and ' + o3.c
end
,'') as Remarks
from o
left join o as o2
on o.ID = o2.ID
and o2.o = 2
left join o as o3
on o.ID = o3.ID
and o3.o = 3
where o.o = 1
order by o.ID;
Output:
+----+------------------------------+
| ID | Remarks |
+----+------------------------------+
| 1 | 2 Male and 1 Female |
| 2 | 1 Female and 1 Male |
| 3 | 1 Female |
| 4 | 1 Male, 1 Other and 2 Female |
| 5 | 2 Male, 1 Other and 2 Female |
+----+------------------------------+

Related

Group by a column and display the value of the column that matches the condition

I have to GROUP BY a column and if there are more than one entries for it, I need to display the one that satisfies the condition. If only one entry is there it should be displayed too.
ID | Name | GroupId
1 | A | x
2 | A | y
3 | B | x
4 | C | z
5 | A | z
6 | B | y
Condition: COUNT(GroupId) > 1 then display y
Expected result:
Name | GroupId
A | y
B | y
C | z
I have found answers with inner query. Is that possible to do without inner queries?
Note: If there are two or more records for a name and none have 'y' then have to display 'x' even if not there
With this:
select
name,
case
when count(distinct groupid) = 1 then max(groupid)
when sum(case when groupid = 'y' then 1 end) > 0 then 'y'
else 'x'
end groupid
from tablename
group by name
For:
CREATE TABLE tablename (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
groupid TEXT
);
INSERT INTO tablename (id,name,groupid) VALUES
(1,'A','x'),
(2,'A','y'),
(3,'B','x'),
(4,'C','z'),
(5,'A','z'),
(6,'B','y'),
(7,'D','k'),
(8,'D','m');
The results are:
| name | groupid |
| ---- | ------- |
| A | y |
| B | y |
| C | z |
| D | x |
See the demo.
You describe:
select t.name,
(case when count(*) > 2 then 'y'
else max(groupid)
end)
from t;
But I think you really want:
select t.name,
(case when min(groupid) <> max(groupid) then 'y'
else max(groupid)
end)
from t;
You can try below -
select name, case when count(groupid)>2 then 'y' else min(groupid) end as groupid
from tablename a
group by name

SQL - self join 'n' times with condition

I have the following table:
Id | Type
--------------------------------------------
C1C1A90D-B131-4450-B1BF-5041F36F9144 | 1
C7B1752D-FD30-445A-AD6C-51D1434607D3 | 2
3AAF8BB6-A6D4-4780-BEF9-ACBBF75A85DE | 3
--------------------------------------------
67EF1537-A22E-4D2D-AAEA-FC0D9E2B9912 | 1
546519ED-5E78-4DAD-ADFF-9DC0AA67B763 | 2
8F66A3F9-C652-4758-8E17-B4DE0B0D85A4 | 3
--------------------------------------------
-- ... and so on ... --
Now, I need a specific type of SELECT (something like this):
SELECT
[Id] AS [OneId] -- Where [Type] = 1,
[Id] AS [TwoId] -- Where [Type] = 2,
[Id] AS [ThreeId] -- Where [Type] = 3
FROM Table
This is what I've tried so far but with bad results:
SELECT
oneI.[Id] AS [OneId] -- Where [Type] = 1,
twoI.[Id] AS [TwoId] -- Where [Type] = 2,
threeI.[Id] AS [ThreeId] -- Where [Type] = 3
FROM Table AS i
INNER JOIN Table AS oneI ON
i.[Id] = oneI.[Id]
INNER JOIN Table AS twoI ON
i.[Id] = twoI.[Id]
INNER JOIN Table AS threeI ON
i.[Id] = threeI.[Id]
WHERE
oneI.[Type] = 1
AND twoI.[Type] = 2
AND threeI.[Type] = 3
Or even worse (which gave me more lots of combinations):
SELECT
oneI.[Id] AS [OneId] -- Where [Type] = 1,
twoI.[Id] AS [TwoId] -- Where [Type] = 2,
threeI.[Id] AS [ThreeId] -- Where [Type] = 3
FROM Table AS i, TABLE AS oneI, Table AS twoI, Table AS threeI
WHERE
oneI.[Type] = 1
AND twoI.[Type] = 2
AND threeI.[Type] = 3
EDIT:
I would also have another column on which I would group those Ids. So the updated table look like:
Id | GroupId | Type
------------------------------------------------------------------
C1C1A90D-B131-4450-B1BF-5041F36F9144 -- OneId | 1 | 1
C7B1752D-FD30-445A-AD6C-51D1434607D3 -- TwoId | 1 | 2
3AAF8BB6-A6D4-4780-BEF9-ACBBF75A85DE -- ThreeId | 1 | 3
------------------------------------------------------------------
67EF1537-A22E-4D2D-AAEA-FC0D9E2B9912 -- OneId | 2 | 1
546519ED-5E78-4DAD-ADFF-9DC0AA67B763 -- TwoId | 2 | 2
8F66A3F9-C652-4758-8E17-B4DE0B0D85A4 -- ThreeId | 2 | 3
----------------------------------------------------------------
So, I would need the following result:
OneI | TwoI | ThreeI | GroupId
------------------------------------------------------
OneId | TwoId | ThreeId | 1
OneId | TwoId | ThreeId | 2
EDIT:
I have one more special case - [Type] column can repeat:
Id | GroupId | Type
-----------------------------------------------------------------------
C1C1A90D-B131-4450-B1BF-5041F36F9144 -- OneId | 1 | 1
C7B1752D-FD30-445A-AD6C-51D1434607D3 -- TwoId | 1 | 2
3AAF8BB6-A6D4-4780-BEF9-ACBBF75A85DE -- ThreeId | 1 | 3
FEB4A345-FEA0-4530-AE52-6CF4F07E37BA -- OtherThreeId | 1 | 3
-----------------------------------------------------------------------
67EF1537-A22E-4D2D-AAEA-FC0D9E2B9912 -- OneId | 2 | 1
546519ED-5E78-4DAD-ADFF-9DC0AA67B763 -- TwoId | 2 | 2
8F66A3F9-C652-4758-8E17-B4DE0B0D85A4 -- ThreeId | 2 | 3
----------------------------------------------------------------
And now the result would be:
OneI | TwoI | ThreeI | GroupId
------------------------------------------------------
OneId | TwoId | ThreeId | 1
OneId | TwoId | OtherThreeId | 1
OneId | TwoId | ThreeId | 2
sqlfiddle
Well, this would required some sequential ordering columns, but you could also express this as
select max(case when [Type] = 1 then Id end) OneId,
max(case when [Type] = 2 then Id end) TwoId,
max(case when [Type] = 3 then Id end) ThreeId
from (select *,
row_number() over (order by (select 1)) Seq
from table
) t
group by (Seq - [Type]);
EDIT :- However, if you want to include group also then use them as in select statement as
select (Seq - [Type]) as GroupId,
max(case when [Type] = 1 then 'OneId' end) OneI,
max(case when [Type] = 2 then 'TwoId' end) TwoI,
max(case when [Type] = 3 then 'ThreeId' end) ThreeI
from (select *,
row_number() over (order by (select 1)) Seq
from table
) t
group by (Seq - [Type]);
For your updated table you can directly use table with group by clause with your GroupId column as then you don't use row_number() function and subquery
select max(case when [Type] = 1 then 'OneId' end) OneI,
max(case when [Type] = 2 then 'TwoId' end) TwoI,
max(case when [Type] = 3 then 'ThreeId' end) ThreeI,
GroupId
from table t
group by GroupId;
Demo
You need an Grp attribute then specifies which rows are together in one group. Something like this
Id | Type | Grp
----------------------------------------------------
C1C1A90D-B131-4450-B1BF-5041F36F9144 | 1 | 1
C7B1752D-FD30-445A-AD6C-51D1434607D3 | 2 | 1
3AAF8BB6-A6D4-4780-BEF9-ACBBF75A85DE | 3 | 1
---------------------------------------------------
67EF1537-A22E-4D2D-AAEA-FC0D9E2B9912 | 1 | 2
546519ED-5E78-4DAD-ADFF-9DC0AA67B763 | 2 | 2
8F66A3F9-C652-4758-8E17-B4DE0B0D85A4 | 3 | 2
--------------------------------------------------
Then you can use conditional aggregation like this
SELECT
MAX(CASE WHEN [Type] = 1 THEN [Id] END) AS [OneId],
MAX(CASE WHEN [Type] = 2 THEN [Id] END) AS [TwoId],
MAX(CASE WHEN [Type] = 3 THEN [Id] END) AS [ThreeId]
FROM YourTable
GROUP BY Grp
Looks like this would do the job for you
; WITH CTE
AS
(
SELECT
Id,
[Type]
FROM YourTable
WHERE [Type]
IN
(
1,2,3,4,5
)
)
SELECT
*
FROM CTE
PIVOT
(
MAX(id)
FOR [Type] IN
(
[1],[2],[3],[4],[5]
)
)Pvt

How to select an attribute based on string value within a group

Table name: Copies
+------------------------------------+
| group_id | my_id | stuff |
+------------------------------------+
| 900 | 1 | Y |
| 900 | 2 | N |
| 901 | 3 | Y |
| 901 | 4 | Y |
| 902 | 5 | N |
| 902 | 6 | N |
| 903 | 7 | N |
| 903 | 8 | Y |
---------------------------------------
The output should be:
+------------------------------------+
| group_id | my_id | stuff |
+------------------------------------+
| 900 | 1 | Y |
| 903 | 8 | Y |
--------------------------------------
Hello, I have a table where I have to discern a 'good' record within a group_id based on a positive (Y) value within the stuff field. I need the full record where only one value fits this criteria. If both stuff values are Y or both are N, then they shouldn't be selected. It seems like this should be simple, but I am not sure how to proceed.
One option here is to use conditional aggregation over each group_id and retain a group if it has a mixture of yes and no answers.
WITH cte AS (
SELECT group_id
FROM Copies
GROUP BY group_id
HAVING SUM(CASE WHEN stuff = 'Y' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN stuff = 'N' THEN 1 ELSE 0 END) > 0
)
SELECT c1.*
FROM Copies c1
INNER JOIN cte c2
ON c1.group_id = c2.group_id
WHERE c1.stuff = 'Y'
One advantage of this solution is that it will show all columns of matching records.
select group_id,
min(my_id)
keep (dense_rank first order by case stuff when 'Y' then 0 end) as my_id,
'Y' as stuff
from table_1
group by group_id
having min(stuff) != max(stuff)
with rows as(
select group_id, my_id, sum(case when stuff = 'Y' then 1 else 0 end) c
from copies
group by group_id, my_id)
select c.*
from copies c inner join rows r on (c.group_id = r.group_id and c.my_id = r.my_id)
where r.c = 1;
Try this:
SELECT C.*
FROM COPIES C,
COPIES C2
WHERE C.STUFF='Y'
AND C2.STUFF='N'
AND C.GROUP_ID=C2.GROUP_ID
Try this:
SELECT t1.*
FROM copies t1
JOIN (
SELECT group_id
FROM copies
GROUP BY group_id
HAVING COUNT(CASE WHEN stuff = 'Y' THEN 1 END) = 1 AND
COUNT(CASE WHEN stuff = 'N' THEN 1 END) = 1
) t2 ON t1.group_id = t2.group_id
WHERE t1.stuff = 'Y'
This works as long as group_id values appear in couples.

Count each condition within group

For every unique GroupId I would like to get a count of each IsGreen, IsRound, IsLoud condition and a total number of rows.
Sample data:
-----------------------------------------------------
id | ItemId | GroupId | IsGreen | IsRound | IsLoud
----+--------+---------+---------+---------+---------
1 | 1001 | 1 | 0 | 1 | 1
2 | 1002 | 1 | 1 | 1 | 0
3 | 1003 | 2 | 0 | 0 | 0
4 | 1004 | 2 | 1 | 0 | 1
5 | 1005 | 2 | 0 | 0 | 0
6 | 1006 | 3 | 0 | 0 | 0
7 | 1007 | 3 | 0 | 0 | 0
Desired result:
----------------------------------------------------------
GroupId | TotalRows | TotalGreen | TotalRound | TotalLoud
--------+-----------+------------+------------+-----------
1 | 2 | 1 | 2 | 1
2 | 3 | 1 | 0 | 1
3 | 2 | 0 | 0 | 0
I'm using the following code to create the table, the problem I'm having is that if any of the groups have no rows that match one of the conditions that group does not appear in the final table. What is the best way to accomplish what I want to do?
SELECT total.GroupId
, total.[Count] AS TotalRows
, IsGreen.[Count] AS TotalGreen
, IsRound.[Count] AS TotalRound
, IsLoud.[Count] AS TotalLoud
FROM (
SELECT GroupId
, count(*) AS [Count]
FROM TestData
GROUP BY GroupId
) TotalRows
INNER JOIN (
SELECT GroupId
, count(*) AS [Count]
FROM TestData
WHERE IsGreen = 1
GROUP BY GroupId
) IsGreen ON IsGreen.GroupId = TotalRows.GroupId
INNER JOIN (
SELECT GroupId
, count(*) AS [Count]
FROM TestData
WHERE IsRound = 1
GROUP BY GroupId
) IsRound ON IsRound.GroupId = TotalRows.GroupId
INNER JOIN (
SELECT GroupId
, count(*) AS [Count]
FROM TestData
WHERE IsLoud = 1
GROUP BY GroupId
) IsLoud ON IsLoud.GroupId = TotalRows.GroupId
You can use count to count rows per each [GroupId] and sum to count each property .
select [GroupId]
, count([GroupId]) as [TotalRows]
, sum([IsGreen]) as [TotalGreen]
, sum([IsRound]) as [TotalRound]
, sum([IsLoud]) as [TotalLoud]
from [TestData]
group by [GroupId]
Use conditional Aggregate. Try this.
SELECT GroupId,
Count(GroupId) TotalRows,
Count(CASE WHEN IsGreen = 1 THEN 1 END) TotalGreen,
Count(CASE WHEN IsRound = 1 THEN 1 END) TotalRound,
Count(CASE WHEN IsLoud = 1 THEN 1 END) TotalLoud
FROM tablename
GROUP BY GroupId

Pivot multiple rows in SQL Sever

I have data like below:
CREATE TABLE #Temp
(RuleName VARCHAR(100),
ParamValue VARCHAR(100),
ParamName VARCHAR(100))
INSERT INTO #Temp
VALUES ('BroadBandAviva','BroadBandAviva1','Rule Name'),
('BroadBandAviva','BroadBand','Expense Category name'),
('BroadBandAviva','DSF','org unit name'),
('BroadBandAviva','ASSISTANT SALES MANAGER','designation'),
('BroadBandAviva','Category A','cityclassification'),
('BroadBandAviva','2000','Eligible Amount'),
('BroadBandAviva','BroadBandAviva2','Rule Name'),
('BroadBandAviva','BroadBand','Expense Category name'),
('BroadBandAviva','DSF','org unit name'),
('BroadBandAviva','ASSISTANT SALES MANAGER','designation'),
('BroadBandAviva','ROI-Rest of India','cityclassification'),
('BroadBandAviva','2000','Eligible Amount')
and I want the output like this:
RuleName Col1 Col2 Col3 Col4 Col5 Col6
---------------------------------------------------------------------------------
BroadBandAviva Rule Name Expense Category name org unit name designation cityclassification Eligible Amount
BroadBandAviva BroadBandAviva1 BroadBand DSF ASSISTANT SALES MANAGER Category A 2000
BroadBandAviva BroadBandAviva2 BroadBand DSF ASSISTANT SALES MANAGER ROI-Rest of India 2000
Basically the first column should stay as it is, third column should become the first row and the second column should come in second row onwards. I tried with pivot function but I could not get the desired output.
Here is an alternative approach which places the values into separate columns, like this:
| RULENAME | GRPNO | COL1 | COL2 | COL3 | COL4 | COL5 | COL6 |
|----------------|-------|-----------------|-----------------------|---------------|-------------------------|---------------------|-----------------|
| BroadBandAviva | -1 | Rule Name | Expense Category name | org unit name | designation | cityclassification | Eligible Amount |
| BroadBandAviva | 0 | BroadBandAviva1 | BroadBand | DSF | ASSISTANT SALES MANAGER | Category A | 2000 |
| BroadBandAviva | 1 | BroadBandAviva2 | BroadBand | DSF | ASSISTANT SALES MANAGER | ROI-Rest of India | 2000 |
Like my previous attempt I use a CTE to retain some needed calculations that are reused in a subsequent union. Unlike the previous attempt though I use conventional case expressions and group by to achieve the pivot. Here is the query.
declare #i int = (select count(distinct ParamName) from Temp)
;with cte as (
select
*
, row_number() over(order by (select 1)) as rn
, (row_number() over(order by (select 1))-1) / #i as grpno
, row_number() over(order by (select 1)) % #i as prow
from temp
)
select
rulename
, -1 as grpno
, max(case when prow = 1 then paramname end) as col1
, max(case when prow = 2 then paramname end) as col2
, max(case when prow = 3 then paramname end) as col3
, max(case when prow = 4 then paramname end) as col4
, max(case when prow = 5 then paramname end) as col5
, max(case when prow = 0 then paramname end) as col6
from cte
WHERE rn <= #i
group by
rulename
union all
select
rulename
, grpno
, max(case when prow = 1 then paramvalue end) as col1
, max(case when prow = 2 then paramvalue end) as col2
, max(case when prow = 3 then paramvalue end) as col3
, max(case when prow = 4 then paramvalue end) as col4
, max(case when prow = 5 then paramvalue end) as col5
, max(case when prow = 0 then paramvalue end) as col6
from cte
group by
rulename
, grpno
;
To follow the logic it is useful to understand the columns PROW (parameter row) and GRPNO (grouping number), here is the CTE contents showing those columns:
/*
| RULENAME | PARAMVALUE | PARAMNAME | RN | GRPNO | PROW |
|----------------|-------------------------|-----------------------|----|-------|------|
| BroadBandAviva | BroadBandAviva1 | Rule Name | 1 | 0 | 1 |
| BroadBandAviva | BroadBand | Expense Category name | 2 | 0 | 2 |
| BroadBandAviva | DSF | org unit name | 3 | 0 | 3 |
| BroadBandAviva | ASSISTANT SALES MANAGER | designation | 4 | 0 | 4 |
| BroadBandAviva | Category A | cityclassification | 5 | 0 | 5 |
| BroadBandAviva | 2000 | Eligible Amount | 6 | 0 | 0 |
| BroadBandAviva | BroadBandAviva2 | Rule Name | 7 | 1 | 1 |
| BroadBandAviva | BroadBand | Expense Category name | 8 | 1 | 2 |
| BroadBandAviva | DSF | org unit name | 9 | 1 | 3 |
| BroadBandAviva | ASSISTANT SALES MANAGER | designation | 10 | 1 | 4 |
| BroadBandAviva | ROI-Rest of India | cityclassification | 11 | 1 | 5 |
| BroadBandAviva | 2000 | Eligible Amount | 12 | 1 | 0 |
*/
see the SQLFiddle Demo
This is as close as I can get, maybe others can use it? note the 2000 below is in the wrong place and I cannot see why or how.
| RULENAME | PARAMS |
|----------------|----------------------------------------------------------------------------------------------------|
| BroadBandAviva | Rule Name, Expense Category name, org unit name, designation, cityclassification, Eligible Amount |
| BroadBandAviva | BroadBandAviva1, BroadBand, DSF, ASSISTANT SALES MANAGER, Category A |
| BroadBandAviva | 2000, BroadBandAviva2, BroadBand, DSF, ASSISTANT SALES MANAGER, ROI-Rest of India |
^^^^^
Here's the query I used:
declare #i int = (select count(distinct ParamName) from Temp)
;with cte as (
select
*
, row_number() over(order by (select 1)) as rn
, row_number() over(order by (select 1)) / #i as grpno
, row_number() over(order by (select 1)) % #i as prow
from temp
)
SELECT
(select RuleName from cte where rn = 1) as RULENAME
, STUFF((
SELECT
', ' + c2.ParamName
FROM CTE as c2
WHERE rn <= #i
order by c2.rn
FOR XML PATH ('')
)
, 1, 1, '') as PARAMS
--, -1, -1, -1
UNION ALL
select
cte.RuleName
, caxml.params
--, cte.grpno, prow, rn
from cte
CROSS APPLY (
SELECT
STUFF((
SELECT
', ' + c2.ParamValue
FROM CTE as c2
WHERE c2.grpno = cte.grpno
order by case when c2.prow = #i then #i else c2.prow end
FOR XML PATH ('')
)
, 1, 1, '')
) caxml (params)
where prow = 1
;
see SQLFiddle Demo for more