MySQL - 行到列

Bob*_*ers 169 mysql sql etl pivot-table crosstab

我试图搜索帖子,但我只找到了SQL Server/Access的解决方案.我需要一个MySQL(5.X)的解决方案.

我有一个表(称为历史)有3列:hostid,itemname,itemvalue.
如果我执行select(select * from history),它将返回

   +--------+----------+-----------+
   | hostid | itemname | itemvalue |
   +--------+----------+-----------+
   |   1    |    A     |    10     |
   +--------+----------+-----------+
   |   1    |    B     |     3     |
   +--------+----------+-----------+
   |   2    |    A     |     9     |
   +--------+----------+-----------+
   |   2    |    c     |    40     |
   +--------+----------+-----------+
Run Code Online (Sandbox Code Playgroud)

如何查询数据库以返回类似的内容

   +--------+------+-----+-----+
   | hostid |   A  |  B  |  C  |
   +--------+------+-----+-----+
   |   1    |  10  |  3  |  0  |
   +--------+------+-----+-----+
   |   2    |   9  |  0  |  40 |
   +--------+------+-----+-----+
Run Code Online (Sandbox Code Playgroud)

Mat*_*ick 249

我将为解决此问题的步骤添加一些更长更详细的解释.如果太长,我道歉.


我将从您给出的基础开始,并使用它来定义一些我将用于本文其余部分的术语.这将是基表:

select * from history;

+--------+----------+-----------+
| hostid | itemname | itemvalue |
+--------+----------+-----------+
|      1 | A        |        10 |
|      1 | B        |         3 |
|      2 | A        |         9 |
|      2 | C        |        40 |
+--------+----------+-----------+
Run Code Online (Sandbox Code Playgroud)

这将是我们的目标,漂亮的数据透视表:

select * from history_itemvalue_pivot;

+--------+------+------+------+
| hostid | A    | B    | C    |
+--------+------+------+------+
|      1 |   10 |    3 |    0 |
|      2 |    9 |    0 |   40 |
+--------+------+------+------+
Run Code Online (Sandbox Code Playgroud)

在值history.hostid列将成为Y值数据透视表.在值history.itemname列将成为x值(原因很明显).


当我必须解决创建数据透视表的问题时,我使用三步过程(可选的第四步)来解决它:

  1. 选择感兴趣的列,即y值x值
  2. 使用额外的列扩展基表 - 每个x值一个
  3. 分组并聚合扩展表 - 每个y值一个组
  4. (可选)美化聚合表

让我们将这些步骤应用到您的问题中,看看我们得到了什么:

第1步:选择感兴趣的列.在期望的结果中,hostid提供y值itemname提供x值.

第2步:使用额外的列扩展基表.我们通常每x值需要一列.回想一下,我们的x值列是itemname:

create view history_extended as (
  select
    history.*,
    case when itemname = "A" then itemvalue end as A,
    case when itemname = "B" then itemvalue end as B,
    case when itemname = "C" then itemvalue end as C
  from history
);

select * from history_extended;

+--------+----------+-----------+------+------+------+
| hostid | itemname | itemvalue | A    | B    | C    |
+--------+----------+-----------+------+------+------+
|      1 | A        |        10 |   10 | NULL | NULL |
|      1 | B        |         3 | NULL |    3 | NULL |
|      2 | A        |         9 |    9 | NULL | NULL |
|      2 | C        |        40 | NULL | NULL |   40 |
+--------+----------+-----------+------+------+------+
Run Code Online (Sandbox Code Playgroud)

请注意,我们没有更改行数 - 我们只添加了额外的列.另请注意NULLs 的模式- itemname = "A"对于新列具有非空值的行A,对于其他新列具有空值.

第3步:对扩展表进行分组和聚合.我们需要group by hostid,因为它提供了y值:

create view history_itemvalue_pivot as (
  select
    hostid,
    sum(A) as A,
    sum(B) as B,
    sum(C) as C
  from history_extended
  group by hostid
);

select * from history_itemvalue_pivot;

+--------+------+------+------+
| hostid | A    | B    | C    |
+--------+------+------+------+
|      1 |   10 |    3 | NULL |
|      2 |    9 | NULL |   40 |
+--------+------+------+------+
Run Code Online (Sandbox Code Playgroud)

(请注意,我们现在每y值有一行.) 好的,我们差不多了!我们只需要摆脱那些丑陋NULL的东西.

第四步:美化.我们只是用零替换任何空值,因此结果集更好看:

create view history_itemvalue_pivot_pretty as (
  select 
    hostid, 
    coalesce(A, 0) as A, 
    coalesce(B, 0) as B, 
    coalesce(C, 0) as C 
  from history_itemvalue_pivot 
);

select * from history_itemvalue_pivot_pretty;

+--------+------+------+------+
| hostid | A    | B    | C    |
+--------+------+------+------+
|      1 |   10 |    3 |    0 |
|      2 |    9 |    0 |   40 |
+--------+------+------+------+
Run Code Online (Sandbox Code Playgroud)

我们已经完成了 - 我们使用MySQL构建了一个漂亮,漂亮的数据透视表.


应用此过程时的注意事项:

  • 在额外的列中使用什么值.我itemvalue在这个例子中使用过
  • 在额外的列中使用什么"中性"值.我用过NULL,但它也可能是,0或者"",取决于你的具体情况
  • 分组时要使用的聚合函数.我用sum,但countmax也经常使用(max建设一排时,经常使用的是已经在许多行传播"对象")
  • 使用多个列作为y值.此解决方案不仅限于使用单个列作为y值 - 只需将额外的列插入到group by子句中(并且不要忘记select它们)

