MySQL – 行到列

我试图searchpost,但我只findSQL Server / Access的解决scheme。 我需要在MySQL(5.X)的解决scheme。

我有一个表(称为历史),有3列:hostid,itemname,itemvalue。
如果我做一个select( 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 | +--------+------+-----+-----+ 

我要添加一个更长,更详细的解释步骤来解决这个问题。 如果太长,我很抱歉。


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

 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值 (显而易见的原因)。


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

  1. select感兴趣的列,即y值x值
  2. 用额外的列扩展基表 – 每个x值一个
  3. 对扩展表进行分组和聚合 – 每个y值为一组
  4. (可选)对汇总表进行美化

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

第1步:select感兴趣的列 。 在所需的结果中, 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进行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构build了一个漂亮的数据透视表。


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

  • 在额外的列中使用什么值。 在这个例子中我使用了itemvalue
  • 在额外的列中使用什么“中性”值。 我用NULL ,但它也可能是0"" ,这取决于你的具体情况
  • 分组时使用什么聚合函数。 我使用了sum ,但countmax也经常被使用(在构build一行“对象”的时候经常使用max ,这个对象已经分布在很多行上)
  • 使用多列的y值。 这个解决scheme不仅限于为y值使用单个列 – 只需将多余的列插入到group by子句中(不要忘记select它们)

已知的限制:

  • 此解决scheme不允许数据透视表中的n列 – 扩展基表时需要手动添加每个透视列。 所以对于5或10个x值,这个解决scheme很好。 100,不太好。 有一些存储过程生成查询的解决scheme,但他们是丑陋的,很难得到正确的。 数据透视表需要有很多列时,我目前还不知道解决这个问题的好方法。
 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; 

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

另一个选项,特别有用的,如果你有许多项目你需要枢纽是让MySQL为您build立查询:

 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; 

testing:

 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 ABCD 1 10 3 0 0 2 9 0 40 5 3 14 67 0 8 

我编辑阿贡Sagita从子查询的答案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' 

使用子查询

 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 

但如果子查询导致多于一行,则会出现问题,在子查询中使用进一步的聚合函数

我把它做成Group By hostId那么它将只显示第一行的值,
喜欢:

 ABC 1 10 2 3 

这不是你正在寻找的确切的答案,但它是我需要在我的项目上的解决scheme,并希望这可以帮助一个人。 这将列出用逗号分隔的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

我的解决scheme

 select h.hostid, sum(ifnull(hA,0)) as A, sum(ifnull(hB,0)) as B, sum(ifnull(hC,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 

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

我想出了一种方法,使我的报告转换成列几乎dynamic使用简单的查询列。 你可以在这里看到和testing它。

查询是固定的,但是这些值是dynamic的并且基于行的值。 你可以构build它所以,我使用一个查询来build立表头和另一个查看值:

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

RexTester的结果

http://rextester.com/ZSWKS28923

举一个真实的例子,下面的报告以列的forms显示船只/公共汽车的出发时间和视觉时间表。 你会看到一个额外的列没有在最后一个col没有使用可视化: sistema venda de passagens online e consumidor final e controle de frota  -  xsl tecnologia  -  xsl.com.br **票务系统在线销售票务和必需品