MySQL - 行到列

时间:2009-08-06 20:22:50

标签: mysql sql pivot-table crosstab

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

我有一个包含3列的表(称为历史记录):hostid,itemname,itemvalue 如果我选择(select * from history),它将返回

   +--------+----------+-----------+
   | hostid | itemname | itemvalue |
   +--------+----------+-----------+
   |   1    |    A     |    10     |
   +--------+----------+-----------+
   |   1    |    B     |     3     |
   +--------+----------+-----------+
   |   2    |    A     |     9     |
   +--------+----------+-----------+
   |   2    |    c     |    40     |
   +--------+----------+-----------+

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

的内容
   +--------+------+-----+-----+
   | hostid |   A  |  B  |  C  |
   +--------+------+-----+-----+
   |   1    |  10  |  3  |  0  |
   +--------+------+-----+-----+
   |   2    |   9  |  0  |  40 |
   +--------+------+-----+-----+

12 个答案:

答案 0 :(得分:227)

我将为解决此问题的步骤添加一些更长更详细的解释。如果时间过长我会道歉。


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

select * from history;

+--------+----------+-----------+
| hostid | itemname | itemvalue |
+--------+----------+-----------+
|      1 | A        |        10 |
|      1 | B        |         3 |
|      2 | A        |         9 |
|      2 | C        |        40 |
+--------+----------+-----------+

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

select * from history_itemvalue_pivot;

+--------+------+------+------+
| hostid | A    | B    | C    |
+--------+------+------+------+
|      1 |   10 |    3 |    0 |
|      2 |    9 |    0 |   40 |
+--------+------+------+------+

history.hostid列中的值将在数据透视表中变为 y值history.itemname列中的值将变为 x值(原因很明显)。


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

  1. 选择感兴趣的列,即 y值 x值
  2. 使用额外列扩展基表 - 每个 x-value
  3. 一列
  4. 对扩展表进行分组和汇总 - 每个 y值
  5. 一个组
  6. (可选)美化聚合表
  7. 让我们将这些步骤应用到您的问题中,看看我们得到了什么:

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

    请注意,我们没有更改行数 - 我们只添加了额外的列。另请注意NULL s的模式 - 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 |
    +--------+------+------+------+
    

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

    第4步:美化。我们只是用零替换任何空值,以便更好地查看结果集:

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

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


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

    • 在额外列中使用什么值。我在此示例中使用了itemvalue
    • 在额外列中使用什么“中性”值。我使用NULL,但也可能是0"",具体取决于您的具体情况
    • 分组时使用的聚合函数。我使用了sum,但也经常使用countmaxmax经常在构建遍布多行的一行“对象”时使用<) / LI>
    • 使用多个列作为y值。此解决方案不仅限于使用单个列作为y值 - 只需将额外的列插入group by子句(并且不要忘记select它们)

    已知限制:

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

答案 1 :(得分:40)

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;

答案 2 :(得分:24)

另一个选项,如果您需要转动很多项目尤其有用,那就是让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;

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

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

SET SESSION group_concat_max_len = 1000000;

测试:

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

答案 3 :(得分:20)

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

答案 4 :(得分:11)

我从子查询编辑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'

答案 5 :(得分:7)

使用子查询

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

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

答案 6 :(得分:3)

我将其转换为Group By hostId,然后它将只显示第一行的值,
像:

A   B  C
1  10
2      3

答案 7 :(得分:2)

我的解决方案:

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

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

答案 8 :(得分:2)

我找到了一种方法,可以使用简单的查询将报表转换为几乎动态的报表。您可以查看并测试它online here

列的查询数量是固定的,但值是动态的并且基于行的值。你可以构建它所以,我使用一个查询来构建表头,另一个查询值:

SELECT distinct concat('<th>',itemname,'</th>') as column_name_table_header FROM history order by 1;

