Пример с таблицами 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
Результатом является то, что каждое совпадение parent
'id
из левой таблицы и child
' pid
из второй таблицы будет отображаться в виде строки в результате:
+----+--------+------+-------+
| id | parent | pid | child |
+----+--------+------+-------+
| 1 | Alex | 1 | Kate |
| 1 | Alex | 1 | Lia |
| 3 | Cath | 3 | Mary |
+----+--------+------+-------+
Теперь, выше не показаны родители без детей (потому что их идентификаторы не совпадают с идентификаторами детей, так что вы делаете? Вместо этого вы делаете внешнее соединение. Есть три типа внешних объединений, слева, правое и полное внешнее соединение. Нам нужно левое, так как нам нужны «лишние» строки из левой таблицы (родитель):
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 (или любая другая СУБД, которую вы можете использовать) не будет знать, что туда поместить, поскольку у этих родителей нет совпадений (ребенок), поэтому нет pid
или child.name
для сопоставления с этими родителями. Таким образом, это специальное ненулевое значение называется NULL
.
Я хочу сказать, что эти NULLs
создаются (в наборе результатов) во время LEFT OUTER JOIN
.
Итак, если мы хотим показать только родителей, которые НЕ имеют ребенка, мы можем добавить WHERE child.pid IS NULL
к LEFT JOIN
выше. Предложение WHERE
оценивается (проверяется) после выполнения JOIN
. Итак, из приведенного выше результата ясно, что будут показаны только последние три строки, в которых pid
равен NULL:
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
В этом случае база данных пытается найти строки из двух таблиц, которые соответствуют этим условиям. То есть строки, где parent.id = child.pid
И 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
.
Итак, почему он возвращает ВСЕ строки из левой таблицы? Поскольку это ЛЕВОЕ СОЕДИНЕНИЕ! И левые соединения возвращают строк, которые соответствуют (в данном случае ни одной) , а также строк из левой таблицы, которые не соответствуют проверке ( все в этом случае ):
+----+--------+------+-------+
| id | parent | pid | child |
+----+--------+------+-------+
| 1 | Alex | NULL | NULL |
| 2 | Bill | NULL | NULL |
| 3 | Cath | NULL | NULL |
| 4 | Dale | NULL | NULL |
| 5 | Evan | NULL | NULL |
+----+--------+------+-------+
Надеюсь, приведенное выше объяснение понятно.
Sidenote (не имеет прямого отношения к вашему вопросу): почему же Pan
не появляется ни в одном из наших JOIN? Поскольку его pid
равен NULL
, а NULL в (не распространенной) логике SQL не равен ничему, поэтому он не может совпадать ни с одним из родительских идентификаторов (1,2,3,4 и 5). , Даже если бы там был NULL, он все равно не соответствовал бы, потому что NULL
не равно ничему, даже самому NULL
(это действительно очень странная логика!). Вот почему мы используем специальный чек IS NULL
, а не = NULL
чек.
Итак, Pan
появится, если мы сделаем RIGHT JOIN
? Да, это будет! Поскольку RIGHT JOIN покажет все соответствующие результаты (первое INNER JOIN, которое мы сделали), а также все строки из RIGHT таблицы, которые не совпадают (в нашем случае это единица, строка (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
. Вы можете попробовать его в других РСУБД, и он покажет:
+------+--------+------+-------+
| 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 |
+------+--------+------+-------+