Put numbers for each rows in STRING_AGG in SQL Server - sql

I have to put some columns and group them together by STRING_AGG also I want to put number first of each rows
What I have:
Name
Cake
Coca
ice-cream
Same
one
five
six
Sara
one
one
NULL
John
two
two
NULL
I want the output be something like this:
Name
Description
Sam
1.two 2.five 3. six
Sara
1.one 2.one
John
1.two 2.two
My Code:
SELECT Name, STRIN_AGG(CONCAT(Cake, ' ,', Coca,' ,', ice-cream))
FROM FoodTable
but I do not know how to consider numbers first of each rows in STRING_AGG

You don't need string_agg():
select name,
concat('1.' + cake, ' 2.' + coca, ' 3.' + ice_cream)
from t;
Note that + returns NULL if any value is NULL. However, concat() simply ignores NULL values.
If you really, really wanted to use string_agg() you could:
select t.name, v.all_together
from t cross apply
(select string_agg(v.n + v.val, ' ') within group (order by v.n) as all_together
from (values ('1.', t.cake),
('2.', t.coca),
('3.', t.ice_cream)
) v(n, val)
) v;

Here is a complete dynamic sql approach. No need to serialize and deserialize the data using XML or JSON. In this case the list of food items is contained in a temporary table so it reads the column names from tempdb.sys.columns.
The query uses CROSS APPLY to unpivot the columns (of food items) and assigns a ROW_NUMBER() to each non NULL item value. Something like this
drop table if exists #FoodTable;
go
create table #FoodTable(
[Name] varchar(100) not null,
Cake varchar(100) null,
Coca varchar(100) null,
[ice-cream] varchar(100) null);
--select * from dbo.test_actuals
insert #FoodTable values
('Sam', 'one', 'five', 'six'),
('Sara', 'one', 'one', null),
('Jon', 'two', 'two', null);
;with unpvt_cte([Name], item, val, rn) as (
select f.[Name], v.*, row_number() over (partition by [Name] order by (select null))
from #FoodTable f
cross apply (values ('Cake', Cake),
('Coca', Coca),
('IceCream', [ice-cream])) v(item, val)
where v.val is not null)
select [Name], string_agg(concat(rn, '.', val), ' ') within group (order by rn) answer
from unpvt_cte
group by [Name];
Name answer
Jon 1.two 2.two
Sam 1.one 2.five 3.six
Sara 1.one 2.one
to make the query dynamic
declare #food_list nvarchar(max);
select #food_list=string_agg(quotename(concat_ws(',', quotename(sysc.[name], ''''),
quotename(sysc.[name], '[]')), '()'), ',')
from tempdb.sys.columns sysc
where object_id = Object_id('tempdb..#FoodTable')
and [name]<>'Name';
declare
#sql_prefix nvarchar(max)=N'
;with unpvt_cte([Name], item, val, rn) as (
select f.[Name], v.*, row_number() over (partition by [Name] order by (select null))
from #FoodTable f
cross apply (values ',
#sql_suffix nvarchar(max)=N'
) v(item, val)
where v.val is not null)
select [Name], string_agg(concat(rn, ''.'', val), '' '') within group (order by rn) answer
from unpvt_cte
group by [Name];';
declare
#sql nvarchar(max)=concat(#sql_prefix, #food_list, #sql_suffix);
print(#sql);
exec sp_executesql #sql;
The print statement outputs the following
;with unpvt_cte([Name], item, val, rn) as (
select f.[Name], v.*, row_number() over (partition by [Name] order by (select null))
from #FoodTable f
cross apply (values ('Cake',[Cake]),('Coca',[Coca]),('ice-cream',[ice-cream])
) v(item, val)
where v.val is not null)
select [Name], string_agg(concat(rn, '.', val), ' ') within group (order by rn) answer
from unpvt_cte
group by [Name];

You may use a union to acquire the numbers for each column. Here I've used a cte but you could have used a subquery. Each query in the union renames the food type column to food and adds a column num that will be used in the final query. In the final query the where clause filters NULL foods and a group by with the string_agg and concat is used to retrieve the data in the desired format. I've included a working fiddle below:
WITH FoodTableNums AS (
SELECT Name, Cake as food, 1 as num FROM FoodTable UNION ALL
SELECT Name, Coca as food, 2 as num FROM FoodTable UNION ALL
SELECT Name, icecream as food, 3 as num FROM FoodTable
)
SELECT
Name,
STRING_AGG(CONCAT(num,'.', food),',' ) WITHIN GROUP( ORDER BY num asc) as Description
FROM
FoodTableNums
WHERE
food IS NOT NULL
GROUP BY
Name
Name
Description
John
1.two,2.two
Sam
1.one,2.five,3.six
Sara
1.one,2.one
db<>fiddle here
Let me know if this works for you.

Here is an option that is a bit more dynamic. You only have to Exclude certain columns ... in this case NAME
We use a bit of JSON to dynamically UNPIVOT the row, and then string_agg() to consolidate.
Example or dbFiddle
Select A.Name
,B.NewValue
From YourTable A
Cross Apply (
Select NewValue=STRING_AGG(concat(Seq,'.',Value),' ') within group (order by Seq)
From (
Select [Key]
,[Value]
,[Seq] = row_number() over (order by ##spid)
From OpenJson( (Select A.* For JSON Path,Without_Array_Wrapper ) )
Where [Key] not in ('Name')
) B1
) B
Results
Name NewValue
Same 1.one 2.five 3.six
Sara 1.one 2.one
John 1.two 2.two

Here is s generic way regardless of how many columns in a table.
It is based on XML/XQuery.
No need to UNPIVOT the rows, and then STRING_AGG() to consolidate.
All data in each row stays in a row.
SQL
-- DDL and data population, start
DECLARE #tbl table (
[Name] varchar(100) not NULL PRIMARY KEY,
Cake varchar(100) null,
Coca varchar(100) null,
[ice-cream] varchar(100) null);
INSERT #tbl VALUES
('Sam', 'one', 'five', 'six'),
('Sara', 'one', 'one', null),
('Jon', 'two', 'two', null);
-- DDL and data population, end
SELECT p.[Name]
, x.query('
for $r in /root/*[local-name()!="Name"]/text()
let $pos := count(root/*[. << $r]) - 1
return concat(string($pos), ".", $r)').value('text()[1]', 'VARCHAR(MAX)') AS Result
FROM #tbl AS p
CROSS APPLY (SELECT * FROM #tbl AS c
WHERE c.[Name] = p.[Name]
FOR XML PATH(''), TYPE, ROOT('root')) AS t(x);
Output
+------+--------------------+
| Name | Result |
+------+--------------------+
| Jon | 1.two 2.two |
| Sam | 1.one 2.five 3.six |
| Sara | 1.one 2.one |
+------+--------------------+

Although I agree with those saying that it would be much better to normalise your tables, but if you can't do that then this proposal makes GGordon's solution dynamic, building an SQL statement that retrieves all the columns on your FoodTable. No matter if they are 3 or 100 food columns.
CREATE TABLE FoodTable (
Name VARCHAR(4),
Cake VARCHAR(3),
Coca VARCHAR(4),
icecream VARCHAR(4)
);
INSERT INTO FoodTable ("Name", "Cake", "Coca", "icecream")
VALUES ('Sam', 'one', 'five', 'six'),
('Sara', 'one', 'one', NULL),
('John', 'two', 'two', NULL);
declare #SQL nvarchar(max);
WITH Food As (
SELECT ORDINAL_POSITION - 1 AS Num, COLUMN_NAME AS Food
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'FoodTable' AND COLUMN_NAME <> 'Name'
)
SELECT #SQL = N'WITH FoodTableNums AS ( ' +
string_agg('SELECT Name, ' + Food + ' as Food, ' + convert(varchar(20), Num) + ' as Num FROM FoodTable', ' UNION ALL ') +
') SELECT Name, STRING_AGG(CONCAT(num,''.'', food),'','' ) WITHIN GROUP( ORDER BY num asc) as Description FROM FoodTableNums WHERE food IS NOT NULL GROUP BY Name'
FROM Food;
EXECUTE sp_ExecuteSQL #SQL;
You can see it working here : Fiddle

Related

Adding 'and' at the end of a comma separated list

Currently, I'm using the stuff function to create a comma separated list per each row.
x,y,z
What I want is to add commas for n-1 items in the list, with the final item being preceded by 'and'
x,y, and z.
For these purposes, just checking row number won't work because this list is being generated per unique Id, therefore I can't just iterate to the end of the table. Code below:
SELECT DISTINCT (sw.OwnerID)
,stuff((
SELECT DISTINCT ', ' + e.pn
FROM fct.enrtablev e
WHERE sw.OwnerID = e.OwnerId
FOR XML PATH('')), 1, 1, '') AS [Pet(s)]
A bit of a hack... AND string_agg() would be a better fit if 2017+
Here we use test the row_number() of the item count sum(1) over(), when equal this is the last item in the list
Example
Declare #YourTable table (OwnerID int,pn varchar(50))
Insert Into #YourTable values
(1,'X')
,(1,'Y')
,(1,'Z')
,(1,'Z')
,(2,'Apples')
Select Distinct
OwnerID
,stuff( ( Select case when row_number() over(order by pn) = nullif(sum(1) over() ,1)
then ', and '
else ', '
end + pn
FROM (Select distinct pn
From #YourTable
Where OwnerID = A.OwnerId
) e
Order By PN
For XML Path('')), 1, 2, '') AS [Pet(s)]
From #YourTable A
Returns
OwnerID Pet(s)
1 X, Y, and Z
2 Apples
XQUery and XML data model is based on ordered sequences. Exactly what we need.
Here is a simple solution based on XQuery and its FLWOR expression.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (OwnerID int, pn VARCHAR(50));
INSERT INTO #tbl (OwnerID, pn) VALUES
(1,'X'),
(1,'Y'),
(1,'Z'),
(2,'Apples');
-- DDL and sample data population, end
SELECT p.OwnerID
, (SELECT *
FROM #tbl AS c
WHERE c.OwnerID = p.OwnerID
FOR XML PATH('r'), TYPE, ROOT('root')
).query('
for $x in /root/r/pn/text()
return if ($x is (/root/r[last()]/pn/text())[1]) then
if (count(/root/r) gt 1) then concat("and ", $x) else string($x)
else concat($x, ",")
').value('.', 'VARCHAR(MAX)') AS Result
FROM #tbl AS p
GROUP BY p.OwnerID;
Output
+---------+----------------+
| OwnerID | Result |
+---------+----------------+
| 1 | X, Y, and Z |
| 2 | Apples |
+---------+----------------+
You can achieve this using ORDER BY and count.
Declare #YourTable table (OwnerID int,pn varchar(50))
Insert Into #YourTable values
(1,'X')
,(1,'Y')
,(1,'Z')
,(2,'Apples')
;WITH CTE_OwnerIdRank as
(
SELECT ownerid, pn, row_number() over (order by ownerId) as totalrn,
count(*) over(partition by ownerid order by ownerid) as ownercnt
from #yourtable
)
SELECT distinct OwnerId,
stuff((
SELECT ', ' + CASE WHEN c.totalrn = c.ownercnt then CONCAT(' and ',c.pn) else c.pn end
FROM CTE_OwnerIdRank as c
WHERE c.OwnerID = o.OwnerId
order by c.totalrn
FOR XML PATH('')), 1, 1, '') AS [Pet(s)]
from #yourtable as o
OwnerId
Pet(s)
1
X, Y, and Z
2
Apples

Syntax Error pivoting a SQL table using string columns

I have a table Occupation like such:
Name | Occupation
-----------------
Sam | Doctor
Joe | Professor
John | Actor
Hailey | Singer
April | Doctor
My goal is to utilize the PIVOT function to display each distinct Occupation as their own column like so:
Doctor | Professor | Singer | Actor
-----------------------------------
Sam | Joe | Hailey | John
April
I went through various stack overflow questions with similar issues, and even utilized the documentation here and followed step by step. My efforts have been futile, so what gives? I am receiving a syntax error every time I run this code. Any suggestions?
Code:
SELECT *
FROM(SELECT Name, Occupation from Occupations) as src
PIVOT
(MAX(Name) FOR Name IN [Doctor], [Professor], [Singer], [Actor]) as piv;
I am solving this problem on Hackerrank, and the compiler was MySQL. I changed it to MSSQL and am no longer receiving a syntax error.
You were missing 2 () One on each side in the PIVOT part, I bolded the brackets I added:
([Doctor], [Professor], [Singer], [Actor]) - These 2
SELECT *
FROM (
SELECT Name, Occupation
FROM Occupations
) as src
PIVOT
(
MAX(Name) FOR Name IN ([Doctor], [Professor], [Singer], [Actor])
) as piv;
This query will generate the answer as per the way you need. The above answer does not provide the answer as the way you required. Try this answer.
DECLARE #sql NVARCHAR(MAX) = N''
DECLARE #cols NVARCHAR(MAX) = N''
--Making the column list dynamically
SELECT #cols = STUFF((SELECT DISTINCT ', '+QUOTENAME( [T2].[Occupation])
FROM Occupation_data [T2]
FOR XML PATH('')), 1, 1, '')
--preparing PIVOT query dynamically.
SET #SQL = ' SELECT
*
into #temp
FROM
(
SELECT row_number() over (order by name) as id, [Name], [Occupation]
FROM Occupation_data
) AS cp
PIVOT
(
min([Name]) FOR [Occupation] IN (' + #cols + ')
) AS up;
Select
row_number() over (order by doctor asc ) as id1
,*
into #temp_1
from #temp where Doctor is not null
Select
row_number() over (Order by Actor asc ) as id2
,*
into #temp_2
from #temp where Actor is not null
Select
row_number() over (Order by Professor asc ) as id3
,*
into #temp_3
from #temp where Professor is not null
Select
row_number() over (Order by Singer asc ) as id4
,*
into #temp_4
from #temp where Singer is not null
select
A.Doctor
,B.Actor
,C.Professor
,D.Singer
from
#temp_1 A inner join
#temp_2 B ON A.id1 = B.id2
INNER JOIN
#temp_3 C ON A.id1 = C.id3
INNER JOIN
#temp_4 D ON A.id1 = d.id4
drop table #temp
drop table #temp_1
drop table #temp_2
drop table #temp_3
drop table #temp_4
';
PRINT( #SQL)
EXEC (#SQL)
Output:
If this answer works for you I will make it more generic and give you so that you can apply this for any work like this.

how to write SQL query for this result?

I have so many long database so I used seq_no in commas separate using more than one sequence store in single column but now I want all sequence in a single column so I am confused how to create this sql result for this.
For example:
TABLE STRUCTURE
SR_NO IS INT ,
SEQ_NO IS VARCHAR(MAX)
SR_NO SEQ_NO
---------------------------------
1 1839073,
2 1850097,1850098,
3 1850099,1850100,1850110
I need to get this result:
SEQ_NO
--------------
1839073
1850097
1850098
1850099
1850100
1850110
Thanks!
declare #t table(Id int,seq varchar(100))
insert into #t (Id,seq) values (1,'1839073,'),(2,'1839073,1850098,'),(3,'1850099,1850100,1850110 ')
;With Cte as (
SELECT A.Id,
Split.a.value('.', 'VARCHAR(100)') AS Seq
FROM
(
SELECT Id,
CAST ('<M>' + REPLACE(seq, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM #t
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a) )
Select ID,Seq from Cte Where Seq > ''
Try splitting it with XML
SELECT SR_NO, t.c.value('.', 'VARCHAR(2000)') COL1
FROM (
SELECT SR_NO, x = CAST('<t>' +
REPLACE(SEQ_NO, ',', '</t><t>') + '</t>' AS XML)
FROM
(values(1,'1839073'),(2, '1850097,1850098'),
(3, '1850099,1850100,1850110')) y(SR_NO, SEQ_NO)
) a
CROSS APPLY x.nodes('/t') t(c)
Result:
SR_NO COL1
1 1839073
2 1850097
2 1850098
3 1850099
3 1850100
3 1850110
You can replace this with your table:
(values (1,'1839073'),(2, '1850097,1850098'),
(3, '1850099,1850100,1850110')) y(SR_NO, SEQ_NO)
This should do it: (Replace YourTableName with your table name)
;WITH CTE(NEW_SEQ_NO, SEQ_NO) as (
SELECT LEFT(SEQ_NO, CHARINDEX(',',SEQ_NO + ',') -1),
STUFF(SEQ_NO, 1, CHARINDEX(',',SEQ_NO + ','), '')
FROM YourTableName
WHERE SEQ_NO <> '' AND SEQ_NO IS NOT NULL
UNION all
SELECT LEFT(SEQ_NO, CHARINDEX(',',SEQ_NO + ',') -1),
STUFF(SEQ_NO, 1, CHARINDEX(',',SEQ_NO + ','), '')
FROM CTE
WHERE SEQ_NO <> '' AND SEQ_NO IS NOT NULL
)
SELECT NEW_SEQ_NO from CTE ORDER BY NEW_SEQ_NO
You can check this topic for more information:
Turning a Comma Separated string into individual rows
I have written the following query after referring Turning a Comma Separated string into individual rows
It will work for you
create table STRUCTURE(SR_NO int, SEQ_NO varchar(max))
insert STRUCTURE select 1, '1839073,'
insert STRUCTURE select 2, '1850097,1850098,'
insert STRUCTURE select 3, '1850099,1850100,1850110'
;with tmp(SR_NO, DataItem, SEQ_NO) as (
select SR_NO, LEFT(SEQ_NO, CHARINDEX(',',SEQ_NO+',')-1),
STUFF(SEQ_NO, 1, CHARINDEX(',',SEQ_NO+','), '')
from STRUCTURE
union all
select SR_NO, LEFT(SEQ_NO, CHARINDEX(',',SEQ_NO+',')-1),
STUFF(SEQ_NO, 1, CHARINDEX(',',SEQ_NO+','), '')
from tmp
where SEQ_NO > ''
)
Select DataItem as SEQ_NO from tmp order by SEQ_NO;

query to combine multiple columns in one columns

I have table which contain the following information.
I want to run a query and show only one line. something like this
I try to use a case statement, However i maybe have other employee who work in different state, and i do not want to list 50 state since most of employee may only work 2-3 state, but in different state. Any help will appreciate. Thanks
Since you want to pivot on multiple columns, my suggestion would be to first look at unpivoting the State, StateWages and StateTax columns then apply the PIVOT function.
You didn't specify what version of SQL Server you are using but you can use either UNPIVOT to CROSS APPLY to convert these multiple columns into rows. The syntax will be similar to:
select PRCo, Employee,
col = c.col + cast(seq as varchar(50)),
c.value
from
(
select PRCo, Employee, State, StateWages, StateTax,
row_number() over(partition by PRCo, Employee
order by state) seq
from yourtable
) d
cross apply
(
select 'State', State union all
select 'StateWages', cast(StateWages as varchar(10)) union all
select 'StateTax', cast(StateTax as varchar(10))
) c (col, value);
See SQL Fiddle with Demo. You'll notice that before I implemented the CROSS APPLY, I used a subquery with row_number() to create a unique value for each row per employee. The reason for doing this is so you can return the multiple state columns, etc. Once you have done this you can use PIVOT:
select PRCo, Employee,
State1, StateWages1, StateTax1,
State2, StateWages2, StateTax2
from
(
select PRCo, Employee,
col = c.col + cast(seq as varchar(50)),
c.value
from
(
select PRCo, Employee, State, StateWages, StateTax,
row_number() over(partition by PRCo, Employee
order by state) seq
from yourtable
) d
cross apply
(
select 'State', State union all
select 'StateWages', cast(StateWages as varchar(10)) union all
select 'StateTax', cast(StateTax as varchar(10))
) c (col, value)
) src
pivot
(
max(value)
for col in (State1, StateWages1, StateTax1,
State2, StateWages2, StateTax2)
) p;
See SQL Fiddle with Demo. The above version works great if you have a limited number of value but it sounds like you need a dynamic solution. Using the code above you can easily convert it to dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+cast(seq as varchar(10)))
from
(
select row_number()
over(partition by PRCo, Employee
order by state) seq
from yourtable
) d
cross apply
(
select 'State', 1 union all
select 'StateWages', 2 union all
select 'StateTax', 3
) c (col, so)
group by col, so, seq
order by seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT PRCo, Employee, ' + #cols + N'
from
(
select PRCo, Employee,
col = c.col + cast(seq as varchar(50)),
c.value
from
(
select PRCo, Employee, State, StateWages, StateTax,
row_number() over(partition by PRCo, Employee
order by state) seq
from yourtable
) d
cross apply
(
select ''State'', State union all
select ''StateWages'', cast(StateWages as varchar(10)) union all
select ''StateTax'', cast(StateTax as varchar(10))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + N')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. Both versions will give a result:
| PRCO | EMPLOYEE | STATE1 | STATEWAGES1 | STATETAX1 | STATE2 | STATEWAGES2 | STATETAX2 |
|------|----------|--------|-------------|-----------|--------|-------------|-----------|
| 1 | 304 | CA | 20162.03 | 804.42 | IN | 20162.03 | 665.90 |

order by using terms in where clause

I have a simple select query -
SELECT ID, NAME
FROM PERSONS
WHERE NAME IN ('BBB', 'AAA', 'ZZZ')
-- ORDER BY ???
I want this result to be ordered by the sequence in which NAMES are provided, that is,
1st row in result set should be the one with NAME = BBB, 2nd is AAA, 3rd it ZZZ.
Is this possible in SQL server ? I would like to know how to do it if there is a simple and short way of doing it, like maybe 5-6 lines of code.
You could create an ordered split function:
CREATE FUNCTION [dbo].[SplitStrings_Ordered]
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
AS
RETURN (SELECT [Index] = ROW_NUMBER() OVER (ORDER BY Number), Item
FROM (SELECT Number, Item = SUBSTRING(#List, Number,
CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number)
FROM (SELECT ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2) AS n(Number)
WHERE Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, LEN(#Delimiter)) = #Delimiter
) AS y);
Then alter your input slightly (a single comma-separated list instead of three individual strings):
SELECT p.ID, p.NAME
FROM dbo.PERSONS AS p
INNER JOIN dbo.SplitStrings_Ordered('BBB,AAA,ZZZ', ',') AS s
ON p.NAME = s.Item
ORDER BY s.[Index];
You could store the names in a temp table with an order. Example:
DECLARE #Names TABLE (
Name VARCHAR(MAX),
SortOrder INT
)
INSERT INTO #Names (Name, SortOrder) VALUES ('BBB', 1)
INSERT INTO #Names (Name, SortOrder) VALUES ('AAA', 2)
INSERT INTO #Names (Name, SortOrder) VALUES ('ZZZ', 3)
SELECT P.ID, P.NAME
FROM PERSONS P
JOIN #Names N ON P.Name = N.Name
ORDER BY N.SortOrder
There is no way to do this using the order in the IN predicate, however, you could create a table of constants giving your constants an order by value:
SELECT p.ID, p.NAME
FROM PERSONS p
INNER JOIN
( VALUES
('BBB', 1),
('AAA', 2),
('ZZZ', 3)
) t (Name, SortOrder)
ON p.Name = t.Name
ORDER BY t.SortOrder;
The other (and in my option less attractive) solution is to use CASE
SELECT ID, NAME
FROM PERSONS
WHERE NAME IN ('BBB', 'AAA', 'ZZZ')
ORDER BY CASE Name
WHEN 'BBB' THEN 1
WHEN 'AAA' THEN 2
WHEN 'ZZZ' THEN 3
END;
SELECT ID, NAME
FROM PERSONS
WHERE NAME IN ('BBB', 'AAA', 'ZZZ')
ORDER BY CASE
WHEN NAME = 'BBB' THEN 1
WHEN NAME = 'AAA' THEN 2
WHEN NAME = 'ZZZ' THEN 3
END ASC
I think this must work:
ORDER BY CASE
WHEN NAME = 'BBB' THEN 0
WHEN NAME = 'AAA' THEN 1
WHEN NAME = 'ZZZ' THEN 2
ELSE 3
END ASC