用于创建交叉表的MySQL存储例程

时间:2013-12-06 15:58:46

标签: mysql stored-procedures

我有一个包含3列的表格(标签)。这是一个小样本:

element_name |  data_timestamp |  data_in
Port1           2013-10-01        4335
Port1           2013-10-02        4335
Port1           2013-10-03        4365
Port1           2013-10-04        4375
Port2           2013-10-01        3335
Port2           2013-10-02        3335
Port2           2013-10-03        3365
Port2           2013-10-04        3375
Port3           2013-10-01        7335
Port3           2013-10-02        7335
Port3           2013-10-03        7365
Port3           2013-10-04        7375

我可以拥有任何不同数量的“elemment_name”,因此我需要一个具有动态列数的交叉表。在我的样本中,我想要:

Port1 | Port2 | Port3 | Date
4335    3335    7335    2013-10-01
4335    3335    7335    2013-10-02
4365    3365    7365    2013-10-03
4375    3375    7375    2013-10-04

我还没有找到一个干净的方法,所以我写了一个存储例程:

CREATE DEFINER=`root`@`localhost` PROCEDURE `makecrosstab`()
BEGIN

DECLARE hdrs TEXT; 
DECLARE value_name varchar(100);
DECLARE value_date datetime;
DECLARE datapoint INT(11);
DECLARE no_more_rows BOOLEAN;
DECLARE data_cur CURSOR FOR

SELECT  REPLACE(REPLACE(replace(replace(replace(tab.element_name,'\n',' '),' ','_'),'.','_'),'(',''),')','') as col, 
        tab.data_timestamp, tab.data_in
        FROM tab
        order by tab.element_name, tab.data_timestamp;

DECLARE CONTINUE HANDLER FOR NOT FOUND
SET no_more_rows = TRUE;

SET @colm = '';
SET @ddl = '';

drop temporary table if exists grid;

SELECT concat(group_concat(distinct REPLACE(REPLACE(replace(replace(replace(tab.element_name,'\n',' '),' ','_'),'.','_'),'(',''),')','')),',') into hdrs
FROM tab; 

WHILE (LOCATE(',', hdrs) > 0)
    DO
        SET @value = ELT(1, hdrs);
        SET @STR = SUBSTRING(hdrs, 1, LOCATE(',',hdrs)-1);
        SET hdrs = SUBSTRING(hdrs, LOCATE(',', hdrs) + 1);
        SET @ddl = concat(@ddl , @STR , ' INT(11) default 0,');  
    END WHILE;
    SET @ddl = concat('create temporary table grid (',@ddl , ' measurement_date datetime);');

    PREPARE stmt FROM @ddl;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;

    insert into grid (measurement_date) select distinct data_timestamp from tab;

    OPEN data_cur;
        the_loop: LOOP

            FETCH  data_cur
            INTO   value_name,value_date,datapoint;

            IF no_more_rows THEN
                CLOSE data_cur;
                LEAVE the_loop;
            END IF;

            SET @colm = concat('update grid set ',value_name ,' = ', datapoint, ' where measurement_date = ''',value_date,''';');
            PREPARE stmt FROM @colm;
               EXECUTE stmt;
               DEALLOCATE PREPARE stmt;
        END LOOP the_loop;

select * from grid;
END

我不禁想到有更好的方法,但如果您不知道结果集将包含多少列,则无法找到任何解决方案。

有谁知道更简单的解决方案?

1 个答案:

答案 0 :(得分:0)

也许以下存储过程可以为您提供一些简化方法的想法:

/* CODE FOR DEMONSTRATION PURPOSES */
DELIMITER $$

CREATE PROCEDURE `sp_cross_tab`()
BEGIN
    DECLARE `done` INT DEFAULT FALSE;
    DECLARE `en` VARCHAR(5);
    DECLARE `crosstab` TEXT DEFAULT '';
    DECLARE `cur` CURSOR FOR
        SELECT `element_name`
        FROM `tab`
        GROUP BY `element_name`;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET `done` = TRUE;
    OPEN `cur`;
    `read_loop`: LOOP
        FETCH `cur` INTO `en`;
        IF `done` THEN
            LEAVE `read_loop`;
        END IF;
        SET `crosstab` := CONCAT(`crosstab`, 'SUM(IF(`element_name` = \'', `en`, '\', `data_in`, 0)) AS `', `en`, '`,');
    END LOOP;
    CLOSE `cur`;
    SET @`query` := CONCAT('SELECT ', `crosstab`, ' `data_ts` FROM `tab` GROUP BY `data_ts`');
    PREPARE `stmt` FROM @`query`;
    SET @`query` := NULL; 
    EXECUTE `stmt`;
    DEALLOCATE PREPARE `stmt`;
END$$

DELIMITER ;

SQL Fiddle demo