SQL 行列倒置

简介:

SQL的的行列倒置已经不是新知识了,但在博主的技术咨询期间,仍发现其实有很多人并不了解这块,所以在此专门写一篇博客记录。本文将以Mysql为例,并以数据采集指标信息获取为例子。在下面的例子,你可以在sqlfiddle运行。

首先我们需要创建数据库Schema:

    CREATE TABLE Chart
        (`createTime` DateTime, `kpi` varchar(30), `field` varchar(30), `value` double);

    INSERT INTO Chart
        (`createTime`,`kpi`, `field`, `value`)
    VALUES
        ("2015-02-01 12:00:00", 'disk', 'disk', 20),
        ("2015-02-01 12:15:00", 'disk', 'disk', 30),
        ("2015-02-01 12:20:00", 'disk', 'disk', 25),
        ("2015-02-01 12:30:00", 'disk', 'disk', 25),
        ("2015-02-01 12:35:00", 'disk', 'disk', 25),
        ("2015-02-01 12:40:00", 'disk', 'disk', 25),

        ("2015-02-01 12:00:00", 'disk', 'disk-all', 20),
        ("2015-02-01 12:20:00", 'disk', 'disk-all', 30),
        ("2015-02-01 12:25:00", 'disk', 'disk-all', 25),
        ("2015-02-01 12:30:00", 'disk', 'disk-all', 25),
        ("2015-02-01 12:35:00", 'disk', 'disk-all', 25),
        ("2015-02-01 12:40:00", 'disk', 'disk-all', 25),
        ("2015-02-01 12:40:00", 'cpu', 'cpu-all', 25),
        ("2015-02-01 12:40:00", 'cpu', 'cpu', 25)
    ;

在这里字段分别代表:createTime = 数据采集时间,kpi = 数据采集指标,field = 作为指标的小类(一个kpi可以包含多个field),value = 采集的数据

当我们创建好了数据结构,下面因为我们希望获取出所有的 固定时间范围内的特定kpi的数据,注意因为可能一个kpi中的多个field,但是某些field漏采了部分时间的数据,所以这里我们需要补充异常点0. 并由于EChart这类图表库,希望我们输入的是横轴和纵轴为两个独立的数组对象表示。所以我们需要如下:

option = {
    ....

    xAxis : [
        {
            type : 'category',
            boundaryGap : false,
            data : ['周一','周二','周三','周四','周五','周六','周日']
        }
    ],
    yAxis : [
        {
            type : 'value',
            axisLabel : {
                formatter: '{value} °C'
            }
        }
    ],
    series : [
        {
            ....
            data:[11, 11, 15, 13, 12, 13, 10]
        },
        {
           ....
            data:[11, 11, 15, 13, 12, 13, 10]
        }
    ]
};

取出横轴比较容易,如下:

SELECT createTime,kpi, field, value FROM Chart WHERE kpi = 'disk' and (createTime BETWEEN '2015-02-01 12:00:00' AND '2015-02-01 12:25:00');

但是纵轴如果我们以同样方式取出,可能存在需要我们自动程序补值,并且需要保证每项数据和横轴对应,所以我们的程序处理会比较复杂,如下:

SELECT createTime,kpi, field, value FROM Chart WHERE kpi = 'disk' and (createTime BETWEEN '2015-02-01 12:00:00' AND '2015-02-01 12:25:00');

结果为:

createTime  kpi field   value
February, 01 2015 12:00:00  disk    disk    20
February, 01 2015 12:15:00  disk    disk    30
February, 01 2015 12:20:00  disk    disk    25
February, 01 2015 12:00:00  disk    disk-all    20
February, 01 2015 12:20:00  disk    disk-all    30
February, 01 2015 12:25:00  disk    disk-all    25

有没有其他方案更佳的呢?当然那就是本文要说的sql的倒置,如果我们能够把返回数据转换为如下:

field   ‘2015-02-01 12:00:00’   ‘2015-02-01 12:15:00’   ‘2015-02-01 12:20:00’   ‘2015-02-01 12:25:00’
disk         20                            30                     25                       0
disk-all     20                             0                     30                       25

那么程序就很好处理了。在上面我们已经能够取出所有的横轴数据并排序,接下来我们将可以很简单的做到行列倒置:如下:

SELECT field,
SUM(IF(createTime = '2015-02-01 12:00:00', value, 0)) as '2015-02-01 12:00:00',
SUM(IF(createTime = '2015-02-01 12:15:00', value, 0)) as '2015-02-01 12:15:00',
SUM(IF(createTime = '2015-02-01 12:20:00', value, 0)) as '2015-02-01 12:20:00',
SUM(IF(createTime = '2015-02-01 12:25:00', value, 0)) as '2015-02-01 12:25:00' 
FROM Chart
WHERE kpi = 'disk' and (createTime BETWEEN '2015-02-01 12:00:00' AND '2015-02-01 12:25:00')
GROUP BY field

这样返回数据满足我们的需求了。


下面我们来分析下这句SQL,

  1. 首先我们利用‘IF(createTime = ‘2015-02-01 12:00:00’, value, 0)’来处理插值,并对每行数据转为以时间为列数据,并可以利用IF来补’0‘,将会如下:

SQL:

