HAWQ中的行列转置
行列转置是ETL或报表系统中的常见需求,HAWQ提供的内建函数和过程语言编程功能,使行列转置操作的实现变得更为简单。
一、行转列
1. 固定列数的行转列
原始数据如下:
test=# select * from score; name | subject | score ------+---------+------- 张三 | 语文 | 80 张三 | 数学 | 70 张三 | 英语 | 60 李四 | 语文 | 90 李四 | 数学 | 100 李四 | 英语 | 80 (6 rows)
要得到以下的结果:
name | 语文 | 数学 | 英语 ------+------+------+------ 张三 | 80 | 70 | 60 李四 | 90 | 100 | 80
(1)使用标准SQL实现
select name,
max(case when subject = '语文' then score else 0 end) as "语文",
max(case when subject = '数学' then score else 0 end) as "数学",
max(case when subject = '英语' then score else 0 end) as "英语"
from score
group by name order by name;
执行结果如下所示:
test=# select name, test-# max(case when subject = '语文' then score else 0 end) as "语文", test-# max(case when subject = '数学' then score else 0 end) as "数学", test-# max(case when subject = '英语' then score else 0 end) as "英语" test-# from score test-# group by name order by name; name | 语文 | 数学 | 英语 ------+------+------+------ 张三 | 80 | 70 | 60 李四 | 90 | 100 | 80 (2 rows)
此方法简单并具有通用性,所有SQL数据库都支持。
(2)使用内建聚合函数实现
select name,
split_part(split_part(tmp,',',3),':',2) as "语文",
split_part(split_part(tmp,',',1),':',2) as "数学",
split_part(split_part(tmp,',',2),':',2) as "英语"
from (select name,string_agg(subject||':'||score,',' order by subject) as tmp
from score
group by name) as t
order by name;
执行结果如下所示:
test=# select name, test-# split_part(split_part(tmp,',',3),':',2) as "语文", test-# split_part(split_part(tmp,',',1),':',2) as "数学", test-# split_part(split_part(tmp,',',2),':',2) as "英语" test-# from (select name,string_agg(subject||':'||score,',' order by subject) as tmp test(# from score test(# group by name) as t test-# order by name; name | 语文 | 数学 | 英语 ------+------+------+------ 张三 | 80 | 70 | 60 李四 | 90 | 100 | 80 (2 rows)
在子查询中按name列分组聚合,使用string_agg函数将同一name的subject和score按subject顺序连接成字符串。subject与score用‘:’连接,段分隔符为‘,’。子查询的结果为:
test=# select name,string_agg(subject||':'||score,',' order by subject) as tmp test-# from score test-# group by name; name | tmp ------+-------------------------- 张三 | 数学:70,英语:60,语文:80 李四 | 数学:100,英语:80,语文:90 (2 rows)
外层查询使用两个嵌套的split_part函数,将字符串分隔成列。内层split_part取得subject:score,外层split_part取得相应subject的score。这种方法利用了HAWQ内建的聚合函数,实现简洁。
2. 不定列数的行转列
原始数据如下:
test=# select * from t1; c1 | c2 | c3 ----+----+---- 1 | 我 | 1 1 | 是 | 2 1 | 谁 | 3 2 | 不 | 1 2 | 知 | 2 3 | 道 | 1 (6 rows)
要得到以下的结果,其中列数是不定的:
c1 | c2 | c3 | c4 ----+----+----+---- 1 | 我 | 是 | 谁 2 | 不 | 知 | 3 | 道 | |
因为结果集列数不固定,必须使用动态SQL实现(HAWQ不支持crosstab函数)。建立如下的PLPGSQL函数:
create or replace function fn_crosstab(refcursor) returns refcursor
as $body$
declare
v_colnum int;
v_sqlstring varchar(2000) := 'select c1 ';
begin
-- 获得最大列数
select max(c) into v_colnum from (select count(*) c from t1 group by c1) t;
for i in 1 .. v_colnum loop
v_sqlstring := v_sqlstring || ', split_part(tmp,'','',' || cast(i as varchar(2)) || ') c' || cast(i+1 as varchar(2));
end loop;
v_sqlstring := v_sqlstring || ' from (select c1,string_agg(c2,'','' order by c3) as tmp from t1 group by c1) t order by c1';
-- raise notice '%', v_sqlstring;
open $1 for execute v_sqlstring;
return $1;
end;
$body$ language plpgsql;
调用函数:
begin;
select fn_crosstab('cur1');
fetch all in cur1;
commit;
服务器游标默认只能在一个事务中存在,事务结束自动销毁。如果没用BEGIN开启一个事务,任何一条语句都是一个事务,所以select fn_crosstab('cur1')所建立的游标立即被销毁。执行结果如下所示:
test=# begin;
BEGIN
test=# select fn_crosstab('cur1');
fn_crosstab
-------------
cur1
(1 row)
test=# fetch all in cur1;
c1 | c2 | c3 | c4
----+----+----+----
1 | 我 | 是 | 谁
2 | 不 | 知 |
3 | 道 | |
(3 rows)
test=# commit;
COMMIT
二、列转行
1. 单行变多行
原始数据如下:
test=# select * from book; id | name | tag ----+------+---------- 1 | Java | aa,bb,cc 2 | C++ | dd,ee (2 rows)
要得到以下的结果:
name | tag | rn ------+------+---- Java | aa | 1 Java | bb | 2 Java | cc | 3 C++ | dd | 1 C++ | ee | 2
HAWQ 2.1.1.0基于PostgreSQL 8.2.15,因此还不包含generate_subscripts()、array_length()、unnest(array) with ordinality等函数功能。为了给每个name的tag按原始位置增加序号,需要建立以下函数,返回数组值及其对应的下标:
create or replace function f_unnest_ord(anyarray, out val anyelement, out ordinality integer) returns setof record language sql immutable as 'select $1[i], i - array_lower($1,1) + 1 from generate_series(array_lower($1,1), array_upper($1,1)) i';
然后执行查询:
select name, (rec).val tag, (rec).ordinality rn
from (select *, f_unnest_ord(arr) as rec
from (select id, name, string_to_array(tag, ',') arr from book) t) t
order by id, rn;
执行结果如下所示:
test=# select name, (rec).val tag, (rec).ordinality rn test-# from (select *, f_unnest_ord(arr) as rec test(# from (select id, name, string_to_array(tag, ',') arr from book) t) t test-# order by id, rn; name | tag | rn ------+-----+---- Java | aa | 1 Java | bb | 2 Java | cc | 3 C++ | dd | 1 C++ | ee | 2 (5 rows)
2. 多列转多行
原始数据如下:
test=# select * from t1; c1 | c2 | c3 | c4 ----+----+----+---- 1 | 我 | 是 | 谁 2 | 不 | 知 | 3 | 道 | | (3 rows)
要得到以下结果:
c1 | c2 | c3 ----+----+---- 1 | 我 | 1 1 | 是 | 2 1 | 谁 | 3 2 | 不 | 1 2 | 知 | 2 3 | 道 | 1
也以看到,原数据只有三行,而结果是六行数据。要达到想要的结果,最重要的是如何从现有的行构造出新的数据行。下面用三种方法实现。
(1)最直接的方法——union
用SQL的并集操作符union是最容易想到的方法。
select *
from (select c1,c2,1 c3 from t1
union all
select c1,c3,2 from t1
union all
select c1,c4,3 from t1) t
where c2 <> ''
order by c1, c3;
查询结果如下:
test=# select * test-# from (select c1,c2,1 c3 from t1 test(# union all test(# select c1,c3,2 from t1 test(# union all test(# select c1,c4,3 from t1) t test-# where c2 <> '' test-# order by c1, c3; c1 | c2 | c3 ----+----+---- 1 | 我 | 1 1 | 是 | 2 1 | 谁 | 3 2 | 不 | 1 2 | 知 | 2 3 | 道 | 1 (6 rows)
(2)最灵活的方法——笛卡尔积
union虽然直接了当,但太过死板。如果列很多,需要叠加很多的union all,凸显乏味。更灵活的方法是通过笛卡尔积运算构造数据行,这种方法的关键在于需要一个所需行数的辅助表。许多关系数据库都提供相应的方法,例如Oracle用connect by level、MySQL用数字辅助表、PostgreSQL用generate_serie函数等。
select *
from (select c1,
case when t2=1 then c2
when t2=2 then c3
else c4
end c2,
t2 c3
from (select * from t1, generate_series(1,3) t2) t) t
where c2 <> ''
order by c1, c3;
查询结果如下:
test=# select * test-# from (select c1, test(# case when t2=1 then c2 test(# when t2=2 then c3 test(# else c4 test(# end c2, test(# t2 c3 test(# from (select * from t1, generate_series(1,3) t2) t) t test-# where c2 <> '' test-# order by c1, c3; c1 | c2 | c3 ----+----+---- 1 | 我 | 1 1 | 是 | 2 1 | 谁 | 3 2 | 不 | 1 2 | 知 | 2 3 | 道 | 1 (6 rows)
(3)最独特的方法——unnest
前面两种是相对通用的方法,关系数据库的SQL都支持,而unnest是PostgreSQL独有的函数。有了前面的基础,这个实现就比较简单了,只要执行下面的查询即可:
select *
from (select c1,split_part(unnest(c2),':',1) c2, split_part(unnest(c2),':',2) c3
from (select c1,string_to_array(c2,',') c2
from (select c1,coalesce(c2,'')||':1,'||coalesce(c3,'')||':2,'||coalesce(c4,'')||':3' c2
from t1) t) t) t
where c2 <> ''
order by c1, c3;
查询结果如下:
test=# select * test-# from (select c1,split_part(unnest(c2),':',1) c2, split_part(unnest(c2),':',2) c3 test(# from (select c1,string_to_array(c2,',') c2 test(# from (select c1,coalesce(c2,'')||':1,'||coalesce(c3,'')||':2,'||coalesce(c4,'')||':3' c2 test(# from t1) t) t) t test-# where c2 <> '' test-# order by c1, c3; c1 | c2 | c3 ----+----+---- 1 | 我 | 1 1 | 是 | 2 1 | 谁 | 3 2 | 不 | 1 2 | 知 | 2 3 | 道 | 1 (6 rows)
参考:
PostgreSQL unnest() with element number
POSTGRESQL交叉表的实现
PostgreSQL 一行变多行
HAWQ中的行列转置的更多相关文章
- Excel 行列转置 解决竖向拉,字母跟着递增的问题
今天工作中遇到需要将Excel行列转置涉及到的数据单元格一共几千个 查询网上说可以通过复制粘贴单元格,粘贴选项中转置一项实现,但是所涉及的sheet页中,数据格式和单元格格式各不一样,转置失败! 怎么 ...
- 用powershell+excel行列转置三步走
本文重点讲解第一步,手动在excel表中输入公式,或者用powershell自动输入公式. 第二步,用powershell向excel中写入数据,略. 第三步,用powershell从excel中读取 ...
- [转]Python中的矩阵转置
Python中的矩阵转置 via 需求: 你需要转置一个二维数组,将行列互换. 讨论: 你需要确保该数组的行列数都是相同的.比如: arr = [[1, 2, 3], [4, 5, 6], [7, 8 ...
- SQL动态长度行列转置
一,案列问题描述: 某销售系统中,注册的用户会在随后的月份中购物下单,需要按月统计注册的用户中各个月下单的金额.源数据表如下: FM::注册月份,CM: 下单月份, AMT:下单金额 期望得到如下统计 ...
- 简化实现动态行列转置的SQL
动态行列转换的计算在实际业务中非经常见,网上各类技术论坛上都有讨论,比方以下这些问题: http://www.iteye.com/problems/87788 http://bbs.csdn.net/ ...
- 使用SQL SERVER PIVOT实现行列转置
一般我们在使用SQL语句实现行列转置时候,最常用的方法无外乎就是 case语句来实现,但是如果需要需要转置的列太多,那么case起来语句就无限庞大,十分不方便,sql server中的PIVOT就可以 ...
- Excel-怎样实现行列转置
有时候,我们为了某些需要,必须把工作表的行列进行转置的方式显示.重新输入很浪费时间,怎样简单的实现转置呢,强大的excel2007提供了此项功能,具体怎么做,下面看我来演示一下. 工具/原料 装有 ...
- 行列转置(Oracle)
一.Oracle行列转置 1.行转列 (1)创建表格.插入测试数据 create table student( id number, name ), course ), score number ) ...
- WebGIS中通过行列号来换算出多种瓦片的URL 之离线地图(转载)
WebGIS中通过行列号来换算出多种瓦片的URL 之离线地图 1.前言 在前面我花了两个篇幅来讲解行列号的获取,也解释了为什么要获取行列号.在这一章,我将把常见的几种请求瓦片时的URL样式罗列出来,并 ...
随机推荐
- php 与 c++ openssl 加密通信
$key = '1234567890123456'; $iv = '1234567890123456'; $enc = openssl_encrypt("hello wolrd!" ...
- MR案例:小文件处理方案
HDFS被设计来存储大文件,而有时候会有大量的小文件生成,造成NameNode资源的浪费,同时也影响MapReduce的处理效率.有哪些方案可以合并这些小文件,或者提高处理小文件的效率呢? 1). 所 ...
- Ubuntu 16.04下EasyOpenJTAG+OpenOCD的安装和使用【转】
本文转载自:http://www.linuxdiyf.com/linux/24086.html Ubuntu 16.04下EasyOpenJTAG+OpenOCD的安装和使用 发布时间:2016-09 ...
- ubuntu下apt-get的配置文件是哪个
答:在/etc/apt/apt.conf 这个配置文件里可以指定使用代理,如: Acquire::https::proxy "http://myproxy.com:8080/";
- 防止XSS攻击的方式
主要有三种请求方式,进行过滤替换非法符号 1.普通的GET请求数据: 2.FORM表单提交数据: 3.Json格式数据提交: 把下面5个文件放入项目中即可 package com.joppay.adm ...
- Spring Boot CRUD+分页(基于Mybatis注解方式)
步骤一:关于Mybatis Mybatis 是用来进行数据库操作的框架.其中分页使用Mybatis中的PageHelper插件. Mybatis与hibernate对比: 1.hibernate是一个 ...
- Yii框架(一)
这里接触了 MVC 设计模式中的控制器和视图部分. 创建了一个操作作为控制器的一部分去处理特定请求. 然后又创建了一个视图去构造响应内容. 在这个小例子中,没有模型调用,唯一涉及到数据的地方是 mes ...
- cocos2d-js入门一
决定搞cocos2d-js,但发现官网已经没有独立的js了,lua,现在全部整合到cocos2d-x中了. win7+cocos2d-x 3.8 由于之前搭建了vs2012 +python平台 ,此时 ...
- codeforces208E Blood Cousins
题目链接:codeforces208E 正解:$dsu$ $on$ $tree$ 解题报告: 又是一波$dsu$ $on$ $tree$咯… $p$级$cousin$其实就是对于$x$的$p$级祖先统 ...
- 微信小程序:scroll-view的bug
flex:1并不能使scroll-view的高度固定,需要添加高度height:1rpx(数值大于0)就行