This would be significantly easier if MySQL supported windowing functions because it's not necessarily the easiest to create a row number for each userid
. I'll show you two ways to do this with hard-coded queries (limited number of results) and then I'll include one version that is using dynamic SQL.
In order to get the final result, you need some sequential number for each gname
within the userid
. This can be done a few different ways.
First, you can use a correlated subquery to count
the number of gname
s per user, you'd then use this sequence to create your new columns via an aggregate function with CASE
expression:
select
userid,
max(case when gnameNum = 1 then gname else '' end) gname1,
max(case when gnameNum = 2 then gname else '' end) gname2,
max(case when gnameNum = 3 then gname else '' end) gname3,
max(case when gnameNum = 4 then gname else '' end) gname4,
max(case when gnameNum = 5 then gname else '' end) gname5,
max(case when gnameNum = 6 then gname else '' end) gname6,
max(case when gnameNum = 7 then gname else '' end) gname7,
max(case when gnameNum = 8 then gname else '' end) gname8
from
(
select userid,
gname,
(select count(*)
from gnames d
where g.userid = d.userid
and g.gname <= d.gname) as gnameNum
from gnames g
) src
group by userid;
See SQL Fiddle with Demo. Inside the subquery you are creating a row number for each gname
, you then use this new value in the column creation. The problem with correlated subqueries is you could suffer from performance issues on larger data sets.
A second method would be to include user variables to create the row number. This code uses 2 variables to compare the previous row to the current row and increases the row number, if the userid
is the same as the previous row. Again, you'd use the row number created to convert the data to new columns:
select
userid,
max(case when rownum = 1 then gname else '' end) gname1,
max(case when rownum = 2 then gname else '' end) gname2,
max(case when rownum = 3 then gname else '' end) gname3,
max(case when rownum = 4 then gname else '' end) gname4,
max(case when rownum = 5 then gname else '' end) gname5,
max(case when rownum = 6 then gname else '' end) gname6,
max(case when rownum = 7 then gname else '' end) gname7,
max(case when rownum = 8 then gname else '' end) gname8
from
(
select
g.userid,
g.gname,
@row:=case when @prev=userid then @row else 0 end + 1 as rownum,
@prev:=userid
from gnames g
cross join
(
select @row:=0, @prev:=null
) r
order by userid, gname
) src
group by userid;
See SQL Fiddle with Demo.
Now, in order to do this dynamically you will need to use a prepared statement. This process will create a sql string that you'll execute to get the final result. For this example, I used the user variable query above:
SET @sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(case when rownum = ',
rownum,
' then gname else '''' end) AS `gname',
rownum, '`'
)
) INTO @sql
from
(
select
g.userid,
g.gname,
@row:=case when @prev=userid then @row else 0 end + 1 as rownum,
@prev:=userid
from gnames g
cross join
(
select @row:=0, @prev:=null
) r
order by userid, gname
) src;
SET @sql = CONCAT('SELECT userid, ', @sql, '
from
(
select
g.userid,
g.gname,
@row:=case when @prev=userid then @row else 0 end + 1 as rownum,
@prev:=userid
from gnames g
cross join
(
select @row:=0, @prev:=null
) r
order by userid, gname
) src
group by userid');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo. All three versions will give a result of:
| USERID | GNAME1 | GNAME2 | GNAME3 | GNAME4 | GNAME5 | GNAME6 | GNAME7 | GNAME8 |
|--------|--------|--------|--------|--------|--------|--------|--------|--------|
| 12 | Aew1 | ASD | ASD23 | ASDer | AVBD | AVBD32 | AVBDe | AVFD |
| 45 | ASD44 | ASD444 | AVBD | AVBD44 | | | | |
| 453 | ASD1 | AVBD22 | | | | | | |
One thing to consider when using the dynamic SQL is MySQL has a set length for the group_concat_max_len
, if you have a lot of columns being created you might run into issues. You'll want to account for that. Here is another question that deals with this MySQL and GROUP_CONCAT() maximum length.