前言:
mysql行列变化,最难的就是将多个列变成多行,使用的比较多的是统计学中行变列,列变行,没有找到现成的函数或者语句,所以自己写了存储过程,使用动态sql来实现,应用业务场景,用户每个月都有使用记录数录入一张表,一个月一个字段,所以表的字段是动态增长的,现在需要实时统计当前用户使用的总数量,如果你知道有多少个字段,那么可以用select c1+c2+c3+…. From tbname where tid=’111’;来实现,但是关键是这个都是动态的,所以在应用程序端来实现确实不适宜,可以放在数据库后台在存储过程里实现。
而且在行变成列中,如果要写单个sql来实现,列的数目就需要写死,因为如果不知道要展示成多少列的话,就需要用动态变量,而一条sql里面无法使用动态变量。但是可以使用sql块来实现动态的效果。
一,列变成行例子演示
1,准备测试数据
这是基础数据表,里面有多个字段wm201403……,现在需要把N个这样的列变成行数据。
USE csdn;
DROP TABLE IF EXISTS flow_table;
CREATE TABLE `flow_table` (
`ID` INT(11) NOT NULL AUTO_INCREMENT,
`Number` BIGINT(11) NOT NULL,
`City` VARCHAR(10) NOT NULL,
`wm201403` DECIMAL(7,2) DEFAULT NULL,
`wm201404` DECIMAL(7,2) DEFAULT NULL,
`wm201405` DECIMAL(7,2) DEFAULT NULL,
`wm201406` DECIMAL(7,2) DEFAULT NULL,
`wm201407` DECIMAL(7,2) DEFAULT NULL,
`wm201408` DECIMAL(7,2) DEFAULT NULL,
PRIMARY KEY (`ID`,`Number`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
录入一批测试数据:
INSERT INTO flow_table(Number,City,wm201403,wm201404,wm201405,wm201406,wm201407,wm201408)SELECT 1,'shanghai',100.2,180.4,141,164,124,127;
INSERT INTO flow_table(Number,City,wm201403,wm201404,wm201405,wm201406,wm201407,wm201408)SELECT 2,'shanghai',110.23,180.34,141.23,104.78,124.67,127.45;
INSERT INTO flow_table(Number,City,wm201403,wm201404,wm201405,wm201406,wm201407,wm201408)SELECT 3,'beijing',123.23,110.34,131.33,154.58,154.67,167.45;
INSERT INTO flow_table(Number,City,wm201403,wm201404,wm201405,wm201406,wm201407,wm201408)SELECT 4,'hangzhou',0,110.34,131.33,154.58,154.67,0;
INSERT INTO flow_table(Number,City,wm201405,wm201406,wm201407,wm201408)SELECT 5,'hangzhou',131.33,154.58,154.67,0;?
需要达到的统计效果是:
+--------+-----------+
| Number | total_num |
+--------+-----------+
| 1 | 836.60 |
| 2 | 788.70 |
| 3 | 841.60 |
| 4 | 550.92 |
| 5 | 440.58 |
+--------+-----------+
5 rows in set (0.00 sec)
2,存储过程遍历:
这个存储过程建立了2张临时表,查询测试表数据形成游标,遍历游标根据主键Number来调用pro_flow_modify存储过程进行行列变化。代码如下:
DELIMITER $$
DROP PROCEDURE IF EXISTS csdn.`proc_all_changes`$$
CREATE PROCEDURE csdn.proc_all_changes()
BEGIN
DECLARE v_number BIGINT;
DECLARE v_city VARCHAR(10);
DECLARE _done INT DEFAULT 0;
/*定义游标*/
DECLARE cur_all CURSOR FOR SELECT Number,City FROM csdn.`flow_table`;
/**这里如果需要定义下当NOT FOUND的时候,EXIT退出游标遍历,不然如果设置成CONTINUE会一直执行下去。*/
DECLARE EXIT HANDLER FOR NOT FOUND BEGIN SET _done=1;END;
/*建立临时表,存放所有字段的临时表*/
DROP TABLE IF EXISTS flow_n_columns;
CREATE TABLE `flow_n_columns` (
`column_name` VARCHAR(10) NOT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8;
/*存放最终变成行的数据表*/
DROP TABLE IF EXISTS flow_tmp;
CREATE TABLE `flow_tmp` (
`Number` INT(11) DEFAULT NULL,
`City` VARCHAR(10) DEFAULT NULL,
`wm_str` VARCHAR(10) DEFAULT NULL,
`Wm` DECIMAL(7,2) DEFAULT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8;
OPEN cur_all;
REPEAT
FETCH cur_all INTO v_number, v_city;
IF NOT _done THEN
CALL csdn.pro_flow_modify(v_number,v_city);
END IF;
UNTIL _done=1 END REPEAT;
CLOSE cur_all;
/*展示下所有的行转列的数据**/
SELECT * FROM csdn.flow_tmp;
END$$
DELIMITER ;?
3,行里变化存储过程
通过查询系统表information_schema.`COLUMNS`来获取测试表flow_table的所有列,然后写动态SQL,来把列的值录入到临时表flow_tmp中。
DELIMITER $$
DROP PROCEDURE IF EXISTS csdn.`pro_flow_modify`$$
CREATE PROCE