Exemplo com as tabelas A e 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
Se você quiser encontrar pais e filhos, faça um
INNER JOIN
:SELECT id, parent.name AS parent
, pid, child.name AS child
FROM
parent INNER JOIN child
ON parent.id = child.pid
O resultado é que cada correspondência de um
pai
's id
da tabela à esquerda e um filho
's pid
da segunda tabela aparecerá como uma linha no resultado:+----+--------+------+-------+
| id | parent | pid | child |
+----+--------+------+-------+
| 1 | Alex | 1 | Kate |
| 1 | Alex | 1 | Lia |
| 3 | Cath | 3 | Mary |
+----+--------+------+-------+
Agora, o exemplo acima não mostra os pais sem filhos (porque seus ids não têm uma correspondência nos ids dos filhos, então o que você faz? Você faz uma junção externa. Existem três tipos de junções externas, a esquerda, a direita e a junção externa completa. Precisamos da esquerda, pois queremos as linhas "extras" da tabela esquerda (pai):
SELECT id, parent.name AS parent
, pid, child.name AS child
FROM
parent LEFT JOIN child
ON parent.id = child.pid
O resultado é que além das partidas anteriores, todos os pais que não têm uma partida (leia-se:não tem filho) também são mostrados:
+----+--------+------+-------+
| 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 |
+----+--------+------+-------+
Onde foram parar todos aqueles
NULL
vem de onde? Bem, o MySQL (ou qualquer outro RDBMS que você possa usar) não saberá o que colocar lá, pois esses pais não têm correspondência (criança), então não há pid
nem child.name
para combinar com esses pais. Então, ele coloca esse não valor especial chamado NULL
. Meu ponto é que esses
NULLs
são criados (no conjunto de resultados) durante o LEFT OUTER JOIN
. Portanto, se quisermos mostrar apenas os pais que NÃO têm filhos, podemos adicionar um
WHERE child.pid IS NULL
para o LEFT JOIN
acima de. O ONDE
cláusula é avaliada (marcada) após o JOIN
é feito. Portanto, fica claro pelo resultado acima que apenas as três últimas linhas em que o pid
é NULL será mostrado: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
Resultado:
+----+--------+------+-------+
| id | parent | pid | child |
+----+--------+------+-------+
| 2 | Bill | NULL | NULL |
| 4 | Dale | NULL | NULL |
| 5 | Evan | NULL | NULL |
+----+--------+------+-------+
Agora, o que acontece se movermos
IS NULL
verifique em ONDE
para a entrada ON
cláusula? 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
Nesse caso, o banco de dados tenta encontrar linhas das duas tabelas que correspondam a essas condições. Ou seja, linhas onde
parent.id =child.pid
E child.pid IN NULL
. Mas ele pode encontrar nenhuma correspondência porque não há child.pid
pode ser igual a algo (1, 2, 3, 4 ou 5) e ser NULL ao mesmo tempo! Então, a condição:
ON parent.id = child.pid
AND child.pid IS NULL
é equivalente a:
ON 1 = 0
que é sempre
Falso
. Então, por que ele retorna TODAS as linhas da tabela à esquerda? Porque é um LEFT JOIN! E as junções à esquerda retornam linhas que correspondem (nenhuma neste caso) e também linhas da tabela esquerda que não correspondem o cheque (todos neste caso ):
+----+--------+------+-------+
| id | parent | pid | child |
+----+--------+------+-------+
| 1 | Alex | NULL | NULL |
| 2 | Bill | NULL | NULL |
| 3 | Cath | NULL | NULL |
| 4 | Dale | NULL | NULL |
| 5 | Evan | NULL | NULL |
+----+--------+------+-------+
Espero que a explicação acima seja clara.
Nota lateral (não diretamente relacionada à sua pergunta):Por que diabos não
Pan
não apareceu em nenhum dos nossos JOINs? Porque seu pid
é NULL
e NULL na lógica (não comum) do SQL não é igual a nada, então não pode corresponder a nenhum dos ids pai (que são 1,2,3,4 e 5). Mesmo que houvesse um NULL lá, ele ainda não corresponderia porque NULL
não é igual a nada, nem mesmo NULL
em si (é uma lógica muito estranha, de fato!). É por isso que usamos a verificação especial IS NULL
e não um =NULL
Verifica. Então,
Pan
aparecer se fizermos um RIGHT JOIN
? Sim vai! Porque um RIGHT JOIN mostrará todos os resultados que correspondem (o primeiro INNER JOIN que fizemos) mais todas as linhas da tabela RIGHT que não correspondem (que no nosso caso é um, o (NULL, 'Pan') fileira.
SELECT id, parent.name AS parent
, pid, child.name AS child
FROM
parent RIGHT JOIN child
ON parent.id = child.pid
Resultado:
+------+--------+------+-------+
| id | parent | pid | child |
+---------------+------+-------+
| 1 | Alex | 1 | Kate |
| 1 | Alex | 1 | Lia |
| 3 | Cath | 3 | Mary |
| NULL | NULL | NULL | Pan |
+------+--------+------+-------+
Infelizmente, o MySQL não tem FULL JOIN
. Você pode experimentá-lo em outros RDBMSs e ele mostrará:
+------+--------+------+-------+
| 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 |
+------+--------+------+-------+