【问题标题】:Swapping rows to columns将行交换到列
【发布时间】:2014-10-16 18:27:23
【问题描述】:

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

【问题讨论】:

  • 在链接的答案中,您将使用您的列 gname 代替 order 和您的 Userid 代替 ID
  • “数据”列怎么样??
  • 抱歉 - 看起来您会使用 gname 代替 dataUserid 代替 ID,并忽略 order。我将尝试在 sqlfiddle 上设置一个类似的示例...
  • tnx,那你能取消我的问题吗??
  • 我只是取消标记它,因为我没有成功转换其他答案。我还用 [pivot] 标记它,这会将它放回首页。

标签: mysql sql pivot


【解决方案1】:

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

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

首先,您可以对count 使用每个用户的gnames 数量的相关子查询,然后您可以使用此序列通过带有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 的问题。

【讨论】:

  • 非常感谢,我已经尝试了最后一段代码作为我在小提琴中的真实数据样本,它就像一个魅力,但是当我在我的工作台上运行它时,我有一个“prepare stmt from @sql”行中的错误 Error:1064,你知道为什么吗??
  • @MT467 不幸的是,我对此一点也不熟悉。我无法帮你解决这个问题。
  • 顺便说一句,有一行被group_concat()剪掉了,对结果有影响吗?
  • @MT467 这就是我在答案中的最后一句话所涵盖的内容 - 您需要查看 group_concat_max_len 长度并进行相应调整。
  • 是的!正是这点,增加它并且警告消失了:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-19
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
相关资源
最近更新 更多