web-dev-qa-db-ja.com

IS NULLまたはIS NOT NULLを結合条件で使用する-理論的な質問

ここに理論的な質問:

Table.field IS NULLまたはtable.field IS NOT NULLを指定すると、結合条件(たとえば、左または右の結合)では機能しませんが、どこ条件?

動作しない例:

-これにより、返品(null値以外)が除外されたすべての出荷が返されます。ただし、これは、[r.id is null]ステートメントに一致するものがあるかどうかにかかわらず、すべての貨物を返します。

SELECT
  *
FROM 
  shipments s
LEFT OUTER JOIN returns r  
  ON s.id = r.id
  AND r.id is null
WHERE
  s.day >= CURDATE() - INTERVAL 10 DAY 

作業例:

-これは、返品に関連するもの(非NULL値)を差し引いた、総出荷数である正しい行数を返します。

SELECT
  *
FROM 
  shipments s
LEFT OUTER JOIN returns r  
  ON s.id = r.id
WHERE
  s.day >= CURDATE() - INTERVAL 10 DAY
  AND r.id is null

これはなぜですか?結合される2つのテーブル間の他のすべてのフィルター条件は正常に機能しますが、何らかの理由でIS NULLおよびIS NOT NULLフィルターはwhereステートメント以外で機能しません。

この理由は何ですか?

35
JoshG

テーブルAとBの例:

 A (parent)       B (child)    
============    =============
 id | name        pid | name 
------------    -------------
  1 | Alex         1  | Kate
  2 | Bill         1  | Lia
  3 | Cath         3  | Mary
  4 | Dale       NULL | Pan
  5 | Evan  

親とその子供を見つけたい場合は、INNER JOINを実行します。

SELECT id,  parent.name AS parent
     , pid, child.name  AS child

FROM
        parent  INNER JOIN  child
  ON   parent.id     =    child.pid

結果は、左側のテーブルのparentidと2番目のテーブルのchildpidのすべての一致が結果の行として表示されます。

+----+--------+------+-------+
| id | parent | pid  | child | 
+----+--------+------+-------+
|  1 | Alex   |   1  | Kate  |
|  1 | Alex   |   1  | Lia   |
|  3 | Cath   |   3  | Mary  |
+----+--------+------+-------+