已知限制:

  • 此解决方案不允许数据透视表中的n列 - 扩展基表时需要手动添加每个数据透视表列.因此对于5或10个x值,这个解决方案很不错.100,不太好.存在一些生成查询的存储过程的解决方案,但它们很丑陋且难以正确.当数据透视表需要有很多列时,我目前还不知道解决这个问题的好方法.

  • +1这是迄今为止我所看到的MySQL中数据透视表/交叉表的最佳/最明确的解释 (23认同)
  • 很好的解释,谢谢.步骤4可以通过使用IFNULL(sum(A),0)AS A合并到步骤3,给你相同的结果,但不需要创建另一个表 (6认同)
  • @WhiteBig 请看一下日期——这个 StackOverflow 答案是在那篇博文之前 1.5 年写的。也许你应该要求博客相信我。 (2认同)
  • 很好的答案!如果列类型是字符串,则必须使用 MAX() 而不是 SUM() (2认同)

sha*_*nuo 43

SELECT 
    hostid, 
    sum( if( itemname = 'A', itemvalue, 0 ) ) AS A,  
    sum( if( itemname = 'B', itemvalue, 0 ) ) AS B, 
    sum( if( itemname = 'C', itemvalue, 0 ) ) AS C 
FROM 
    bob 
GROUP BY 
    hostid;
Run Code Online (Sandbox Code Playgroud)

  • 如果 itemname 是动态的怎么办? (2认同)

Mih*_*hai 26

另一个选项,如果你有很多需要转动的项目,特别有用的是让mysql为你构建查询:

SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'ifnull(SUM(case when itemname = ''',
      itemname,
      ''' then itemvalue end),0) AS `',
      itemname, '`'
    )
  ) INTO @sql
FROM
  history;
SET @sql = CONCAT('SELECT hostid, ', @sql, ' 
                  FROM history 
                   GROUP BY hostid');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Run Code Online (Sandbox Code Playgroud)

FIDDLE 添加了一些额外的值以使其正常工作

GROUP_CONCAT 默认值为1000,因此如果您有一个非常大的查询,请在运行之前更改此参数

SET SESSION group_concat_max_len = 1000000;
Run Code Online (Sandbox Code Playgroud)

测试:

DROP TABLE IF EXISTS history;
CREATE TABLE history
(hostid INT,
itemname VARCHAR(5),
itemvalue INT);

INSERT INTO history VALUES(1,'A',10),(1,'B',3),(2,'A',9),
(2,'C',40),(2,'D',5),
(3,'A',14),(3,'B',67),(3,'D',8);

  hostid    A     B     C      D
    1     10      3     0      0
    2     9       0    40      5
    3     14     67     0      8
Run Code Online (Sandbox Code Playgroud)


小智 22

利用Matt Fenwick帮助我解决问题的想法(非常感谢),让我们将它简化为只有一个查询:

select
    history.*,
    coalesce(sum(case when itemname = "A" then itemvalue end), 0) as A,
    coalesce(sum(case when itemname = "B" then itemvalue end), 0) as B,
    coalesce(sum(case when itemname = "C" then itemvalue end), 0) as C
from history
group by hostid
Run Code Online (Sandbox Code Playgroud)


hau*_*ing 12

我编辑Agung Sagita的子查询答案加入.我不确定这两种方式有多大区别,只是为了另一种参考.

SELECT  hostid, T2.VALUE AS A, T3.VALUE AS B, T4.VALUE AS C
FROM TableTest AS T1
LEFT JOIN TableTest T2 ON T2.hostid=T1.hostid AND T2.ITEMNAME='A'
LEFT JOIN TableTest T3 ON T3.hostid=T1.hostid AND T3.ITEMNAME='B'
LEFT JOIN TableTest T4 ON T4.hostid=T1.hostid AND T4.ITEMNAME='C'
Run Code Online (Sandbox Code Playgroud)


小智 9

使用子查询

SELECT  hostid, 
    (SELECT VALUE FROM TableTest WHERE ITEMNAME='A' AND hostid = t1.hostid) AS A,
    (SELECT VALUE FROM TableTest WHERE ITEMNAME='B' AND hostid = t1.hostid) AS B,
    (SELECT VALUE FROM TableTest WHERE ITEMNAME='C' AND hostid = t1.hostid) AS C
FROM TableTest AS T1
GROUP BY hostid
Run Code Online (Sandbox Code Playgroud)

但如果子查询产生多行,则会出现问题,在子查询中使用更多聚合函数


ako*_*ako 7

如果您可以使用MariaDB,那么有一个非常非常简单的解决方案。

MariaDB-10.02开始,添加了一个名为CONNECT的新存储引擎,它可以帮助我们将另一个查询或表的结果转换为数据透视表,就像您想要的一样:您可以查看文档

首先安装连接存储引擎

现在我们表的数据透视表是itemname,每个项目的数据都位于itemvalue列中,因此我们可以使用以下查询获得结果数据透视表:

create table pivot_table
engine=connect table_type=pivot tabname=history
option_list='PivotCol=itemname,FncCol=itemvalue';
Run Code Online (Sandbox Code Playgroud)

现在我们可以从以下内容中选择我们想要的内容pivot_table

select * from pivot_table
Run Code Online (Sandbox Code Playgroud)

更多详情请点击此处


小智 5

我的解决方案:

select h.hostid, sum(ifnull(h.A,0)) as A, sum(ifnull(h.B,0)) as B, sum(ifnull(h.C,0)) as  C from (
select
hostid,
case when itemName = 'A' then itemvalue end as A,
case when itemName = 'B' then itemvalue end as B,
case when itemName = 'C' then itemvalue end as C
  from history 
) h group by hostid
Run Code Online (Sandbox Code Playgroud)

它在提交的案例中产生了预期的结果。