SELECT field,
IF(createTime = '2015-02-01 12:00:00', value, 0) as '2015-02-01 12:00:00',
IF(createTime = '2015-02-01 12:15:00', value, 0) as '2015-02-01 12:15:00',
IF(createTime = '2015-02-01 12:20:00', value, 0) as '2015-02-01 12:20:00',
IF(createTime = '2015-02-01 12:25:00', value, 0) as '2015-02-01 12:25:00' 
FROM Chart
WHERE kpi = 'disk' and (createTime BETWEEN '2015-02-01 12:00:00' AND '2015-02-01 12:25:00');

结果为:

field   ‘2015-02-01 12:00:00’   ‘2015-02-01 12:15:00’   ‘2015-02-01 12:20:00’   ‘2015-02-01 12:25:00’
disk               20                       0                       0                       0
disk                0                       30                      0                       0
disk                0                       0                       25                      0
disk-all            20                      0                       0                       0
disk-all            0                       0                       30                      0
disk-all            0                       0                       0                       25
  1. 这下我们就可以利用sql的聚合函数sum和group by来聚合数据行:

SQL:

SELECT field,
SUM(IF(createTime = '2015-02-01 12:00:00', value, 0)) as '2015-02-01 12:00:00',
SUM(IF(createTime = '2015-02-01 12:15:00', value, 0)) as '2015-02-01 12:15:00',
SUM(IF(createTime = '2015-02-01 12:20:00', value, 0)) as '2015-02-01 12:20:00',
SUM(IF(createTime = '2015-02-01 12:25:00', value, 0)) as '2015-02-01 12:25:00' 
FROM Chart
WHERE kpi = 'disk' and (createTime BETWEEN '2015-02-01 12:00:00' AND '2015-02-01 12:25:00')
GROUP BY field

效果如上。

对于sql行列转置可以简述为分为两部分:

  1. 利用条件逻辑(mysql: IF, sql server: case … when(sql server 2005开始支持数据透视表pivot) ..)将 需要倒置的数据变为列。
  2. 利用聚合函数(sum、max、min…)group by 合并数据。这里需要注意max、min需要注意数据的边界,如存在负数且默认值采用0,那么max就会存在问题,所以一般sum是最安全的(任何数加0都不会改变结果);但对于特定场景max、min也是安全方案。

我们也可以将上面两次请求合并为一次,这就需要mysql的动态拼接,如下:

SELECT 
@time_sql := group_concat("SUM(IF(createTime = '", t.createTime, "', value, 0)) AS '" , t.createTime, "'")  
FROM (
 SELECT DISTINCT createTime FROM Chart ORDER BY createTime
) AS t;

 set @v_sql = CONCAT("SELECT field", IF(ISNULL(@time_sql) , " ", CONCAT(", ", @time_sql)) ," FROM Chart GROUP BY field");

prepare stmt from @v_sql; 
EXECUTE stmt;   
deallocate prepare stmt; 

本文转自破狼博客园博客,原文链接:http://www.cnblogs.com/whitewolf/p/4337950.html,如需转载请自行联系原作者
目录
相关文章
|
6月前
|
SQL HIVE 索引
【Hive SQL 每日一题】行列转换
该文介绍了如何使用SQL进行数据的行列转换。首先展示了行转列的例子,通过创建一个学生成绩表,利用`IF`和`SUM`函数按学生ID分组,将每个学生的各科成绩转换为独立列。然后,文章讲述了列转行的需求,利用`LATERAL VIEW`和`POSEXPLODE`将已转换的表格恢复为原始行格式,通过索引匹配过滤笛卡尔积避免错误结果。此外,还提到了使用`UNION ALL`的另一种列转行方法。
127 1
|
SQL Oracle 关系型数据库
【SQL应知应会】行列转换(三)• Oracle版
【SQL应知应会】行列转换(三)• Oracle版
150 0
|
存储 SQL Oracle
【SQL应知应会】行列转换(二)• MySQL版
【SQL应知应会】行列转换(二)• MySQL版
281 0
|
SQL 存储 Oracle
通过sql做数据透视表,数据库表行列转换(pivot和Unpivot用法)(一)
在mssql中大家都知道可以使用pivot来统计数据,实现像excel的透视表功能 一、MSsqlserver中我们通常的用法
395 0
|
SQL HIVE
SQL之行列转换
Hive 查询语句之行列转换
158 0
|
存储 SQL 分布式计算
MaxCompute SQL使用小技巧之行列转换
行列转换在业务需求分析经常使用,方法很多,这里介绍下使用Maxcomputer内置函数进行转换
1437 0
|
SQL 索引 存储
SQL Server SQL性能优化之--pivot行列转换减少扫描计数优化查询语句
原文:SQL Server SQL性能优化之--pivot行列转换减少扫描计数优化查询语句 原文出处:http://www.cnblogs.com/wy123/p/5933734.html     先看常用的一种表结构设计方式:   那么可能会遇到一种典型的查询方式,主子表关联,查询子表中的某些(或者全部)Key点对应的Value,横向显示(也即以行的方式显示)   这种查询方式很明显的一个却显示多次对字表查询(暂时抛开索引)   相比这种查询方式很多人都遇到过,如果子表是配置信息之类的小表的话,问题不大,如果字表数据量较大,可能就会有影响了。
1253 0
|
SQL Go
SQL Server中行列转换
转自http://www.cnblogs.com/zhangzt/archive/2010/07/29/1787825.htmlSQL Server中行列转换 Pivot UnPivot PIVOT用于将列值旋转为列名(即行转列),在SQL Server 2000可以用聚合函数配合CAS...
975 0