行列转置是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中的行列转置的更多相关文章

  1. Excel 行列转置 解决竖向拉,字母跟着递增的问题

    今天工作中遇到需要将Excel行列转置涉及到的数据单元格一共几千个 查询网上说可以通过复制粘贴单元格,粘贴选项中转置一项实现,但是所涉及的sheet页中,数据格式和单元格格式各不一样,转置失败! 怎么 ...

  2. 用powershell+excel行列转置三步走

    本文重点讲解第一步,手动在excel表中输入公式,或者用powershell自动输入公式. 第二步,用powershell向excel中写入数据,略. 第三步,用powershell从excel中读取 ...

  3. [转]Python中的矩阵转置

    Python中的矩阵转置 via 需求: 你需要转置一个二维数组,将行列互换. 讨论: 你需要确保该数组的行列数都是相同的.比如: arr = [[1, 2, 3], [4, 5, 6], [7, 8 ...

  4. SQL动态长度行列转置

    一,案列问题描述: 某销售系统中,注册的用户会在随后的月份中购物下单,需要按月统计注册的用户中各个月下单的金额.源数据表如下: FM::注册月份,CM: 下单月份, AMT:下单金额 期望得到如下统计 ...

  5. 简化实现动态行列转置的SQL

    动态行列转换的计算在实际业务中非经常见,网上各类技术论坛上都有讨论,比方以下这些问题: http://www.iteye.com/problems/87788 http://bbs.csdn.net/ ...

  6. 使用SQL SERVER PIVOT实现行列转置

    一般我们在使用SQL语句实现行列转置时候,最常用的方法无外乎就是 case语句来实现,但是如果需要需要转置的列太多,那么case起来语句就无限庞大,十分不方便,sql server中的PIVOT就可以 ...

  7. Excel-怎样实现行列转置

    有时候,我们为了某些需要,必须把工作表的行列进行转置的方式显示.重新输入很浪费时间,怎样简单的实现转置呢,强大的excel2007提供了此项功能,具体怎么做,下面看我来演示一下. 工具/原料   装有 ...

  8. 行列转置(Oracle)

    一.Oracle行列转置 1.行转列 (1)创建表格.插入测试数据 create table student( id number, name ), course ), score number ) ...

  9. WebGIS中通过行列号来换算出多种瓦片的URL 之离线地图(转载)

    WebGIS中通过行列号来换算出多种瓦片的URL 之离线地图 1.前言 在前面我花了两个篇幅来讲解行列号的获取,也解释了为什么要获取行列号.在这一章,我将把常见的几种请求瓦片时的URL样式罗列出来,并 ...

随机推荐

  1. USB详解

    USB作为一种串行接口,应用日益广泛.如同每个工程设计人员必须掌握I2C,RS232这些接口一样,我们也必须掌握USB.但是USB的接口协议实在有点费解,Linux UCHI驱动作者之一Alan St ...

  2. poj_2528 Mayor's posters (线段树经典题+离散化方法)

    由于题面中给定的wall最大长度为10 000 000:若简单用线段树势必会超时. 而注意到题面中规定了输入海报的个数<=1000:因此不妨离散化,使得线段中叶节点仅含有1000个,那么线段最大 ...

  3. Spring @Scheduler使用cron时的执行问题

    主要想弄清使用Spring @Scheduler cron表达式时的两个问题: 同一定时任务,第二次触发时间到了,第一次还没有执行完成时会执行吗? 不同的定时任务,相互之间是否有影响? 结论写在前面: ...

  4. jsp判断以某个字母开头

    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ tag ...

  5. RabbitMQ入门(5)——主题(Topic)

    前面我们介绍了通过使用direct exchage,改善了fanout exchange只能进行虚拟广播的方式.尽管如此,直接交换也有自身的局限,它不能基于多个条件路由. 在我们的日志系统中,也许我们 ...

  6. url拼接

    在做网页抓取的时候经常会遇到一个问题就是页面中的链接是相对链接,这个时候就需要对链接进行url拼接,才能得到绝对链接. url严格按照一定的格式构成,一般为如下5个字段: 详细可参考RFC:http: ...

  7. WinCE数据通讯之Web Service分包传输篇

    前面写过<WinCE数据通讯之Web Service篇>那篇对于数据量不是很大的情况下单包传输是可以了,但是对于大数据量的情况下WinCE终端的内存往往会在解包或者接受数据时产生内存溢出. ...

  8. shell脚本中case select 的使用

    #!/bin/bash # case echo "1.Install PHP" echo "2.Install Mysql" echo "3.Inst ...

  9. JavaScript之右下角广告

    网站中,我们都遇到想这样的悬浮广告,我们先给图片设置右下角悬浮属性,关闭按钮键也就是节点的删除: window.onload = function(){ var TipBox = document.g ...

  10. JavaScript高级与面向对象

    对象:任何事物都可以看作是对象. 1.面向对象与面向过程的概念 面向过程:凡是自己亲力亲为,自己按部就班的解决现有问题. 面向对象:自己充当一个指挥者的角色,指挥更加专业的对象帮我解决问题. 联系:面 ...