PostgreSQL Oracle 兼容性之 - Partition By Outer Join实现稠化报表

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
简介:

标签

PostgreSQL , Oracle , 稠化报表 , partition by outer join


背景

背景介绍:借用Oracle的一篇例子:

http://blog.sina.com.cn/s/blog_4cef5c7b01016lm5.html

在数据库表中,存储的数据经常是稀疏数据(sparse data),而不是稠密数据(dense data)。

先来了解一下什么是稀疏数据,比如一个产品销售情况表(比如有产品名、销售时间(精确到年月)、销售量3个列),假设某个时间某些产品它没有销售,一般也不会将这些产品的销售量存储为0,而是不存储,这样在产品销售情况表中就会产生很多缺失的行(gap rows),导致的结果就是特定产品销售数据按时间维度进行排序,是不连续的,或者说此产品销售在时间序列上是有缺失的。顾名思义,稠密数据是相对于稀疏数据来说的,还是用上面的假设说明,也就是说产品在某个时间没有销售,也必须存储此产品销售情况,销售量置0存储,这样对某个特定产品来说它在时间序列就是连续的,但是事实经常不是如此,所以才有将稀疏数据稠密化的过程,数据稠密化在数据仓库应用中很常见。

例如以下报表,由于某些商品在某些月份没有销售,所以不会有记录。

postgres=# create table t_sales(year text, month text, prod name, sales int);  
CREATE TABLE  
postgres=# insert into t_sales values ('2008', '1', 'a', 1000);  
INSERT 0 1  
postgres=# insert into t_sales values ('2008', '1', 'b', 1500);  
INSERT 0 1  
postgres=# insert into t_sales values ('2008', '2', 'a', 2000);  
INSERT 0 1  
postgres=# insert into t_sales values ('2008', '2', 'b', 3000);  
INSERT 0 1  
postgres=# insert into t_sales values ('2008', '2', 'c', 1000);  
INSERT 0 1  
postgres=# insert into t_sales values ('2008', '3', 'a', 3000);  
INSERT 0 1  
  
  
postgres=# select * from t_sales;  
 year | month | prod | sales   
------+-------+------+-------  
 2008 | 1     | a    |  1000  
 2008 | 1     | b    |  1500  
 2008 | 2     | a    |  2000  
 2008 | 2     | b    |  3000  
 2008 | 2     | c    |  1000  
 2008 | 3     | a    |  3000  
(6 rows)  

Oracle 通过partition by outer join语法可以方便的实现稠化。

SELECT .....  
  
FROM table_reference  
  
PARTITION BY (expr [, expr ]... )  
  
RIGHT OUTER JOIN table_reference  
  
   
  
SELECT .....  
  
FROM table_reference  
  
LEFT OUTER JOIN table_reference  

达到类似这样的效果

postgres=# select * from continue_out();  
 year | month | prod | sales   
------+-------+------+-------  
 2008 | 2     | a    |  2000  
 2008 | 2     | b    |  3000  
 2008 | 2     | c    |  1000  
 2008 | 3     | a    |  3000  
 2008 | 3     | b    |     0  
 2008 | 3     | c    |     0  
 2008 | 1     | a    |  1000  
 2008 | 1     | b    |  1500  
 2008 | 1     | c    |     0  
(9 rows)  

PostgreSQL Partition By Outer Join实现

通过UDF,可以达到类似的效果:

create or replace function continue_out () returns setof t_sales as $$  
declare  
  v_year text;  
  v_month text;  
begin  
  -- 按年、月为分区 补齐prod  (select distinct year, month from t_sales)  
  -- 如果已有元表,则直接取元表  
  -- 如无元表,对于连续型数据,亦可以使用PG的generate_series直接生成。  
  for v_year, v_month in   
    select distinct year, month from t_sales    
  loop  
    -- 全量 prod  (select distinct prod from t_sales)   
    -- 如已有元表,则直接取元表  
    -- 如无元表,对于连续型数据,亦可以使用PG的generate_series直接生成。  
    return query   
      select   
        coalesce(t1.year,v_year),   
        coalesce(t1.month,v_month),   
        t2.prod,   
        coalesce(t1.sales, 0)   
      from   
      t_sales t1   
        right outer join   
      (select distinct prod from t_sales) t2   
        on (t1.prod=t2.prod and t1.year=v_year and t1.month=v_month);   
  end loop;    
end;  
$$ language plpgsql strict;  

如下

postgres=# select * from continue_out();  
 year | month | prod | sales   
