2014-10-16 78 views
2

我读过以前的文章关于数据透视表和交换行到列,但我找不到正确的答案我的问题。我有以下表中的MySQL:交换行到列

+--------+--------+ 
| Userid | gname | 
+--------+--------+ 
|  12 | AVBD | 
|  12 | ASD | 
|  12 | AVFD | 
|  12 | Aew1 | 
|  12 | AVBD32 | 
|  12 | ASD23 | 
|  12 | AVBDe | 
|  12 | ASDer | 
|  45 | AVBD | 
|  45 | ASD444 | 
|  45 | AVBD44 | 
|  45 | ASD44 | 
| 453 | AVBD22 | 
| 453 | ASD1 | 
+--------+--------+ 

我想产生一个有序的数据透视表:

+--------+--------+--------+--------+--------+--------+--------+--------+--------+ 
| Userid | gname1 | gname2 | gname3 | gname4 | gname5 | gname6 | gname7 | gname8 | 
+--------+--------+--------+--------+--------+--------+--------+--------+--------+ 
|  12 | AVBD | ASD | AVFD | Aew1 | AVBD32 | ASD23 | AVBDe | ASDer | 
|  45 | AVBD | ASD444 | AVBD44 | ASD44 |  |  |  |  | 
| 453 | AVBD22 | ASD1 |  |  |  |  |  |  | 
+--------+--------+--------+--------+--------+--------+--------+--------+--------+ 

gname名称是动态的,没有限制。

这是一个带SQLFiddle http://sqlfiddle.com/#!2/65fec

CREATE TABLE gnames 
    (`Userid` int, `gname` varchar(6)) 
; 

INSERT INTO gnames 
    (`Userid`, `gname`) 
VALUES 
    (12, 'AVBD'), 
    (12, 'ASD'), 
    (12, 'AVFD'), 
    (12, 'Aew1'), 
    (12, 'AVBD32'), 
    (12, 'ASD23'), 
    (12, 'AVBDe'), 
    (12, 'ASDer'), 
    (45, 'AVBD'), 
    (45, 'ASD444'), 
    (45, 'AVBD44'), 
    (45, 'ASD44'), 
    (453, 'AVBD22'), 
    (453, 'ASD1') 
; 
+0

在链接的答案中,您可以使用列'gname'代替'order',并且'Userid'代替'ID'。 – 2014-10-16 18:32:02

+0

'data'列如何? – MT467 2014-10-16 18:50:51

+0

对不起 - 它看起来像使用'gname'代替'data','Userid'代替'ID',并且只是省略'order'。我会尽力建立一个类似的例子在sqlfiddle ... – 2014-10-16 18:53:38

回答

3

这将是显著更容易,如果在MySQL支持的窗口函数,因为它不一定是最容易为每个userid行号沿着数据设置。我将向您展示两种使用硬编码查询(结果数量有限)的方法,然后我将包含一个使用动态SQL的版本。

为了获得最终结果,您需要在userid内的每个gname有一些连续编号。这可以通过几种不同的方式来完成。

首先,你可以使用相关子查询到count每个用户gname S上的号码,你会再使用这个序列通过聚合函数CASE表达式来创建新的列:

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; 

SQL Fiddle with Demo。在子查询中,您将为每个gname创建一个行号,然后在列创建中使用此新值。相关子查询的问题是您可能会遇到大数据集上的性能问题。

第二种方法是包含用户变量以创建行号。如果userid与前一行相同,则此代码使用2个变量将前一行与当前行进行比较,并增加行号。再次,您将使用创建的行号将数据转换为新列:

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; 

请参阅SQL Fiddle with Demo

现在,为了动态地执行此操作,您需要使用prepared statement。这个过程将创建一个sql字符串,您将执行以获得最终结果。对于此示例,我使用上面的用户变量查询:

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; 

请参阅SQL Fiddle with Demo。所有这三个版本会给出一个结果:

| 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 |  |  |  |  |  |  | 

有一点要考虑使用动态SQL时,MySQL有对group_concat_max_len集的长度,如果你创建了很多列,你可能会遇到的问题。你会想说明这一点。这是另一个处理这个MySQL and GROUP_CONCAT() maximum length的问题。

+0

感谢这么多,我已经尝试过的代码的最后一节,以在拨弄我的真实数据的样本,它就像一个魅力,但是当我运行它我工作台我有一个错误,“从@sql准备stmt”行​​错误:1064,你有什么想法为什么? – MT467 2014-10-17 17:15:02

+0

@ MT467不幸的是,我并不完全了解这一点。我无法帮你解决这个问题。 – Taryn 2014-10-17 17:20:58

+0

BTW,有是由GROUP_CONCAT()切一排,不会影响结果呢? – MT467 2014-10-17 17:33:41