web-dev-qa-db-ja.com

PostgreSQLがOUTERJOINクエリを書き換えないようにするにはどうすればよいですか?

私の質問は:

SELECT  Acol1, Acol2, Bcol1, Bcol2, Ccol1, Ccol2
FROM    tableA LEFT JOIN
            (tableB FULL JOIN tableC ON (Bcol1 = Ccol1))
            ON (Acol1 = Bcol1)

EXPLAIN ANALYZE私に与える:

                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Hash Right Join  (cost=99.65..180.45 rows=1770 width=24) (actual time=0.043..0.103 rows=3 loops=1)
   Hash Cond: (tableb.bcol1 = tablea.acol1)
   ->  Hash Left Join  (cost=49.83..104.08 rows=1770 width=16) (actual time=0.011..0.062 rows=3 loops=1)
         Hash Cond: (tableb.bcol1 = tablec.ccol1)
         ->  Seq Scan on tableb  (cost=0.00..27.70 rows=1770 width=8) (actual time=0.001..0.002 rows=3 loops=1)
         ->  Hash  (cost=27.70..27.70 rows=1770 width=8) (actual time=0.004..0.004 rows=3 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 1kB
               ->  Seq Scan on tablec  (cost=0.00..27.70 rows=1770 width=8) (actual time=0.001..0.002 rows=3 loops=1)
   ->  Hash  (cost=27.70..27.70 rows=1770 width=8) (actual time=0.014..0.014 rows=3 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 1kB
         ->  Seq Scan on tablea  (cost=0.00..27.70 rows=1770 width=8) (actual time=0.009..0.011 rows=3 loops=1)
 Total runtime: 0.151 ms

PostgresはfulltableBtableCの間の外部結合をright outerjoin。後でtableAを使用した左側のouterjoinはとにかくnull値を削除するためです。元のクエリと同等です。

ただし、Postgresをハッキングして、結合列挙関連のアルゴリズムを実装し、実験を行っています。 Postgresが完全なouterjoinを左のouterjoinに変更することを望まない。そうする方法はありますか?

2
Doris W.

目的に応じて最適化バリアを導入できます。

序文

この質問の目的には、プレーンなEXPLAINANALYZEなし)で十分です。 ON式を囲む括弧は単なるノイズです。明確にするためにテーブルエイリアスを追加します。

完全結合自体については、「完全結合」が表示されます。

SELECT * FROM tableB b FULL JOIN tableC c ON b.Bcol1 = c.Ccol1;

サブクエリを使用して完全結合を書き換えることができます。

SELECT a.Acol1, a.Acol2, d.Bcol1, d.Bcol2, d.Ccol1, d.Ccol2
FROM   tableA a
LEFT   JOIN (
   SELECT *  -- sort out conflicting names with aliases
   FROM   tableB b FULL JOIN tableC c ON b.Bcol1 = c.Ccol1
   ) d ON a.Acol1 = d.Bcol1;

サブクエリでエイリアスと競合する名前を整理する必要がありますが、その場合も、基になるテーブルの同じ名前の複数の列の外側のSELECTでこれを行う必要があります。

サブクエリは最適化の障壁を課さないため、クエリは全体として最適化されます。 「左結合」または「右結合」が引き続き表示されます)。ただし、このフォームを拡張して解決策を見つけることができます。

解決策1.OFFSET 0ハック(文書化されていない)

EXPLAIN
SELECT a.Acol1, a.Acol2, d.Bcol1, d.Bcol2, d.Ccol1, d.Ccol2
FROM   tableA a
LEFT   JOIN (
   SELECT *  -- you'll have to sort out conflicting names with aliases
   FROM   tableB b FULL JOIN tableC c ON b.Bcol1 = c.Ccol1
   OFFSET 0  -- undocumented hack
   ) d ON a.Acol1 = d.Bcol1;

「完全結合」が表示されます。

なぜですか?サブクエリがOFFSET句を使用するとすぐに、クエリプランナー/オプティマイザーはサブクエリを個別に計画します。 OFFSET 0は論理的なノイズですが、Postgresは、これをクエリのヒントにしてサブクエリを効果的に実現する句を考慮しています。 (Postgresは他の方法ではクエリヒントをサポートしていませんが。)これは多くの議論の的となっている問題です。関連:

解決策2.CTEを使用する(文書化)

EXPLAIN
WITH cte AS (
   SELECT *  -- you'll have to sort out conflicting names with aliases
   FROM   tableB b FULL JOIN tableC c ON b.Bcol1 = c.Ccol1
   ) 
SELECT a.Acol1, a.Acol2, d.Bcol1, d.Bcol2, d.Ccol1, d.Ccol2
FROM   tableA a
LEFT   JOIN cte d ON a.Acol1 = d.Bcol1;

「完全結合」も表示されます。

マニュアル:

WITHクエリの便利なプロパティは、親クエリまたは兄弟のWITHクエリによって複数回参照されている場合でも、親クエリの実行ごとに1回だけ評価されることです。したがって、複数の場所で必要となる高価な計算をWITHクエリ内に配置して、冗長な作業を回避できます。別の可能なアプリケーションは、副作用のある関数の不要な複数の評価を防ぐことです。ただし、このコインの反対側は、オプティマイザーが通常のサブクエリよりも親クエリからWITHクエリに制限をプッシュすることができないことです。 WITHクエリは通常、親クエリが後で破棄する可能性のある行を抑制せずに、書き込まれたものとして評価されます。(ただし、前述のとおり上記では、クエリへの参照が限られた数の行のみを要求する場合、評価が早期に停止する可能性があります。)

大胆な強調鉱山。

SQLフィドル。 (Postgres 9.3の場合、それ以降のバージョンはまだ利用できません)

2

コードを修正してみてください

SELECT  Acol1, Acol2, K.D.Bcol1, K.D.Bcol2, K.D.Ccol1, K.D.Ccol2  
FROM tableA LEFT JOIN  
( SELECT  D.Bcol1, D.Bcol2, D.Ccol1, D.Ccol2  
  FROM (tableB FULL JOIN   
 tableC ON  
Bcol1 = Ccol1 ) D ) K  
ON Acol1 = K.D.Bcol1    

K.D.を使用していますデータフローを示します。