SELECT
     hostid
    ,(case when itemname = (select distinct itemname from history a order by 1 limit 0,1) then itemvalue else '' end) as col1
    ,(case when itemname = (select distinct itemname from history a order by 1 limit 1,1) then itemvalue else '' end) as col2
    ,(case when itemname = (select distinct itemname from history a order by 1 limit 2,1) then itemvalue else '' end) as col3
    ,(case when itemname = (select distinct itemname from history a order by 1 limit 3,1) then itemvalue else '' end) as col4
FROM history order by 1;

你也可以总结一下:

SELECT
     hostid
    ,sum(case when itemname = (select distinct itemname from history a order by 1 limit 0,1) then itemvalue end) as A
    ,sum(case when itemname = (select distinct itemname from history a order by 1 limit 1,1) then itemvalue end) as B
    ,sum(case when itemname = (select distinct itemname from history a order by 1 limit 2,1) then itemvalue end) as C
FROM history group by hostid order by 1;
+--------+------+------+------+
| hostid | A    | B    | C    |
+--------+------+------+------+
|      1 |   10 |    3 | NULL |
|      2 |    9 | NULL |   40 |
+--------+------+------+------+

RexTester的结果:

Results of RexTester

http://rextester.com/ZSWKS28923

对于一个真实的使用示例,本报告下面列出了带有视觉时间表的船/公共汽车出发时间。您将看到在最后一个col上未使用的另一列而不会混淆可视化: sistema venda de passagens online e consumidor final e controle de frota - xsl tecnologia - xsl.com.br **票务系统在线销售票务和必要的

答案 9 :(得分:1)

这不是您正在寻找的确切答案,但这是我在项目中需要的解决方案,希望这有助于某人。这将列出以逗号分隔的1到n行项目。 Group_Concat使这在MySQL中成为可能。

select
cemetery.cemetery_id as "Cemetery_ID",
GROUP_CONCAT(distinct(names.name)) as "Cemetery_Name",
cemetery.latitude as Latitude,
cemetery.longitude as Longitude,
c.Contact_Info,
d.Direction_Type,
d.Directions

    from cemetery
    left join cemetery_names on cemetery.cemetery_id = cemetery_names.cemetery_id 
    left join names on cemetery_names.name_id = names.name_id 
    left join cemetery_contact on cemetery.cemetery_id = cemetery_contact.cemetery_id 

    left join 
    (
        select 
            cemetery_contact.cemetery_id as cID,
            group_concat(contacts.name, char(32), phone.number) as Contact_Info

                from cemetery_contact
                left join contacts on cemetery_contact.contact_id = contacts.contact_id 
                left join phone on cemetery_contact.contact_id = phone.contact_id 

            group by cID
    )
    as c on c.cID = cemetery.cemetery_id


    left join
    (
        select 
            cemetery_id as dID, 
            group_concat(direction_type.direction_type) as Direction_Type,
            group_concat(directions.value , char(13), char(9)) as Directions

                from directions
                left join direction_type on directions.type = direction_type.direction_type_id

            group by dID


    )
    as d on d.dID  = cemetery.cemetery_id

group by Cemetery_ID

这个墓地有两个通用名称,因此名称列在不同的行中,这些行由一个id连接但有两个名称ID,查询产生如下内容

 CemeteryID Cemetery_Name纬度
1 Appleton,Sulpher Springs 35.4276242832293

答案 10 :(得分:1)

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

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

首先install the connect storage engine

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

create table pivot_table
engine=connect table_type=pivot tabname=history
option_list='PivotCol=itemname,FncCol=itemvalue';

现在我们可以从pivot_table中选择我们想要的:

select * from pivot_table

More details here

答案 11 :(得分:0)

很抱歉,我可能无法完全解决您的问题,但是PostgreSQL比MySQL早10年,并且与MySQL相比非常先进,并且有许多方法可以轻松实现这一点。安装PostgreSQL并执行此查询

CREATE EXTENSION tablefunc;

然后瞧!这是广泛的文档:PostgreSQL: Documentation: 9.1: tablefunc或此查询

CREATE EXTENSION hstore;

然后再次瞧瞧! PostgreSQL: Documentation: 9.0: hstore