I have a table look like this:
+-------+--------+--------+
| Grp | Party | Member |
+-------+--------+--------+
| FC | Party1 | Tom |
| FC | Party1 | Alice |
| FC | Party2 | John |
| FC | Party3 | Mary |
| GC | Party2 | Anna |
| GC | Party4 | Alex |
| GC | Party5 | Diana |
+-------+--------+--------+
I want to transform the table into list like this:
+-------+--------+
| ID | Text |
+-------+--------+
| 1 | FC |
| 1.1 | Party1 |
| 1.1.1 | Tom |
| 1.1.2 | Alice |
| 1.2 | Party2 |
| 1.2.1 | John |
| 1.3 | Party3 |
| 1.3.1 | Mary |
| 2 | GC |
| 2.1 | Party2 |
| 2.1.1 | Anna |
| 2.2 | Party4 |
| 2.2.1 | Alex |
| 2.3 | Party5 |
| 2.3.1 | Diana |
+-------+--------+
I have tried rollup with row_number, but the result still far away what I want
;with ctx as (
select * from #test
group by rollup(Grp, Party, Member)
)
select row_number() over (partition by grp order by grp, party, member) as g,
row_number() over (partition by grp, party order by grp, party, member) as p,
row_number() over (partition by grp, party, member order by grp, party, member) as m,
grp, party, member
from ctx
where grp is not null
order by grp, party, member
Thanks in advance.
EDIT
Here is the SQL to generate the table, hope this can help
declare #test table (Grp varchar(10), Party varchar(10), Member varchar(20))
insert into #test values ('FC', 'Party1', 'Tom')
insert into #test values ('FC', 'Party1', 'Alice')
insert into #test values ('FC', 'Party2', 'John')
insert into #test values ('FC', 'Party3', 'Mary')
insert into #test values ('GC', 'Party2', 'Anna')
insert into #test values ('GC', 'Party4', 'Alex')
insert into #test values ('GC', 'Party5', 'Diana')
This uses DENSE_RANK to get the correct numbering for the ID. Then CROSS APPLY to unpivot the data and mark which row is for the Grp, Party, or Member. Finally use WHERE to filter only those rows you need:
WITH CteUnpivot AS(
SELECT *
FROM (
SELECT *,
rnGrp = DENSE_RANK() OVER(ORDER BY Grp),
rnParty = DENSE_RANK() OVER(PARTITION BY Grp ORDER BY Party),
rnMember = ROW_NUMBER() OVER(PARTITION BY Grp, Party ORDER BY Member)
FROM test
) t
CROSS APPLY(VALUES
('Grp', Grp),
('Party', Party),
('Member', Member)
) x (col, [Text])
)
SELECT
ID = CASE
WHEN col = 'Grp' THEN CAST(rnGrp AS VARCHAR(3))
WHEN col = 'Party' THEN CAST(rnGrp AS VARCHAR(3)) + '.' + CAST(rnParty AS VARCHAR(3))
WHEN col = 'Member' THEN CAST(rnGrp AS VARCHAR(3)) + '.' + CAST(rnParty AS VARCHAR(3)) + '.' + CAST(rnMember AS VARCHAR(3))
END,
[Text]
FROM CteUnpivot
WHERE
(col = 'Grp' AND rnParty = 1 AND rnMember = 1)
OR (col = 'Party' AND rnMember = 1)
OR (col = 'Member')
ORDER BY rnGrp, rnParty, rnMember;
ONLINE DEMO
If order does not matter for Member, replace rnMember with:
rnMember = ROW_NUMBER() OVER(PARTITION BY Grp, Party ORDER BY (SELECT NULL))
ONLINE DEMO
Here is one way
;WITH cte
AS (SELECT Dense_rank()OVER (ORDER BY grp) AS g,
Dense_rank()OVER (partition BY grp ORDER BY party) AS p,
Row_number()OVER (partition BY grp, party ORDER BY member) AS m,
grp,
party,
member
FROM #test
WHERE grp IS NOT NULL)
SELECT DISTINCT grp,
Cast(g AS VARCHAR(10)) AS [Text]
FROM cte
UNION ALL
SELECT DISTINCT party,
Concat(g, '.', p)
FROM cte
UNION ALL
SELECT member,
Concat(g, '.', p, '.', m)
FROM cte
ORDER BY [Text]
You need to use DENSE_RANK for parents to generate hierarchy numbers properly. If you have duplicates in Member as well then change the ROW_NUMBER to DENSE_RANK inside CTE and add distinct to the final select query
Note : If you are using anything less than SQL SERVER 2012 then use + operator for concatenation instead of CONCAT
Frankly, I would not do this at the database level. Instead I would ensure the output is sorted by {Grp, Party, Member} and then assign "Id" values in a single pass through as you display the data.
However, if you're determined to do this in the database server for whatever reason, you could use the dense_rank() function to get each individual id:
;with cte as (
select dense_rank() over (order by Grp) id0,
dense_rank() over (partition by Grp order by Party) id1,
dense_rank() over (partition by Grp, Party order by Member) id2,
Grp, Party, Member
from Table1
), grps as (select distinct id0, Grp from cte),
parties as (select distinct id0, id1, Party from cte),
members as (select distinct id0, id1, id2, Member from cte),
[list] as (
select cast(id0 as varchar(50)) as id, Grp as [Text] from grps
union all
select cast(id0 as varchar(50)) + '.' + cast(id1 as varchar(50)), Party from parties
union all
select cast(id0 as varchar(50)) + '.' + cast(id1 as varchar(50)) + '.' + cast(id2 as varchar(50)), Member from members
)
select id, [Text]
from [list]
order by id
This option doesn't use DENSE_RANK() but ROW_NUMBER() but is essentially similar to other answers posted.
With grps As (
Select Grp, GrpNo = Row_Number() Over (Order By Grp)
From (Select Distinct Grp From MyTable) As MyTable),
parties As (
Select MyTable.Grp, MyTable.Party, grps.GrpNo, PrtyNo = Row_Number() Over (Partition By MyTable.Grp Order By MyTable.Party)
From (Select Distinct Grp, Party From MyTable) As MyTable
Join grps On MyTable.Grp = grps.Grp),
members As (
Select MyTable.Grp, MyTable.Party, MyTable.Member,
parties.GrpNo, parties.PrtyNo, MbrNo = Row_Number() Over (Partition By MyTable.Grp, MyTable.Party Order By #groups.Member)
From MyTable
Join parties On MyTable.Grp = parties.Grp And MyTable.Party = parties.Party)
Select ID = Convert(char(5), GrpNo),
[Text] = Grp
From grps
Union All
Select ID = Convert(char(1), GrpNo) + '.' + Convert(char(1), PrtyNo),
[Text] = Party
From parties
Union All
Select ID = Convert(char(1), GrpNo) + '.' + Convert(char(1), PrtyNo) + '.' + Convert(char(1), MbrNo),
[Text] = Member
From members;
Related
I have data that repeated sequentially..
A
A
A
B
B
B
A
A
A
I need to group them like this
A
B
A
What is the best approach to do so using sqlite?
Assuming that you have a column that defines the ordering of the rows, say id, you can address this gaps-and-island problem with window functions:
select col, count(*) cnt, min(id) first_id, max(id) last_id
from (
select t.*,
row_number() over(order by id) rn1,
row_number() over(partition by col order by id) rn2
from mytable t
) t
group by col, rn1 - rn2
order by min(id)
I added a few columns to the resultset that give more information about the content of each group.
If you have defined a column that defines the order of the rows, like an id, you can use window function LEAD():
select col
from (
select col, lead(col, 1, '') over (order by id) next_col
from tablename
)
where col <> next_col
See the demo.
Results:
| col |
| --- |
| A |
| B |
| A |
I have a table Contacts that basically looks like following:
Id | Name | ContactId | Contact | Amount
---------------------------------------------
1 | A | 1 | 12323432 | 555
---------------------------------------------
1 | A | 2 | 23432434 | 349
---------------------------------------------
2 | B | 3 | 98867665 | 297
--------------------------------------------
2 | B | 4 | 88867662 | 142
--------------------------------------------
2 | B | 5 | null | 698
--------------------------------------------
Here, ContactId is unique throughout the table. Contact can be NULL & I would like to exclude those.
Now, I want to select top 5 contacts for each Id based on their Amount. I am accomplished that by following query:
WITH cte AS (
SELECT id, Contact, amount, ROW_NUMBER()
over (
PARTITION BY id
order by amount desc
) AS RowNo
FROM contacts
where contact is not null
)
select *from cte where RowNo <= 5
It's working fine upto this point. Now I want to concate these (<=5) record for each group & show them in a single row by concatenating them.
Expected Result :
Id | Name | Contact
-------------------------------
1 | A | 12323432;23432434
-------------------------------
2 | B | 98867665;88867662
I am using following query to achieve this but it still gives all records in separate rows and also including Null values too:
WITH cte AS (
SELECT id, Contact, amount,contactid, ROW_NUMBER()
over (
PARTITION BY id
order by amount desc
) AS RowNo
FROM contacts
where contact is not null
)
select *from id, name,
STUFF ((
SELECT distinct '; ' + isnull(contact,'') FROM cte
WHERE co.id= cte.id and co.contactid= cte.contactid
and RowNo <= 5
FOR XML PATH('')),1, 1, '')as contact
from contacts co inner join cte where cte.id = co.id and co.contactid= cte.contactid
Above query still gives me all top 5 contacts in diff rows & including null too.
Is it a good idea to use CTE and STUFF togather? Please suggest if there is any better approach than this.
I got the problem with my final query:
I don't need original Contact table in my final Select, since I already have everything I needed in CTE. Also, Inside STUFF(), I'm using contactid to join which is what actually I'm trying to concat here. Since I'm using that condition for join, I am getting records in diff rows. I've removed these 2 condition and it worked.
WITH cte AS (
SELECT id, Contact, amount,contactid, ROW_NUMBER()
over (
PARTITION BY id
order by amount desc
) AS RowNo
FROM contacts
where contact is not null
)
select *from id, name,
STUFF ((
SELECT distinct '; ' + isnull(contact,'') FROM cte
WHERE co.id= cte.id
and RowNo <= 5
FOR XML PATH('')),1, 1, '')as contact
from cte where rowno <= 5
You can use conditional aggregation:
id, name, contact,
select id, name,
concat(max(case when seqnum = 1 then contact + ';' end),
max(case when seqnum = 2 then contact + ';' end),
max(case when seqnum = 3 then contact + ';' end),
max(case when seqnum = 4 then contact + ';' end),
max(case when seqnum = 5 then contact + ';' end)
) as contacts
from (select c.*
row_number() over (partition by id order by amount desc) as seqnum
from contacts c
where contact is not null
) c
group by id, name;
If you are running SQL Server 2017 or higher, you can use string_agg(): as most other aggregate functions, it ignores null values by design.
select id, name, string_agg(contact, ',') within group (order by rn) all_contacts
from (
select id, name, contact
row_number() over (partition by id order by amount desc) as rn
from contacts
where contact is not null
) t
where rn <= 5
group by id, name
Note that you don't strictly need a CTE here; you can return the columns you need from the subquery, and use them directly in the outer query.
In earlier versions, one approach using stuff() and for xml path is:
with cte as (
select id, name, contact,
row_number() over (partition by id order by amount desc) as rn
from contacts
where contact is not null
)
select id, name,
stuff(
(
select ', ' + c1.concat
from cte c1
where c1.id = c.id and c1.rn <= 5
order by c1.rn
for xml path (''), type
).value('.', 'varchar(max)'), 1, 2, ''
) all_contacts
from cte
group by id, name
I agree with #GMB. STRING_AGG() is what you need ...
WITH
contacts(Id,nm,ContactId,Contact,Amount) AS (
SELECT 1,'A',1,12323432,555
UNION ALL SELECT 1,'A',2,23432434,349
UNION ALL SELECT 2,'B',3,98867665,297
UNION ALL SELECT 2,'B',4,88867662,142
UNION ALL SELECT 2,'B',5,NULL ,698
)
,
with_filter_val AS (
SELECT
*
, ROW_NUMBER() OVER(PARTITION BY id ORDER BY amount DESC) AS rn
FROM contacts
)
SELECT
id
, nm
, STRING_AGG(CAST(contact AS CHAR(8)),',') AS contact_list
FROM with_filter_val
WHERE rn <=5
GROUP BY
id
, nm
-- out id | nm | contact_list
-- out ----+----+-------------------
-- out 1 | A | 12323432,23432434
-- out 2 | B | 98867665,88867662
I have to show multiple incomes, type of income and employer name values for a single individual in a single row. So, if 'A' has three different incomes from three different sources,
id | Name | Employer | IncomeType | Amount
123 | XYZ | ABC.Inc | EarningsformJob | $200.00
123 | XYZ | Self | Self Employment | $300.00
123 | XYZ. | ChildSupport| Support | $500.00
I need to show them as
id | Name | Employer1 | Incometype1| Amount1 | Employer2 | incometype2 | Amount2| Employer3 | Incometype3| Amount3.....
123 |XYZ | ABC.Inc |EarningsformJob | $200.00|Self | Self Employment | $300.00|ChildSupport| Support | $500.00.....
I need both 'fixed number of columns' (where we know how many times employer, incometype and amount colums are going to repeat)logic and 'dynamic display of columns' ( unknown number of times these columns are going to repeat)
Thanks.
Since you are using SQL Server there are several ways that you can transpose the rows of data into columns.
Aggregate Function / CASE: You can use an aggregate function with a CASE expression along with row_number(). This version would require that you have a known number of values to become columns:
select id,
name,
max(case when rn = 1 then employer end) employer1,
max(case when rn = 1 then IncomeType end) IncomeType1,
max(case when rn = 1 then Amount end) Amount1,
max(case when rn = 2 then employer end) employer2,
max(case when rn = 2 then IncomeType end) IncomeType2,
max(case when rn = 2 then Amount end) Amount2,
max(case when rn = 3 then employer end) employer3,
max(case when rn = 3 then IncomeType end) IncomeType3,
max(case when rn = 3 then Amount end) Amount3
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) src
group by id, name;
See SQL Fiddle with Demo.
PIVOT/UNPIVOT: You could use the UNPIVOT and PIVOT functions to get the result. The UNPIVOT converts your multiple columns of Employer, IncomeType and Amount into multiples rows before applying the pivot. You did not specific what version of SQL Server, assuming you have a known number of values then you could use the following in SQL Server 2005+ which uses CROSS APPLY with UNION ALL to unpivot:
select id, name,
employer1, incometype1, amount1,
employer2, incometype2, amount2,
employer3, incometype3, amount3
from
(
select id, name, col+cast(rn as varchar(10)) col, value
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) t
cross apply
(
select 'employer', employer union all
select 'incometype', incometype union all
select 'amount', cast(amount as varchar(50))
) c (col, value)
) src
pivot
(
max(value)
for col in (employer1, incometype1, amount1,
employer2, incometype2, amount2,
employer3, incometype3, amount3)
) piv;
See SQL Fiddle with Demo.
Dynamic Version: Lastly, if you have an unknown number of values then you will need to use dynamic SQL to generate the result.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+cast(rn as varchar(10)))
from
(
select row_number() over(partition by id order by employer) rn
from yourtable
) d
cross apply
(
select 'employer', 1 union all
select 'incometype', 2 union all
select 'amount', 3
) c (col, so)
group by col, rn, so
order by rn, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, name,' + #cols + '
from
(
select id, name, col+cast(rn as varchar(10)) col, value
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) t
cross apply
(
select ''employer'', employer union all
select ''incometype'', incometype union all
select ''amount'', cast(amount as varchar(50))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. All versions give a result:
| ID | NAME | EMPLOYER1 | INCOMETYPE1 | AMOUNT1 | EMPLOYER2 | INCOMETYPE2 | AMOUNT2 | EMPLOYER3 | INCOMETYPE3 | AMOUNT3 |
-------------------------------------------------------------------------------------------------------------------------------------
| 123 | XYZ | ABC.Inc | EarningsformJob | 200 | ChildSupport | Support | 500 | Self | Self Employment | 300 |
I have a query that returns some data that looks like this:
Description
----------
Thing1
Thing2
Thing3
Thing4
Thing5
Thing6
Thing7
I would like to make the data look like this:
Desc1 Desc2 Desc3
----- ----- -----
Thing1 Thing2 Thing3
Thing4 Thing5 Thing6
Thing7
Could someone provide an example on how to do this?
Thanks!
You stated the comments that the order of the data in the Desc columns does not matter.
If that is the case, then you can use the following which implement NTILE and row_number():
;with cte as
(
select description,
'desc'+cast(ntile(3) over(order by Description) as varchar(10)) col
from yt
)
select desc1, desc2, desc3
from
(
select description, col,
row_number() over(partition by col order by col) rn
from cte
) d
pivot
(
max(description)
for col in (desc1, desc2, desc3)
) piv;
See SQL Fiddle with Demo.
The NTILE function distributes the rows into separate groups. Once that is done, then apply a row_number() to give a unique number to each row while grouping.
This gives a result:
| DESC1 | DESC2 | DESC3 |
----------------------------
| Thing1 | Thing4 | Thing6 |
| Thing2 | Thing5 | Thing7 |
| Thing3 | (null) | (null) |
Another approach of doing it, if the order does matter, and skipping PIVOT function:
WITH CTE AS
(
SELECT
(ROW_NUMBER() OVER (ORDER BY [Description])+2) /3 AS RowID,
(ROW_NUMBER() OVER (ORDER BY [Description])+2) % 3 +1 AS ColID,
[Description]
FROM Table1
)
SELECT
MIN(CASE WHEN ColID = 1 THEN [Description] END) DESC1
,MIN(CASE WHEN ColID = 2 THEN [Description] END) DESC2
,MIN(CASE WHEN ColID = 3 THEN [Description] END) DESC3
FROM CTE
GROUP BY RowID
SQLFiddle DEMO
I used the base data from the sqlfiddle of #NenadZivkovic to provide a different solution
http://www.sqlfiddle.com/#!6/b676a/15
WITH Numbered AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY Description) as rn,
(ROW_NUMBER() OVER (ORDER BY Description) - 1) % 3 as colid,
(ROW_NUMBER() OVER (ORDER BY Description) - 1) / 3 as line,
Description
FROM Table1
)
SELECT desc1.description as DESC1, desc2.description as DESC2, desc3.description as DESC3
FROM (SELECT line, description FROM Numbered WHERE colid=0) as desc1
LEFT JOIN
(SELECT line, description FROM Numbered WHERE colid=1) as desc2
ON (desc1.line = desc2.line)
LEFT JOIN
(SELECT line, description FROM Numbered WHERE colid=2) as desc3
ON (desc2.line = desc3.line)
I want to get rid of duplicates in one column (device_name) but keep related data from another column (app_id). Each device can have couple of applications (1 -> x, usually between 1-5) so I want to put those app identifiers into a new columns which I want to call [APP1],[APP2],[APP3] and so on. The best option would be kind of dynamic Pivot, but any static solution will be welcome as well.
Thanks for help in advance.
PS
I came up with below code what so ever, but in only concatenate APP id's separated by comas into one column.
USE tempdb;
SELECT DEVICE_NAME,
NoOfApps,
STUFF(( SELECT ', ' + APP_ID
FROM dbo.Aperture_full_test apps
WHERE apps.DEVICE_NAME = Aperture_full_test.DEVICE_NAME
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)'), 1, 2, '') AS Appid
FROM ( SELECT DEVICE_NAME, COUNT(DEVICE_NAME) AS NoOfApps
FROM dbo.Aperture_full_test
GROUP BY DEVICE_NAME
) Aperture_full_test
ORDER BY NoOfApps DESC
Data sample:
USE tempdb;
GO
IF OBJECT_ID('dbo.Aperture_full_test') IS NOT NULL
DROP TABLE dbo.Aperture_full_test;
GO
CREATE TABLE dbo.Aperture_full_test
(
DEVICE_NAME varchar(30) NOT NULL,
APP_ID varchar(10) NOT NULL
);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('LDNSQLF700', 157848);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('LDNSQLF700', 155439);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('LDNSQLF700', 635533);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('NYSQL502', 189164);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('NYSQL502', 188641);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('AUSSQL140', 537990);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('AUSSQL140', 1349605);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('JAP543X2', 5646789);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('EU456CLX', 6545789);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('EUCTX654', 5637965);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('EUCTX654', 6464367) ;
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('EUCTX654', 1323123) ;
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('EUCTX654', 1004326) ;
GO
Since you are using SQL Server you can implement the PIVOT function.
If you have a known number of values then you can hard code the query using the following:
select device_name, App1, App2, App3, App4, App5
from
(
select device_name, app_id,
'App'+
cast(row_number() over(partition by device_name
order by device_name) as varchar(10)) col
from Aperture_full_test
) d
pivot
(
max(app_id)
for col in (App1, App2, App3, App4, App5)
) piv;
See SQL Fiddle with Demo.
But if you are going to have an unknown number of app_ids for each device, then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ', ' + QUOTENAME('App'+cast(rn as varchar(10)))
from
(
select row_number() over(partition by device_name
order by device_name) rn
from Aperture_full_test
) d
group by rn
order by rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT device_name, ' + #cols + ' from
(
select device_name, app_id,
''App''+
cast(row_number() over(partition by device_name
order by device_name) as varchar(10)) col
from Aperture_full_test
) x
pivot
(
max(app_id)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo. Both give the result:
| DEVICE_NAME | APP1 | APP2 | APP3 | APP4 |
-------------------------------------------------------
| AUSSQL140 | 537990 | 1349605 | (null) | (null) |
| EU456CLX | 6545789 | (null) | (null) | (null) |
| EUCTX654 | 5637965 | 6464367 | 1323123 | 1004326 |
| JAP543X2 | 5646789 | (null) | (null) | (null) |
| LDNSQLF700 | 157848 | 155439 | 635533 | (null) |
| NYSQL502 | 189164 | 188641 | (null) | (null) |
Edit, If you want to count the total number of devices for each server, then you can use count() over(). The hard-coded version will be:
select device_name, TotalDevices, App1, App2, App3, App4, App5
from
(
select device_name, app_id,
'App'+
cast(row_number() over(partition by device_name
order by device_name) as varchar(10)) col,
count(app_id) over(partition by device_name) TotalDevices -- add this line
from Aperture_full_test
) d
pivot
(
max(app_id)
for col in (App1, App2, App3, App4, App5)
) piv;
See SQL Fiddle with Demo
This is a pivot query, which can be done in the group by. What you need is a sequence number for the Apps.
select aft.deviceName, COUNT(*) as NumApps,
MAX(case when seqnum = 1 then App_id end) as App1,
MAX(case when seqnum = 2 then App_id end) as App2,
MAX(case when seqnum = 3 then App_id end) as App3,
MAX(case when seqnum = 4 then App_id end) as App4,
MAX(case when seqnum = 5 then App_id end) as App5
from (select aft.*,
ROW_NUMBER() over (partition by device_name order by (select NULL)) as seqnum
from Aperture_full_test aft
) aft
group by aft.deviceName
order by NumApps desc
This version does not have the Apps in a particular order becasue the xml code did not have them in a particular order.