将行交换为列

时间:2014-10-16 18:27:24

标签: mysql sql pivot

我已经阅读了有关数据透视表和将行交换到列的先前帖子,但我无法找到问题的正确答案。我在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')
;

1 个答案:

答案 0 :(得分:3)

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

为了获得最终结果,您需要gname中每个userid的一些序号。这可以通过几种不同的方式完成。

首先,您可以使用相关子查询count每个用户gname的数量,然后使用此序列通过{{1}的聚合函数创建新列表达式:

CASE

SQL Fiddle with Demo。在子查询中,您为每个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; 创建一个行号,然后在列创建中使用此新值。相关子查询的问题是您可能会遇到较大数据集的性能问题。

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

userid

请参阅SQL Fiddle with Demo

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

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。所有三个版本都会得到以下结果:

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时要考虑的一件事是MySQL有一个| 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 | | | | | | | 的设置长度,如果你创建了很多列,你可能会遇到问题。你想要考虑到这一点。这是另一个处理此MySQL and GROUP_CONCAT() maximum length的问题。