------+-------+------+-------  
 2008 | 2     | a    |  2000  
 2008 | 2     | b    |  3000  
 2008 | 2     | c    |  1000  
 2008 | 3     | a    |  3000  
 2008 | 3     | b    |     0  
 2008 | 3     | c    |     0  
 2008 | 1     | a    |  1000  
 2008 | 1     | b    |  1500  
 2008 | 1     | c    |     0  
(9 rows)  

通过窗口,可以实现同比,从而实现快速的搜索到销量逆增长的商品。

postgres=# select *, sales - coalesce(lag(sales) over w1, sales)  from continue_out() window w1 as (partition by prod order by year,month);  
 year | month | prod | sales | ?column?   
------+-------+------+-------+----------  
 2008 | 1     | a    |  1000 |        0  
 2008 | 2     | a    |  2000 |     1000  
 2008 | 3     | a    |  3000 |     1000  
 2008 | 1     | b    |  1500 |        0  
 2008 | 2     | b    |  3000 |     1500  
 2008 | 3     | b    |     0 |    -3000  
 2008 | 1     | c    |     0 |        0  
 2008 | 2     | c    |  1000 |     1000  
 2008 | 3     | c    |     0 |    -1000  
(9 rows)  

详细执行计划如下:

postgres=# load 'auto_explain';
LOAD
Time: 22.171 ms
postgres=# set auto_explain.
auto_explain.log_analyze            auto_explain.log_format             auto_explain.log_nested_statements  auto_explain.log_triggers           auto_explain.sample_rate            
auto_explain.log_buffers            auto_explain.log_min_duration       auto_explain.log_timing             auto_explain.log_verbose            
postgres=# set auto_explain.log_analyze =on;
SET
Time: 0.272 ms
postgres=# set auto_explain.log_buffers =on;
SET
Time: 0.257 ms
postgres=# set auto_explain.log_min_duration =0;
SET
Time: 0.279 ms
postgres=# set auto_explain.log_nested_statements =on;
SET
Time: 0.262 ms
postgres=# set auto_explain.log_timing =on;
SET
Time: 0.269 ms
postgres=# set auto_explain.log_verbose =on;
SET
Time: 0.245 ms
postgres=# set client_min_messages ='log';
SET
Time: 0.265 ms

postgres=# select *, sales - coalesce(lag(sales) over w1, sales)  from continue_out() window w1 as (partition by prod order by year,month);
LOG:  duration: 0.030 ms  plan:
Query Text: select coalesce(t1.year,v_year), coalesce(t1.month,v_month), t2.prod, coalesce(t1.sales, 0) from t_sales t1 
    right outer join 
  (select distinct prod from t_sales) t2 
  on (t1.prod=t2.prod and t1.year=v_year and t1.month=v_month)
Hash Left Join  (cost=34.04..38.80 rows=200 width=132) (actual time=0.024..0.027 rows=3 loops=1)
  Output: COALESCE(t1.year, $2), COALESCE(t1.month, $3), t_sales.prod, COALESCE(t1.sales, 0)
  Hash Cond: (t_sales.prod = t1.prod)
  Buffers: shared hit=2
  ->  HashAggregate  (cost=16.38..18.38 rows=200 width=64) (actual time=0.009..0.009 rows=3 loops=1)
        Output: t_sales.prod
        Group Key: t_sales.prod
        Buffers: shared hit=1
        ->  Seq Scan on public.t_sales  (cost=0.00..15.10 rows=510 width=64) (actual time=0.002..0.003 rows=6 loops=1)
              Output: t_sales.year, t_sales.month, t_sales.prod, t_sales.sales
              Buffers: shared hit=1
  ->  Hash  (cost=17.65..17.65 rows=1 width=132) (actual time=0.007..0.007 rows=3 loops=1)
        Output: t1.year, t1.month, t1.sales, t1.prod
        Buckets: 1024  Batches: 1  Memory Usage: 9kB
        Buffers: shared hit=1
        ->  Seq Scan on public.t_sales t1  (cost=0.00..17.65 rows=1 width=132) (actual time=0.003..0.004 rows=3 loops=1)
              Output: t1.year, t1.month, t1.sales, t1.prod
              Filter: ((t1.year = $2) AND (t1.month = $3))
              Rows Removed by Filter: 3
              Buffers: shared hit=1
LOG:  duration: 0.020 ms  plan:
Query Text: select coalesce(t1.year,v_year), coalesce(t1.month,v_month), t2.prod, coalesce(t1.sales, 0) from t_sales t1 
    right outer join 
  (select distinct prod from t_sales) t2 
  on (t1.prod=t2.prod and t1.year=v_year and t1.month=v_month)