さて、上記では子供のいない親は表示されません(子供のIDに一致するIDがないため、どうしますか?代わりに外部結合を実行します。外部結合には、左、右、完全な外部結合左のテーブル(親)から「余分な」行が必要なため、左の結合が必要です。

SELECT id,  parent.name AS parent
     , pid, child.name  AS child

FROM
        parent  LEFT JOIN  child
  ON   parent.id    =    child.pid

結果は、以前の一致に加えて、一致していない(読み取り:子供がいない)すべての親も表示されます。

+----+--------+------+-------+
| id | parent | pid  | child | 
+----+--------+------+-------+
|  1 | Alex   |   1  | Kate  |
|  1 | Alex   |   1  | Lia   |
|  3 | Cath   |   3  | Mary  |
|  2 | Bill   | NULL | NULL  |
|  4 | Dale   | NULL | NULL  |
|  5 | Evan   | NULL | NULL  |
+----+--------+------+-------+

それらのNULLはどこから来たのですか?まあ、MySQL(または使用する他のRDBMS)は、これらの親には一致(子供)がないため、そこに何を配置するかわかりません。したがって、これらの親と一致するpidも_child.nameもありません。したがって、NULLと呼ばれるこの特別な非値を配置します。

私のポイントは、これらのNULLsLEFT OUTER JOINの間に(結果セットに)作成されるということです


したがって、子供のいない親のみを表示する場合は、上記のWHERE child.pid IS NULLLEFT JOINを追加できます。 WHERE節は、JOINの実行後に評価(チェック)されます。したがって、上記の結果から、pidがNULLである最後の3行のみが表示されることが明らかです。

SELECT id,  parent.name AS parent
     , pid, child.name  AS child

FROM
        parent  LEFT JOIN  child
  ON   parent.id    =    child.pid

WHERE child.pid IS NULL

結果:

+----+--------+------+-------+
| id | parent | pid  | child | 
+----+--------+------+-------+
|  2 | Bill   | NULL | NULL  |
|  4 | Dale   | NULL | NULL  |
|  5 | Evan   | NULL | NULL  |
+----+--------+------+-------+

では、IS NULLチェックをWHEREから結合ON句に移動するとどうなりますか?

SELECT id,  parent.name AS parent
     , pid, child.name  AS child

FROM
        parent  LEFT JOIN  child
  ON   parent.id    =    child.pid
  AND  child.pid IS NULL

この場合、データベースはこれらの条件に一致する2つのテーブルから行を見つけようとします。つまり、parent.id = child.pid[〜#〜] and [〜#〜]child.pid IN NULLである行。しかし、それはそのような一致はありませんを見つけることができます。なぜなら、child.pidは何か(1、2、3、4、または5)に等しく、同時にNULLになることができないからです!

したがって、条件:

ON   parent.id    =    child.pid
AND  child.pid IS NULL

以下と同等です:

ON   1 = 0

これは常にFalseです。

では、なぜ左のテーブルからすべての行を返すのですか? LEF JOINだから!そして、左結合は戻ります一致する行(この場合はなし)そして一致しない左側のテーブルの行 =チェック(この場合はすべて):

+----+--------+------+-------+
| id | parent | pid  | child | 
+----+--------+------+-------+
|  1 | Alex   | NULL | NULL  |
|  2 | Bill   | NULL | NULL  |
|  3 | Cath   | NULL | NULL  |
|  4 | Dale   | NULL | NULL  |
|  5 | Evan   | NULL | NULL  |
+----+--------+------+-------+

上記の説明が明確であることを願っています。



サイドノート(あなたの質問に直接関係ない):なぜ私たちのJOINのどれにもPanが表示されないのはなぜですか?彼のpidNULLであり、SQLの(一般的ではない)ロジックのNULLは何にも等しくないため、親ID(1、2、3、 4および5)。そこにNULLがあったとしても、NULLは何にも等しくなく、NULL自体にも等しくないため、一致しません(実際、非常に奇妙なロジックです!)。そのため、IS NULLチェックではなく、特別なチェック= NULLを使用します。

したがって、RIGHT JOINを実行するとPanが表示されますか?はい、そうです! RIGHT JOINには、一致するすべての結果(最初に行ったINNER JOIN)に加えて、一致しないRIGHTテーブルのすべての行(この場合は1つ、(NULL, 'Pan')行)が表示されるためです。

SELECT id,  parent.name AS parent
     , pid, child.name  AS child

FROM
        parent  RIGHT JOIN  child
  ON   parent.id     =    child.pid

結果:

+------+--------+------+-------+
| id   | parent | pid  | child | 
+---------------+------+-------+
|   1  | Alex   |   1  | Kate  |
|   1  | Alex   |   1  | Lia   |
|   3  | Cath   |   3  | Mary  |
| NULL | NULL   | NULL | Pan   |
+------+--------+------+-------+

残念ながら、MySQLにはFULL JOINがありません。他のRDBMSで試してみると、次のように表示されます。

+------+--------+------+-------+
|  id  | parent | pid  | child | 
+------+--------+------+-------+
|   1  | Alex   |   1  | Kate  |
|   1  | Alex   |   1  | Lia   |
|   3  | Cath   |   3  | Mary  |
|   2  | Bill   | NULL | NULL  |
|   4  | Dale   | NULL | NULL  |
|   5  | Evan   | NULL | NULL  |
| NULL | NULL   | NULL | Pan   |
+------+--------+------+-------+
81
ypercubeᵀᴹ

NULL部分は実際の結合の後に計算されるため、where句に含める必要があります。

6
Sabeen Malik

実際には、NULLフィルターは無視されていません。これが、2つのテーブルを結合する方法です。

データベースサーバーが実行するステップを理解して理解するために、データベースサーバーが実行する手順を説明します。たとえば、NULL条件を無視していると言ったクエリを実行するとします。 SELECT * FROM貨物s LEFT OUTER JOINはrを返します
ON s.id = r.id AND r.idはnull WHERE s.day> = CURDATE()-間隔10日

最初に起こったのは、表SHIPMENTSのすべての行が選択されることです

次のステップで、データベースサーバーは2番目の(RETURNS)テーブルからレコードを1つずつ選択し始めます。

3番目のステップで、RETURNSテーブルのレコードは、クエリで指定した結合条件に対して修飾されます。この場合は(s.id = r.idおよびr.idはNULL)です

3番目のステップで適用されるこの条件は、サーバーがRETURNSテーブルの現在のレコードを受け入れるか拒否して、SHIPMENTテーブルの選択された行に追加するかを決定するだけであることに注意してください。 SHIPMENTテーブルからのレコードの選択には影響しません。

そして、SHIPMENTテーブルのすべての行とRETURNSテーブルの選択された行を含む2つのテーブルの結合が完了したら、中間結果にwhere句を適用します。そのため、r.id = nullの中間結果のすべてのレコードよりもwhere句に条件(r.idがNULL)を入れると、フィルターで除外されます。

3
Muhammad Usama

WHERE句は、JOIN条件が処理された後に評価されます。

2
Joe Stefanelli

あなたはLEFT OUTTER JOINこれは、RIGHTテーブルに一致するレコードがあるかどうかに関係なく、ステートメントのLEFT上のテーブルのすべてのTupleが必要であることを示します。この場合、結果はRIGHTテーブルからプルーニングされますが、ON句内にANDをまったく含めなかった場合と同じ結果になります。

WHERE句でANDを実行すると、LEFT JOINの実行後にプルーンが発生します。

2
Suroot

実行計画でこれを明確にする必要があります。 JOINが優先され、その後、結果がフィルタリングされます。

1
Paul Sonier