null クラスを持つ任意のレコードでクロス積を返しています。あなたの結果はこれでよろしいですか?
11gR2 で 2 つのサンプル クエリを作成しました。
WITH a as
(select NULL as class, 5 as columna from dual
UNION
select NULL as class, 7 as columna from dual
UNION
select NULL as class, 9 as columna from dual
UNION
select 'X' as class, 3 as columna from dual
UNION
select 'Y' as class, 2 as columna from dual),
b as
(select NULL as class, 2 as columnb from dual
UNION
select NULL as class, 15 as columnb from dual
UNION
select NULL as class, 5 as columnb from dual
UNION
select 'X' as class, 7 as columnb from dual
UNION
select 'Y' as class, 9 as columnb from dual)
SELECT * from a JOIN b ON (a.class = b.class
OR (a.class is null AND b.class is null))
このクエリで EXPLAIN PLAN を実行すると、テーブル (私の場合はインライン ビュー) が NESTED LOOPS を介して結合されていることが示されます。NESTED LOOPS 結合は、一方のテーブルの最初の行をスキャンし、次に他方のテーブルの各行をスキャンして一致するかどうかを調べ、次に最初のテーブルの 2 行目をスキャンし、2 番目のテーブルで一致を探す、というように動作します。直接比較していないためです。 JOIN の OR 部分にいずれかのテーブルがある場合、オプティマイザーは NESTED LOOPS を使用する必要があります。
舞台裏では次のようになります。
- テーブル A の行 1 を取得します。クラスが null の場合、テーブル A のこの行を結果セットに含めます。
- テーブル A の行 1 にいる間に、クラスが null であるすべての行をテーブル B で検索します。
- テーブル A の行 1 とテーブル B のすべての行でクロス積を実行します。
- これらの行を結果セットに含めます
- テーブル A の行 2 を取得します。クラスが null の場合、テーブル A のこの行を結果セットに含めます。
- ....など
SELECT ステートメントを に変更するとSELECT * FROM a JOIN b ON NVL(a.class, 'N/A') = NVL(b.class, 'N/A')
、EXPLAIN は HASH JOIN が使用されていることを示します。ハッシュ結合は、基本的に小さいテーブルの各結合キーのハッシュを生成し、次に大きいテーブルをスキャンして、一致する各行の小さいテーブルのハッシュを見つけます。この場合、これは単純な Equijoin であるため、オプティマイザーは駆動テーブルの各行を問題なくハッシュできます。
舞台裏では次のようになります。
- テーブル A を調べて、NULL クラス値を「N/A」に変換します
- テーブル A の各行をハッシュ化します。
- ハッシュ テーブル A は現在、一時領域またはメモリにあります。
- テーブル B をスキャンし、NULL クラス値を「N/A」に変換してから、値のハッシュを計算します。ハッシュ テーブルのルックアップ ハッシュが存在する場合は、結果セットにテーブル A および B から結合された行を含めます。
- B のスキャンを続行します。
クエリに対して EXPLAIN PLAN を実行すると、おそらく同様の結果が得られます。
最終結果は同じですが、最初のクエリで「OR」を使用してテーブルを結合していないため、オプティマイザーはより良い結合方法を使用できません。駆動テーブルが大きい場合、または大きなセカンダリ テーブルに対してフル テーブル スキャンを強制している場合、NESTED LOOPS は非常に遅くなる可能性があります。
ANSI 関数を使用してCOALESCE
、他のデータベース システムで NVL oracle 関数をエミュレートできます。ここでの本当の問題は、NULL 値に参加しようとしているということです。実際には、「NO CLASS」または null = unknown の代わりに null = nothing という意味で「null」クラスを識別する他の方法が必要です。 .
コメントであなたの質問に答える補遺:
null クエリの場合、SQL エンジンは次のことを行います。
- テーブル A から行 1 を読み取ります。クラスは null です。「N/A」に変換します。
- テーブル B には、クラスが null である 3 つの行があり、各 null を「N/A」に変換します。
- 最初の行は 3 行すべてに一致するため、A1B1、A1B2、A1B3 の 1 行の 3 行が結果セットに追加されます。
- テーブル A から行 2 を読み取ります。クラスは null です。「N/A」に変換します。
- テーブル B には、クラスが null である 3 つの行があり、各 null を「N/A」に変換します。
- 2 行目は 3 行すべてに一致するため、A2B1、A2B2、A2B3 の 1 行である 3 行が結果セットに追加されます。
- テーブル A から行 3 を読み取ります。クラスは null です。「N/A」に変換します。
- テーブル B には、クラスが null である 3 つの行があり、各 null を「N/A」に変換します。
- 3 行目は 3 行すべてに一致するため、A3B1、A3B2、A3B3 の 1 行の 3 行が結果セットに追加されます。10. 行 4 と 5 は null ではないため、結合のこの部分では処理されません。
「N/A」クエリの場合、SQL エンジンは次のことを行います。
- テーブル A から行 1 を読み取ります。クラスは null です。「N/A」に変換し、この値をハッシュします。
- テーブル A から行 2 を読み取ります。クラスは null です。「N/A」に変換し、この値をハッシュします。
- テーブル A から行 3 を読み取ります。クラスは null です。「N/A」に変換し、この値をハッシュします。
- テーブル A から行 4 を読み取り、クラスは null ではなく、この値をハッシュします。
- テーブル A から行 5 を読み取り、クラスは null ではなく、この値をハッシュします。
- ハッシュ テーブル C がメモリに格納されます。
- テーブル B から行 1 を読み取り、クラスは null であり、「N/A」に変換し、値をハッシュします。
- ハッシュ値をメモリ内のハッシュ テーブルと比較し、一致ごとに結果セットに行を追加します。A1、A2、A3 の 3 つの行が見つかりました。結果は、A1B1、A2B1、A3B1 に追加されます。
- テーブル B から行 2 を読み取り、クラスは null であり、「N/A」に変換し、値をハッシュします。
- ハッシュ値をメモリ内のハッシュ テーブルと比較し、一致ごとに結果セットに行を追加します。A1、A2、A3 の 3 つの行が見つかりました。結果は、A1B2、A2B2、A3B2 に追加されます。
- テーブル B から行 3 を読み取ります。クラスは null です。「N/A」に変換し、値をハッシュします。
- ハッシュ値をメモリ内のハッシュ テーブルと比較し、一致ごとに結果セットに行を追加します。A1、A2、A3 の 3 つの行が見つかりました。結果は、A1B3、A2B3、A3B3 に追加されます。