Hash Left Join  (cost=34.04..38.80 rows=200 width=132) (actual time=0.016..0.018 rows=3 loops=1)
  Output: COALESCE(t1.year, $2), COALESCE(t1.month, $3), t_sales.prod, COALESCE(t1.sales, 0)
  Hash Cond: (t_sales.prod = t1.prod)
  Buffers: shared hit=2
  ->  HashAggregate  (cost=16.38..18.38 rows=200 width=64) (actual time=0.008..0.009 rows=3 loops=1)
        Output: t_sales.prod
        Group Key: t_sales.prod
        Buffers: shared hit=1
        ->  Seq Scan on public.t_sales  (cost=0.00..15.10 rows=510 width=64) (actual time=0.003..0.004 rows=6 loops=1)
              Output: t_sales.year, t_sales.month, t_sales.prod, t_sales.sales
              Buffers: shared hit=1
  ->  Hash  (cost=17.65..17.65 rows=1 width=132) (actual time=0.004..0.004 rows=1 loops=1)
        Output: t1.year, t1.month, t1.sales, t1.prod
        Buckets: 1024  Batches: 1  Memory Usage: 9kB
        Buffers: shared hit=1
        ->  Seq Scan on public.t_sales t1  (cost=0.00..17.65 rows=1 width=132) (actual time=0.002..0.003 rows=1 loops=1)
              Output: t1.year, t1.month, t1.sales, t1.prod
              Filter: ((t1.year = $2) AND (t1.month = $3))
              Rows Removed by Filter: 5
              Buffers: shared hit=1
LOG:  duration: 0.019 ms  plan:
Query Text: select coalesce(t1.year,v_year), coalesce(t1.month,v_month), t2.prod, coalesce(t1.sales, 0) from t_sales t1 
    right outer join 
  (select distinct prod from t_sales) t2 
  on (t1.prod=t2.prod and t1.year=v_year and t1.month=v_month)
Hash Left Join  (cost=34.04..38.80 rows=200 width=132) (actual time=0.014..0.017 rows=3 loops=1)
  Output: COALESCE(t1.year, $2), COALESCE(t1.month, $3), t_sales.prod, COALESCE(t1.sales, 0)
  Hash Cond: (t_sales.prod = t1.prod)
  Buffers: shared hit=2
  ->  HashAggregate  (cost=16.38..18.38 rows=200 width=64) (actual time=0.007..0.008 rows=3 loops=1)
        Output: t_sales.prod
        Group Key: t_sales.prod
        Buffers: shared hit=1
        ->  Seq Scan on public.t_sales  (cost=0.00..15.10 rows=510 width=64) (actual time=0.002..0.003 rows=6 loops=1)
              Output: t_sales.year, t_sales.month, t_sales.prod, t_sales.sales
              Buffers: shared hit=1
  ->  Hash  (cost=17.65..17.65 rows=1 width=132) (actual time=0.004..0.004 rows=2 loops=1)
        Output: t1.year, t1.month, t1.sales, t1.prod
        Buckets: 1024  Batches: 1  Memory Usage: 9kB
        Buffers: shared hit=1
        ->  Seq Scan on public.t_sales t1  (cost=0.00..17.65 rows=1 width=132) (actual time=0.001..0.003 rows=2 loops=1)
              Output: t1.year, t1.month, t1.sales, t1.prod
              Filter: ((t1.year = $2) AND (t1.month = $3))
              Rows Removed by Filter: 4
              Buffers: shared hit=1
LOG:  duration: 0.045 ms  plan:
Query Text: select distinct year, month from t_sales
HashAggregate  (cost=17.65..19.65 rows=200 width=64) (actual time=0.040..0.041 rows=3 loops=1)
  Output: year, month
  Group Key: t_sales.year, t_sales.month
  Buffers: shared hit=1
  ->  Seq Scan on public.t_sales  (cost=0.00..15.10 rows=510 width=64) (actual time=0.028..0.029 rows=6 loops=1)
        Output: year, month, prod, sales
        Buffers: shared hit=1
