web-dev-qa-db-ja.com

「PARTITIONED OUTER JOIN」とは何ですか?

これはRedditに関する質問で出てきたばかりで、

  • OracleのPARTITIONED OUTER JOINとは何ですか? (定義)
  • 簡単な例はどのようなものですか? (使用する)
  • それ以外の場合にPARTITIONED OUTER JOINを欠くPostgreSQLまたは標準SQLの観点から、それをどのように記述しますか? (等価)
2
Evan Carroll

{1}パーティション化された外部結合:定義

... "このような結合は、クエリで定義された各論理パーティションに外部結合を適用することにより、従来の外部結合構文を拡張します。Oracleは、PARTITION BY句で指定した式に基づいて、クエリの行を論理的に分割します。パーティション化された外部結合は、論理的にパーティション化されたテーブル内の各パーティションの外部結合と、結合の反対側にあるテーブルとのUNIONです。」 ( ドキュメント

{2}簡単な例

「データは通常スパースな形式で格納されます。つまり、特定のディメンション値の組み合わせに値が存在しない場合、ファクトテーブルには行が存在しません。ただし、データを密に表示したい場合があります。フォーム。ファクトデータが存在しない場合でも、ディメンション値のすべての組み合わせの行が表示されます

...「たとえば、特定の期間中に製品が販売されなかった場合でも、その期間の製品の横に売上高がゼロの製品を表示したい場合があります。」 ( documentation からの引用)

テストテーブルとデータ(INSERT)

-- Oracle 12c
create table sales (
  date_ date
, location_ varchar2( 16 )
, qty_ number
);

create table locations (
  name varchar2( 16 )
);

-- dates for locations are "gappy": 
-- none of the locations has entries for all 3 dates
-- ( date range: 2019-01-15 - 2019-01-17 )
insert into sales ( date_, location_, qty_ ) 
  values ( date '2019-01-17', 'London', 11 ) ;
insert into sales ( date_, location_, qty_ ) 
  values ( date '2019-01-15', 'London', 10 ) ;
insert into sales ( date_, location_, qty_ ) 
  values ( date '2019-01-16', 'Paris', 20 ) ;
insert into sales ( date_, location_, qty_ ) 
  values ( date '2019-01-17', 'Boston', 31 ) ;
insert into sales ( date_, location_, qty_ ) 
  values ( date '2019-01-16', 'Boston', 30 ) ;

-- locations
insert into locations ( name ) values ( 'London' );
insert into locations ( name ) values ( 'Paris' );
insert into locations ( name ) values ( 'Boston' );

必要な出力

date_       location_  qty_
2019-01-15  London     10
2019-01-15  Paris       0    -- not INSERTed!
2019-01-15  Boston      0    -- not INSERTed!
2019-01-16  London      0    -- not INSERTed!
2019-01-16  Paris      20
2019-01-16  Boston     30
2019-01-17  London     11
2019-01-17  Paris       0    -- not INSERTed!
2019-01-17  Boston     31

クエリ(パーティション化された外部結合)

select S.date_, S.qty_, L.name
from sales S partition by ( date_ ) 
  right join locations L on S.location_ = L.name
;

-- result
DATE_      QTY_  NAME    
15-JAN-19  NULL  Boston  
15-JAN-19  10    London  
15-JAN-19  NULL  Paris   
16-JAN-19  30    Boston  
16-JAN-19  NULL  London  
16-JAN-19  20    Paris   
17-JAN-19  31    Boston  
17-JAN-19  11    London  
17-JAN-19  NULL  Paris 

クエリ(バージョン2、同じ結合)

-- same as above, using NVL(), column aliases, and ORDER BY ...
select S.date_, nvl( S.qty_, 0 ) as sold, L.name as location
from sales S partition by ( date_ ) 
  right join locations L on S.location_ = L.name
order by S.date_, L.name
;

DATE_           SOLD LOCATION        
--------- ---------- ----------------
15-JAN-19          0 Boston          
15-JAN-19         10 London          
15-JAN-19          0 Paris           
16-JAN-19         30 Boston          
16-JAN-19          0 London          
16-JAN-19         20 Paris           
17-JAN-19         31 Boston          
17-JAN-19         11 London          
17-JAN-19          0 Paris           

Dbfiddle here(Oracle 18c)

{3}同等

次のクエリは、PARTITION BY(date_)外部結合とほぼ同じ働きをします。 CROSS JOIN(内部SELECT)とLEFT OUTER JOINの組み合わせを使用しています。 (NULLから0への変換は省略)

オラクル

select SL.*, S.qty_
from
(
  select *
  from (
    select unique date_ from sales
  ) , (
    select unique name from locations
  )
) SL left join (
  select date_, location_, qty_ from sales
) S on SL.name = S.location_ and SL.date_ = S.date_ 
order by SL.date_, SL.name
;

DATE_      NAME    QTY_  
15-JAN-19  Boston  NULL  
15-JAN-19  London  10    
15-JAN-19  Paris   NULL  
16-JAN-19  Boston  30    
16-JAN-19  London  NULL  
16-JAN-19  Paris   20    
17-JAN-19  Boston  31    
17-JAN-19  London  11    
17-JAN-19  Paris   NULL 

PostgreSQL 10( dbfiddle

-- DDL and INSERTs
create table sales (
  date_ date
, location_ varchar( 16 )
, qty_ number
);

create table locations (
  name varchar( 16 )
);

insert into sales ( date_, location_, qty_ ) values 
  ( '2019-01-17', 'London', 11 )
, ( '2019-01-15', 'London', 10 )
, ( '2019-01-16', 'Paris', 20 )
, ( '2019-01-17', 'Boston', 31 )
, ( '2019-01-16', 'Boston', 30 )

insert into locations ( name ) values 
( 'London' ), ( 'Paris' ), ( 'Boston' );

クエリ(Postgres)

-- SL: all date_ <-> location combinations
-- S: all location_ and qty_ values of table sales
select SL.*, S.qty_
from
(
  select *
  from (
    select distinct date_ from sales
  ) S_ cross join (
    select distinct name from locations
  ) L_
) SL left join (
  select date_, location_, qty_ from sales
) S on SL.name = S.location_ and SL.date_ = S.date_ 
order by SL.date_, SL.name
;

date_        name    qty_
2019-01-15   Boston  
2019-01-15   London  10
2019-01-15   Paris    
2019-01-16   Boston  30
2019-01-16   London    
2019-01-16   Paris   20
2019-01-17   Boston  31
2019-01-17   London  11
2019-01-17   Paris    
6
stefan