MySQL-从行到列

MySQL-从行到列,mysql,sql,pivot-table,etl,crosstab,Mysql,Sql,Pivot Table,Etl,Crosstab,我试图搜索帖子,但只找到了SQL Server/Access的解决方案。我需要一个MySQL 5.X中的解决方案 我有一个名为history的表,它有3列:hostid、itemname、itemvalue。 如果我从历史中选择*,它将返回 +--------+----------+-----------+ | hostid | itemname | itemvalue | +--------+----------+-----------+ | 1 | A

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

我有一个名为history的表,它有3列:hostid、itemname、itemvalue。 如果我从历史中选择*,它将返回

   +--------+----------+-----------+
   | 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 |
   +--------+------+-----+-----+
我通过hostId将其放入组中,然后它将只显示第一行的值, 比如:

使用子查询

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

但如果子查询产生的结果超过一行,则会出现问题,请在子查询中使用进一步的聚合函数

我将对解决此问题的步骤添加更长更详细的说明。如果时间太长,我道歉

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
我将从你给出的基础开始,用它来定义几个术语,我将在本文的其余部分使用这些术语。这将是基本表:

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

history.hostid列中的值将成为透视表中的y值。由于明显的原因,history.itemname列中的值将变为x值

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

选择感兴趣的列,即y值和x值 用额外的列扩展基表-每个x值一列 分组并聚合扩展表-每个y值一组 可选对聚合表进行修饰 让我们将这些步骤应用于您的问题,看看我们得到了什么:

步骤1:选择感兴趣的列。在所需结果中,hostid提供y值,itemname提供x值

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

请注意,我们没有更改行数,只是添加了额外的列。还要注意null的模式-itemname=a的行对于新列a具有非null值,对于其他新列具有null值

步骤3:对扩展表进行分组和聚合。我们需要按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值有一行。好了,我们快到了!我们只需要去掉那些丑陋的空值

第四步:美化。我们只是将任何空值替换为零,因此结果集更易于查看:

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,但count和max也经常使用max在构建跨多行分布的单行对象时经常使用max 对y值使用多列。这个解决方案并不局限于对y值使用单个列,只需将额外的列插入GROUPBY子句,并且不要忘记选择它们 已知的限制:

此解决方案不允许数据透视表中有n列—扩展基表时,需要手动添加每个数据透视列。所以对于5或10个x值,这个解决方案很好。100美元,不太好。有些解决方案使用存储过程生成查询,但它们很难看,很难正确执行。当透视表需要有很多列时,我目前不知道有什么好方法来解决这个问题。
利用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
我将的答案从子查询编辑为join。 我不确定这两种方式之间有多大区别,但仅供参考

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'

这不是你想要的确切答案,但这是我在项目中需要的解决方案,希望这能帮助到别人。这将列出由逗号分隔的1到n行项目。在MySQL中,Group_Concat使这成为可能

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,查询生成如下内容 墓地墓地名称纬度 1阿普尔顿,硫磺弹簧35.4276242832293我的解决方案:

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

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

另一个选项,如果您有许多项目需要透视,则特别有用,就是让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;
添加了一些额外的值以查看其工作情况

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

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

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

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;
结果:

对于一个实际的使用示例,本报告在下面的列中显示了船只/公共汽车的出发时间和到达时间,并提供了一个可视化的时间表。您将看到最后一列未使用的附加列,而不会混淆可视化:
**在线售票系统和presential售票系统很抱歉,也许我没有完全解决您的问题,但PostgreSQL比MySQL早10年,与MySQL相比非常先进,有很多方法可以轻松实现这一点。安装PostgreSQL并执行此查询

CREATE EXTENSION tablefunc;
CREATE EXTENSION hstore;
那么,瞧!这里有大量的文档:或者这个查询

CREATE EXTENSION tablefunc;
CREATE EXTENSION hstore;
然后再看一次

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

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

首先

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

创建表透视表 引擎=连接表\u类型=透视选项卡名称=历史 option_list='PivotCol=itemname,FncCol=itemvalue'; 现在,我们可以从pivot_表中选择所需内容:

从透视表中选择*
@Rob,你能编辑这个问题以包含准确的查询吗?请注意:@ako的链接只与MariaDB相关。自动生成和运行轴:为“a”、“B”、“C”创建三个不同的行@Palani:不,不。瞧,谢谢,这对我很有用!然而,几年后,仅供参考,我不得不使用MAX而不是SUM,因为我的itemValue是字符串,而不是数值。如果itemname是动态的呢?非常好的解释,谢谢。第4步可以通过使用IFNULLsumA,0作为A合并到第3步中,得到相同的结果,但不需要创建另一个表。这是pivot最令人惊讶的解决方案,但我只是好奇,在构成x轴的itemname列中是否有多个值,就像这里我们只有三个值,即A、B、,C.如果这些值得到扩展,则扩展到A、B、C、D、E、AB、BC、AC、AD、H…..n。那么,在这种情况下,解决办法是什么呢?好吧,令人惊讶的解释。如果某位大师能够进一步阐述,解决手动添加专栏的问题,那将是非常好的。据我所知,这是一个由Stratos Provatopoulos撰写的博客,我不知道你是否是他。另外,请引述消息来源,并将其归功于他,以示礼貌@WhiteBig请看一下日期——这个答案是在那篇博文发表前1.5年写的。也许你应该让博客来信任我。@Mihai也许你可以帮我。看看这个:可以将“ifnullSUMcase when itemname=”和“then itemvalue end,0 AS”简化为“SUMcase when itemname=”和“then itemvalue else 0 end AS”,。当itemname='A'时,这会输出类似SUMcase的术语,然后itemvalue else 0以'A'结束。我们可以将其构建为视图吗?
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 |
+--------+------+------+------+
CREATE EXTENSION tablefunc;
CREATE EXTENSION hstore;