外部結合が内部結合であるのはどんなとき
ダジャレを使った英語のなぞなぞに、このようなものがあります。「When is a boy not a boy?(男の子が男の子でないのはどんなとき?) When he's abed!(寝床にいるとき、彼がベッド(a bed)であるとき)」 「When is a door not a door?(ドアがドアでないのはどんなとき?) When it's ajar!(半開きのとき、広口瓶(a jar)であるとき)」 では、これはどうでしょう。「When is an outer join not an outer join?(外部結合が外部結合でないのはどんなとき?)」 失礼しました。これはダジャレなぞなぞではありませんでした。1つの段落にダジャレなぞなぞ3つは多過ぎのようです。それでは、最後のなぞなぞの答えについて見て行きましょう。
作業中、実際には外部結合ではなく内部結合である、というような外部結合を目にすることがよくあります。IBMから聞いたところによれば、照会エンジンは、それらを外部結合として処理するかもしれませんが、結果セットは、内部結合によって生成されるものと同じだということです。このようなことが起こるのは、照会が、2次表またはビュー(データベース ファイル)からの列(フィールド)でNULLでない値をテストする場合です。
サンプル コードを示す前に、まずは、データから見て行きましょう。以下は、インボイス ヘッダー表です。
INVOICE | COMPANY | CUSTNBR | INVDATE |
---|---|---|---|
101 | 1 | 50 | 2021-02-05 |
102 | 1 | 33 | 2021-02-05 |
103 | 2 | 70 | 2021-02-06 |
104 | 2 | 53 | 2021-02-06 |
以下は、対応する詳細表です。
INVOICE | LINE | ITEM | PRICE | QTY |
---|---|---|---|---|
101 | 1 | AB-205 | 3.00 | 2 |
101 | 2 | DD-762 | 5.00 | 4 |
102 | 1 | TR-911 | 2.00 | 2 |
103 | 1 | JP-310 | 6.00 | 3 |
103 | 2 | AB-205 | 1.00 | 2 |
101 | 3 | Null | null | Null |
以下が、先程、言おうとしていたタイプの照会です。ヘッダー表から詳細表への左外部結合があり、WHERE節は、詳細表に格納されているITEM列を参照します。
select h.invoice, h.company, h.CustNbr, h.InvDate,
d.line, d.item, d.qty
from invhdr as h
left join invdtl as d
on h.invoice = d.invoice
where d.item = 'AB-205';
結果セットは以下のようになります。
INVOICE | COMPANY | CUSTNBR | INVDATE | LINE | ITEM | QTY |
---|---|---|---|---|---|---|
101 | 1 | 50 | 2021-02-05 | 1 | AB-205 | 2 |
103 | 2 | 70 | 2021-02-06 | 2 | AB-205 | 2 |
この照会では明確に外部結合が求められているため、101から104までのインボイスが期待されていましたが、そのようにはならず、内部結合を使用した場合と同じ結果となっています。
さて、これは内部結合なのでしょうか、外部結合なのでしょうか。あるいは、照会が正しい結果を生成する限りは、その違いは重要なのでしょうか。皆さんはどうか分かりませんが、私にとっては重要なことです。
1つには、自己文書化の問題があります。つまり、人間がソース コードを読んで、そのコードがどのような処理を行っているのか、さらには、なぜそれを行っているのかを理解できるようにするべきだという考え方です。外部結合で2次表の列をテストすると、すぐに混乱が生じることになります。そのステートメントを書いた開発者は、果たして外部結合を必要としていたのでしょうか。あるいは、外部結合が必要だと開発者は考えていたのでしょうか。そもそも開発者は、考えることさえしたのでしょうか。
2つには、正しい結果がどのようなものであるべきかの判定の問題があります。多くのショップでは、テスト データは、ライブ データのコピーであり、識別可能なデータが変更される場合も、されない場合もあります。一部のショップでは、テスト データは ライブ データそのものです 。そのようなテスト データに関しては、顕著な問題が2つあります。(1)包括的でないことが多いため、ソフトウェアを通じてすべてのパスをテストするわけではない、(2)たいていの場合、データがあまりにも多過ぎる。テスト データ セットは、出力を検証しやすいように小規模であるべきです。
このSELECTを書いたプログラマーは、テストの手際が悪かったという可能性はあります。プログラマーは、本番データベースに対してクイック テストを実行し、数値が画面やレポートと一致するのを確認して、次の問題の対応に移ったのかもしれません。
また、プログラマーは外部結合を 必要としていたのは本当 だが、品目AB-205の2次データが必要なだけだったという可能性もあります。 そのようなタイプの照会については、若い頃に記事を書いたことがあります。 もしそうなら、このステートメントには、確かにバグがあるということです。
数か月前の記事に書いたように、見掛けると、ちょっと注意しないといけないと思わせられるプログラミング作法というものがあります。外部結合でNULLでない値をテストするというのも、そのようなプログラミング作法の1つです。注意を促しやすくする良い方法は、相関名を使用することです。 item の前に d. があることで、テストが2次表の列に対してであることに気付きやすくなるからです。
また、NULLのテストということになると、話は別になってくるということも述べておく必要があります。内部結合は、2次表の列がNULLである行のみを返します。
select h.invoice, h.company, h.CustNbr, h.InvDate,
d.line, d.item, d.qty
from invhdr as h
join invdtl as d
on h.invoice = d.invoice
where d.item is null;
INVOICE | COMPANY | CUSTNBR | INVDATE | LINE | ITEM | QTY |
---|---|---|---|---|---|---|
101 | 1 | 50 | 2021-02-05 | 3 | Null | Null |
外部結合は、NULLの2次列がある行も、マッチしていない行のNULL値も返します。
select h.invoice, h.company, h.CustNbr, h.InvDate,
d.line, d.item, d.qty
from invhdr as h
left join invdtl as d
on h.invoice = d.invoice
where d.item is null;
INVOICE | COMPANY | CUSTNBR | INVDATE | LINE | ITEM | QTY |
---|---|---|---|---|---|---|
101 | 1 | 50 | 2021-02-05 | 3 | Null | null |
104 | 2 | 53 | 2021-02-06 | null | Null | null |
効果的で信頼できるコードは、偶然できるものではありません。警察署を舞台にしたドラマ「ヒルストリート・ブルース」で、フィル・エスターハウス巡査部長が部下を送り出すときのお決まりの台詞を借りれば、「Let's be careful out there(皆、気を付けて行け)」ということです。