LOG:  duration: 0.832 ms  plan:
Query Text: select *, sales - coalesce(lag(sales) over w1, sales)  from continue_out() window w1 as (partition by prod order by year,month);
WindowAgg  (cost=60.08..85.08 rows=1000 width=136) (actual time=0.808..0.818 rows=9 loops=1)
  Output: year, month, prod, sales, (sales - COALESCE(lag(sales) OVER (?), sales))
  Buffers: shared hit=9
  ->  Sort  (cost=60.08..62.58 rows=1000 width=132) (actual time=0.803..0.804 rows=9 loops=1)
        Output: year, month, prod, sales
        Sort Key: continue_out.prod, continue_out.year, continue_out.month
        Sort Method: quicksort  Memory: 26kB
        Buffers: shared hit=9
        ->  Function Scan on public.continue_out  (cost=0.25..10.25 rows=1000 width=132) (actual time=0.782..0.783 rows=9 loops=1)
              Output: year, month, prod, sales
              Function Call: continue_out()
              Buffers: shared hit=9
 year | month | prod | sales | ?column? 
------+-------+------+-------+----------
 2008 | 1     | a    |  1000 |        0
 2008 | 2     | a    |  2000 |     1000
 2008 | 3     | a    |  3000 |     1000
 2008 | 1     | b    |  1500 |        0
 2008 | 2     | b    |  3000 |     1500
 2008 | 3     | b    |     0 |    -3000
 2008 | 1     | c    |     0 |        0
 2008 | 2     | c    |  1000 |     1000
 2008 | 3     | c    |     0 |    -1000
(9 rows)

Time: 1.362 ms

PostgreSQL的UDF非常强大,可以实现很多功能。

当然也期待PostgreSQL直接将partition by语法引入,那就更加方便了。

参考

https://www.postgresql.org/docs/10/static/functions-window.html

https://www.postgresql.org/docs/10/static/plpgsql.html

http://blog.sina.com.cn/s/blog_4cef5c7b01016lm5.html

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
7月前
|
SQL Oracle 关系型数据库
避坑,Oracle中left join 与 (+) 的区别
避坑,Oracle中left join 与 (+) 的区别
|
7月前
|
关系型数据库 分布式数据库 数据库
PolarDB PostgreSQL版:Oracle兼容的高性能数据库
PolarDB PostgreSQL版是一款高性能的数据库,具有与Oracle兼容的特性。它采用了分布式架构,可以轻松处理大量的数据,同时还支持多种数据类型和函数,具有高可用性和可扩展性。它还提供了丰富的管理工具和性能优化功能,为企业提供了可靠的数据存储和处理解决方案。PolarDB PostgreSQL版在数据库领域具有很高的竞争力,可以满足各种企业的需求。
|
3月前
|
Oracle NoSQL 关系型数据库
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
557 2
|
7月前
|
人工智能 Oracle 关系型数据库
一篇文章弄懂Oracle和PostgreSQL的Database Link
一篇文章弄懂Oracle和PostgreSQL的Database Link
|
7月前
|
SQL Oracle 关系型数据库
常用数据库的分页语句(mySQL、oracle、PostgreSQL、SQL Server)
常用数据库的分页语句(mySQL、oracle、PostgreSQL、SQL Server)
|
7月前
|
SQL Oracle 关系型数据库
Oracle查询优化-left join、right join、inner join、full join和逗号的区别
【1月更文挑战第5天】【1月更文挑战第13篇】实际查询时,多表联查是常规操作,但是连接方式有多种。
598 0
|
SQL Oracle 关系型数据库
Oracle,Postgresql等数据库使用
Oracle,Postgresql等数据库简单使用
172 0
Oracle,Postgresql等数据库使用
|
SQL Oracle 关系型数据库
Polar DB-O (兼容 Oracle 语法版本)和Polar DB PostgreSQL 版本概述(二)
Polar DB-O (兼容 Oracle 语法版本)和Polar DB PostgreSQL 版本概述(二)
1832 0
|
7月前
|
存储 Oracle 关系型数据库
PolarDB 开源版通过orafce支持Oracle兼容性
背景PolarDB 的云原生存算分离架构, 具备低廉的数据存储、高效扩展弹性、高速多机并行计算能力、高速数据搜索和处理; PolarDB与计算算法结合, 将实现双剑合璧, 推动业务数据的价值产出, 将数据变成生产力.本文将介绍PolarDB开源版通过orafce支持Oracle兼容性 .测试环境为m...
137 0
|
SQL Cloud Native 关系型数据库
ADBPG(AnalyticDB for PostgreSQL)是阿里云提供的一种云原生的大数据分析型数据库
ADBPG(AnalyticDB for PostgreSQL)是阿里云提供的一种云原生的大数据分析型数据库
1295 1

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版
  • 推荐镜像

    更多