Oracleデータベースで2つのクエリのUNION
を実行しています。どちらにもWHERE
句があります。 WHERE
句の後にUNION
を実行した場合とUNION
句の後にWHERE
を実行した場合のパフォーマンスに違いはありますか?
例えば:
SELECT colA, colB FROM tableA WHERE colA > 1
UNION
SELECT colA, colB FROM tableB WHERE colA > 1
に比べ:
SELECT *
FROM (SELECT colA, colB FROM tableA
UNION
SELECT colA, colB FROM tableB)
WHERE colA > 1
2番目のケースでは、パフォーマンスに影響する両方のテーブルで全テーブルスキャンを実行すると考えています。あれは正しいですか?
私の経験では、Oracleはsimple述語をプッシュするのが得意です。次のテストは、Oracle 11.2で行われました。 10gのすべてのリリースでも同じ実行計画が生成されると確信しています。
(以前のバージョンを実行して次のことを試みた場合は、コメントを残してください)
create table table1(a number, b number);
create table table2(a number, b number);
explain plan for
select *
from (select a,b from table1
union
select a,b from table2
)
where a > 1;
select *
from table(dbms_xplan.display(format=>'basic +predicate'));
PLAN_TABLE_OUTPUT
---------------------------------------
| Id | Operation | Name |
---------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | VIEW | |
| 2 | SORT UNIQUE | |
| 3 | UNION-ALL | |
|* 4 | TABLE ACCESS FULL| TABLE1 |
|* 5 | TABLE ACCESS FULL| TABLE2 |
---------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("A">1)
5 - filter("A">1)
手順(4,5)でわかるように、述語はプッシュダウンされ、並べ替え(結合)の前に適用されます。
次のようなサブクエリ全体をオプティマイザにプッシュダウンさせることができませんでした
where a = (select max(a) from empty_table)
または結合。適切なPK/FK制約が適切に設定されていれば可能かもしれませんが、明らかに制限があります:)
ただの注意
試したなら
SELECT colA, colB FROM tableA WHERE colA > 1
UNION
SELECT colX, colA FROM tableB WHERE colA > 1
に比べ:
SELECT *
FROM (SELECT colA, colB FROM tableA
UNION
SELECT colX, colA FROM tableB)
WHERE colA > 1
次に、2番目のクエリでは、where句のcolAに実際にはtableBのcolXが含まれるため、まったく異なるクエリになります。列がこの方法でエイリアスされている場合、混乱する可能性があります。
注:私のアドバイスは何年も前に真実でしたが、Oracleのオプティマイザーは改善されており、ここでの場所の位置はもはや重要ではありません。ただし、UNION ALL
vs UNION
、およびポータブルSQLは、すべてのデータベースにはない可能性のある最適化に依存しないようにする必要があります。
簡単な答えは、WHERE
の前にUNION
が必要であり、可能な場合はUNION ALL
を使用することです。 UNION ALL
を使用している場合は、EXPLAIN出力を確認します。Oracleは、WHERE
条件が残っている場合に最適化するのに十分なほど賢い場合があります。
その理由は次のとおりです。 UNION
の定義は、2つのデータセットに重複がある場合、それらを削除する必要があることを示しています。したがって、その操作には暗黙的にGROUP BY
があり、これは遅くなる傾向があります。さらに悪いことに、Oracleのオプティマイザー(少なくとも3年前、および変更されたとは思わない)は、GROUP BY
(暗黙的または明示的)を介して条件をプッシュしようとしません。そのため、Oracleは必要以上に大きなデータセットを作成し、グループ化して、フィルター処理を行う必要があります。したがって、可能な限り事前フィルタリングすることは公式には良いアイデアです。 (これは、偶然、WHERE
句に条件を残すのではなく、可能な限りHAVING
に条件を入れることが重要な理由です。)
さらに、2つのデータセットの間に重複がないことがわかった場合は、UNION ALL
を使用します。これは、データセットを連結するという点でUNION
に似ていますが、データの重複排除を試みません。これにより、費用のかかるグループ化操作が節約されます。私の経験では、この操作を利用できることは非常に一般的です。
UNION ALL
には暗黙のGROUP BY
がないため、Oracleのオプティマイザーが条件をプッシュする方法を知っている可能性があります。テストするためにOracleが座っているわけではないので、自分でテストする必要があります。
EXPLAIN PLANを確認する必要がありますが、COL_AにINDEXまたはPARTITIONがない場合は、両方の表のFULL TABLE SCANを確認しています。
これを念頭に置いて、最初の例は、FULL TABLE SCANを実行するときにデータの一部を破棄することです。その結果はUNIONによってソートされ、重複データはドロップされます。これにより、結果セットが得られます。
2番目の例では、両方のテーブルの内容全体をプルしています。その結果は大きくなる可能性があります。そのため、UNIONはより多くのデータをソートしてから、重複するものを削除しています。次に、フィルタを適用して、目的の結果セットを提供します。
一般的なルールとして、データをより早くフィルタ除去するほど、データセットは小さくなり、結果をより速く得ることができます。いつものように、あなたの走行距離は異なる場合があります。
ColAにインデックスがあることを確認してから、両方を実行して時間を計ります。それはあなたに最高の答えを与えるでしょう。
私はそれが多くのものに依存すると思う-EXPLAIN PLAN
各オプティマイザーが選択するものを確認します。それ以外の場合-@raymanが示唆するように-両方を実行し、時間を計ります。
SELECT * FROM (SELECT colA, colB FROM tableA UNION SELECT colA, colB FROM tableB) as tableC WHERE tableC.colA > 1
2つのテーブルに同じフィールド名を含むユニオンを使用している場合、サブクエリにtableC(上記のクエリ)として名前を付ける必要があります。最後に、WHERE
条件はWHERE tableC.colA > 1