【问题标题】:MySQL pivot table with dynamic headers based on single column data具有基于单列数据的动态标题的 MySQL 数据透视表
【发布时间】:2011-09-03 17:36:21
【问题描述】:

我正在尝试编写一个查询来创建一个数据“表”,如下所示:

SELECT cs.`category_id`, cs.`ProcessDate`, cs.`PercentChange`
  FROM `Category_Statistics` cs
 WHERE cs.`ProcessDate` >= '2011-05-10'
   AND cs.`ProcessDate` <= '2011-05-14'

这会返回如下内容:

CategoryId  |  ProcessDate  | PercentChange
-------------------------------------------
category_4  |  2011-05-10   |      10
category_4  |  2011-05-11   |      18
category_4  |  2011-05012   |      12
...
category_7  |  2011-05-10   |      21
category_7  |  2011-05-11   |      7
...
category_12 |  2011-05-10   |      7
category_12 |  2011-05-11   |      15

现在我希望结果是这样的(来自 MySQL 查询,而不是由应用程序操作):

CategoryId    | 2011-05-10 | 2011-05-11 | 2011-05-12 | 2011-05-13 | 2011-05-14 |
--------------------------------------------------------------------------------
category_4    |     10     |     18     |     12     |      9     |      14    |
category_7    |     21     |      7     |     16     |      14    |      13    |
categeory_12  |      7     |     15     |     11     |      19    |       8    |
--------------------------------------------------------------------------------

对此有两个警告:

  1. 日期范围可以扩大或缩小 (取决于查询)

  2. PercentChange 在某些情况下可能为空 案例(比如说 category_7 / 2011-05-12 可能没有设置值)

所以最终我不太确定如何构建查询的选择部分以反映动态列数(我知道它与 CONCAT 有关)。

编辑 --> 部分工作代码 -->

SELECT `CategoryId`,
   MAX(IF(c.`ProcessedOn` = '2011-04-20', c.`PercentChange`, NULL)) AS '2011-04-20',
   MAX(IF(c.`ProcessedOn` = '2011-04-21', c.`PercentChange`, NULL)) AS '2011-04-21',
   MAX(IF(c.`ProcessedOn` = '2011-04-22', c.`PercentChange`, NULL)) AS '2011-04-22',
   MAX(IF(c.`ProcessedOn` = '2011-04-23', c.`PercentChange`, NULL)) AS '2011-04-23',
   MAX(IF(c.`ProcessedOn` = '2011-04-24', c.`PercentChange`, NULL)) AS '2011-04-24'
  FROM `Category_Gravity` c
 WHERE c.`ProcessedOn` >= '2011-04-20'
   AND c.`ProcessedOn` <= '2011-04-24'
 GROUP BY `CategoryId`

我现在需要做的就是转动

MAX(IF(c.`ProcessedOn` = '2011-04-20', c.`PercentChange`, NULL)) AS '2011-04-20',

变成更动态的东西(因为我的日期范围会改变)

【问题讨论】:

    标签: mysql dynamic pivot-table


    【解决方案1】:

    看看这个类似的线程,我写了一个 sp 来完成任务

    Join two tables (with a 1-M relationship) where the second table needs to be 'flattened' into one row

    编辑。更新答案

    create table `pivot` (
      `id` int(11) not null auto_increment,
      `categoryid` int(11) default null,
      `processdate` date default null,
      `percentchange` int(11) default null,
      primary key (`id`)
    ) engine=myisam auto_increment=9 default charset=latin1;
    
    /*Data for the table `pivot` */
    
    insert  into `pivot`(`id`,`categoryid`,`processdate`,`percentchange`) values (1,4,'2011-05-10',1);
    insert  into `pivot`(`id`,`categoryid`,`processdate`,`percentchange`) values (2,4,'2011-05-11',22);
    insert  into `pivot`(`id`,`categoryid`,`processdate`,`percentchange`) values (3,4,'2011-05-12',3);
    insert  into `pivot`(`id`,`categoryid`,`processdate`,`percentchange`) values (4,7,'2011-05-10',4);
    insert  into `pivot`(`id`,`categoryid`,`processdate`,`percentchange`) values (5,7,'2011-05-11',5);
    insert  into `pivot`(`id`,`categoryid`,`processdate`,`percentchange`) values (6,12,'2011-05-10',6);
    insert  into `pivot`(`id`,`categoryid`,`processdate`,`percentchange`) values (7,12,'2011-05-12',7);
    insert  into `pivot`(`id`,`categoryid`,`processdate`,`percentchange`) values (8,4,'2011-05-13',12);
    
    
    
    delimiter //
    drop procedure if exists dynamic_view2//
    create procedure dynamic_view2(in sdate date,in edate date)
    begin
    declare finish int default 0;
    declare cdate date;
    declare str varchar(10000) default "select categoryid,";
    declare curs cursor for select processdate from pivot where processdate between sdate and edate group by processdate;
    declare continue handler for not found set finish = 1;
    open curs;
    my_loop:loop
    fetch curs into cdate;
    if finish = 1 then
    leave my_loop;
    end if;
    set str = concat(str, "max(case when processdate = '",cdate,"' then percentchange else null end) as `",cdate,"`,");
    end loop;
    close curs;
    set str = substr(str,1,char_length(str)-1);
    set @str = concat(str," from pivot
                group by categoryid");
    
    prepare stmt from @str;
    execute stmt;
    deallocate prepare stmt;
    end;//
    delimiter ;
    
    
    mysql> call dynamic_view2('2011-05-10','2011-05-13');
    +------------+------------+------------+------------+------------+
    | categoryid | 2011-05-10 | 2011-05-11 | 2011-05-12 | 2011-05-13 |
    +------------+------------+------------+------------+------------+
    |          4 |          1 |         22 |          3 |         12 |
    |          7 |          4 |          5 |       NULL |       NULL |
    |         12 |          6 |       NULL |          7 |       NULL |
    +------------+------------+------------+------------+------------+
    3 rows in set (0.00 sec)
    

    【讨论】:

    • @nick rulez 我想效仿你的例子,但是我有点挣扎,因为我没有使用 1-M 关系,只是一个包含一列的表,我想成为列标题。我并不是在贬低您的解决方案,只是在将我的示例在您的程序中生效时遇到了麻烦。
    • @nick rulez 看起来很棒,我只是在经历它(所以我理解这个过程而不是复制/粘贴),还有一件事我不太明白,VARCHAR 的使用(10000)。我了解该 var 中的内容,但为什么不使用 TEXT 代替(或者这在 SELECT 语句中不起作用?),我还认为 VARCHAR 限制为 255 个字符(返回 mysql 手册)
    • 看一下手册。 dev.mysql.com/doc/refman/5.0/en/char.html 从 mysql 5.0.3 varchar 可以达到 65535 个字符 ;)
    • @nick rulez 接受了您的答案并实施了它并且它正在工作,我希望能够通过直接查询而不是程序来完成它,但这绝对可以解决问题。谢谢你的好榜样。我将不得不阅读有关理解这一点的某些部分的文档,但就目前而言,它正在发挥作用。非常感谢您的精彩回答。
    • 如果您不想使用存储过程,您可以使用脚本/编程语言动态创建查询。我没有看到任何其他选择。很高兴我能帮助你。再见。
    猜你喜欢
    • 2019-11-24
    • 2021-06-13
    • 1970-01-01
    • 2011-12-14
    相关资源
    最近更新 更多