Eu quero fazer uma junção externa completa no MySQL. Isso é possível? Uma junção externa completa é suportada pelo MySQL?
Eu quero fazer uma junção externa completa no MySQL. Isso é possível? Uma junção externa completa é suportada pelo MySQL?
Respostas:
Você não possui JOYS FULL no MySQL, mas com certeza pode imitá-los .
Para um código SAMPLE transcrito desta questão SO, você tem:
com duas tabelas t1, t2:
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
A consulta acima funciona para casos especiais em que uma operação FULL OUTER JOIN não produziria nenhuma linha duplicada. A consulta acima depende do UNION
operador set para remover linhas duplicadas introduzidas pelo padrão de consulta. Podemos evitar a introdução de linhas duplicadas usando um padrão anti-junção para a segunda consulta e, em seguida, usar um operador de conjunto UNION ALL para combinar os dois conjuntos. No caso mais geral, onde um JOGO EXTERNO CHEIO retornaria linhas duplicadas, podemos fazer isso:
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION ALL
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
WHERE t1.id IS NULL
(SELECT ... FROM tbl1 LEFT JOIN tbl2 ...) UNION ALL (SELECT ... FROM tbl1 RIGHT JOIN tbl2 ... WHERE tbl1.col IS NULL)
t1
e t2
, a consulta nesta resposta retornará um conjunto de resultados que emula a junção externa completa . Porém, no caso mais geral, por exemplo, a lista SELECT não contém colunas / expressões suficientes para tornar as linhas retornadas únicas; esse padrão de consulta é insuficiente para reproduzir o conjunto que seria produzido por a FULL OUTER JOIN
. Para obter uma emulação mais fiel, precisaríamos de um UNION ALL
operador set e uma das consultas precisaria de um padrão anti-junção . O comentário de Pavle Lekic (acima) fornece o padrão de consulta correto .
A resposta que Pablo Santa Cruz deu está correta; no entanto, caso alguém tropece nesta página e deseje mais esclarecimentos, aqui está um detalhamento detalhado.
Suponha que tenhamos as seguintes tabelas:
-- t1
id name
1 Tim
2 Marta
-- t2
id name
1 Tim
3 Katarina
Uma junção interna, assim:
SELECT *
FROM `t1`
INNER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Nos levaria apenas os registros que aparecem nas duas tabelas, assim:
1 Tim 1 Tim
As junções internas não têm uma direção (como esquerda ou direita) porque são explicitamente bidirecionais - exigimos uma correspondência dos dois lados.
As junções externas, por outro lado, são para localizar registros que podem não ter uma correspondência na outra tabela. Como tal, você deve especificar qual lado da associação pode ter um registro ausente.
LEFT JOIN
e RIGHT JOIN
são abreviação de LEFT OUTER JOIN
e RIGHT OUTER JOIN
; Usarei seus nomes completos abaixo para reforçar o conceito de junções externas versus junções internas.
Uma junção externa esquerda, assim:
SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
... nos forneceria todos os registros da tabela esquerda, independentemente de eles corresponderem ou não à tabela correta, assim:
1 Tim 1 Tim
2 Marta NULL NULL
Uma junção externa direita, assim:
SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
... nos obteria todos os registros da tabela da direita, independentemente de haver ou não uma correspondência na tabela da esquerda, assim:
1 Tim 1 Tim
NULL NULL 3 Katarina
Uma junção externa completa nos forneceria todos os registros de ambas as tabelas, independentemente de terem ou não correspondência na outra tabela, com NULLs nos dois lados onde não há correspondência. O resultado ficaria assim:
1 Tim 1 Tim
2 Marta NULL NULL
NULL NULL 3 Katarina
No entanto, como Pablo Santa Cruz apontou, o MySQL não suporta isso. Podemos emular isso fazendo uma UNIÃO de uma junção esquerda e uma junção direita, assim:
SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`
UNION
SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Você pode pensar UNION
no significado de "executar ambas as consultas e empilhar os resultados uns sobre os outros"; algumas das linhas virão da primeira consulta e outras da segunda.
Deve-se notar que a UNION
no MySQL eliminará duplicatas exatas: Tim apareceria nas duas consultas aqui, mas o resultado da UNION
única lista dele uma vez. Meu colega de guru de banco de dados considera que esse comportamento não deve ser invocado. Para ser mais explícito, podemos adicionar uma WHERE
cláusula à segunda consulta:
SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`
UNION
SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`
WHERE `t1`.`id` IS NULL;
Por outro lado, se você quiser ver duplicatas por algum motivo, poderá usar UNION ALL
.
FULL OUTER JOIN
. Não há nada errado em fazer consultas dessa maneira e usar o UNION para remover essas duplicatas. Mas, para realmente replicar um FULL OUTER JOIN
, precisamos de uma das consultas para ser uma anti-junção.
UNION
operação removerá essas duplicatas; mas também remove TODAS as linhas duplicadas, incluindo linhas duplicadas que seriam retornadas por uma junção externa completa. Para emular a FULL JOIN b
, o padrão correto é (a LEFT JOIN b) UNION ALL (b ANTI JOIN a)
.
O uso de uma union
consulta removerá duplicatas, e isso é diferente do comportamento de full outer join
nunca remover duplicatas:
[Table: t1] [Table: t2]
value value
------- -------
1 1
2 2
4 2
4 5
Este é o resultado esperado de full outer join
:
value | value
------+-------
1 | 1
2 | 2
2 | 2
Null | 5
4 | Null
4 | Null
Este é o resultado do uso left
e right Join
com union
:
value | value
------+-------
Null | 5
1 | 1
2 | 2
4 | Null
Minha consulta sugerida é:
select
t1.value, t2.value
from t1
left outer join t2
on t1.value = t2.value
union all -- Using `union all` instead of `union`
select
t1.value, t2.value
from t2
left outer join t1
on t1.value = t2.value
where
t1.value IS NULL
Resultado da consulta acima igual ao resultado esperado:
value | value
------+-------
1 | 1
2 | 2
2 | 2
4 | NULL
4 | NULL
NULL | 5
@ Steve Chambers : [Dos comentários, com muito obrigado!]
Nota: Essa pode ser a melhor solução, tanto para eficiência quanto para gerar os mesmos resultados que aFULL OUTER JOIN
. Esta postagem no blog também explica bem - para citar o Método 2: "Isso manipula linhas duplicadas corretamente e não inclui nada que não deveria. É necessário usar emUNION ALL
vez de simplesUNION
, o que eliminaria as duplicatas que eu quero manter. Isso pode ser significativamente mais eficiente em grandes conjuntos de resultados, pois não há necessidade de classificar e remover duplicatas ".
Eu decidi adicionar outra solução que vem da full outer join
visualização e matemática, não é melhor que acima, mas mais legível:
Junção externa completa significa
(t1 ∪ t2)
: tudo dentrot1
ou dentrot2
(t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only
: tudo dentrot1
et2
mais tudo ot1
que não está dentrot2
e mais tudo ot2
que não está dentrot1
:
-- (t1 ∩ t2): all in both t1 and t2
select t1.value, t2.value
from t1 join t2 on t1.value = t2.value
union all -- And plus
-- all in t1 that not exists in t2
select t1.value, null
from t1
where not exists( select 1 from t2 where t2.value = t1.value)
union all -- and plus
-- all in t2 that not exists in t1
select null, t2.value
from t2
where not exists( select 1 from t1 where t2.value = t1.value)
FULL OUTER JOIN
. Esta postagem no blog também explica bem - para citar o Método 2: "Isso manipula linhas duplicadas corretamente e não inclui nada que não deveria. É necessário usar UNION ALL em vez de UNION simples, o que eliminaria as duplicatas que eu quero Isso pode ser significativamente mais eficiente em grandes conjuntos de resultados, pois não há necessidade de classificar e remover duplicatas ".
O MySql não possui sintaxe FULL-OUTER-JOIN. Você deve emular executando ESQUERDA JOIN e DIREITA JOIN da seguinte maneira:
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
Mas o MySql também não possui uma sintaxe RIGHT JOIN. De acordo com a simplificação da junção externa do MySql , a junção direita é convertida na junção esquerda equivalente alternando t1 e t2 na cláusula FROM
e ON
na consulta. Assim, o MySql Query Optimizer converte a consulta original no seguinte -
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t2
LEFT JOIN t1 ON t2.id = t1.id
Agora, não há nenhum problema em escrever a consulta original como está, mas digamos que se você tiver predicados como a cláusula WHERE, que é um predicado antes da junção ou um predicado AND na ON
cláusula, que é um predicado durante a junção , então você pode querer dar uma olhada no diabo; que está em detalhes.
O otimizador de consultas do MySql rotineiramente verifica os predicados se eles são nulos . Agora, se você tiver feito o DIREITO JOIN, mas com o predicado WHERE na coluna de t1, poderá correr o risco de encontrar um cenário rejeitado por nulo .
Por exemplo, a consulta a seguir -
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
é traduzido para o seguinte pelo Query Optimizer-
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
UNION
SELECT * FROM t2
LEFT JOIN t1 ON t2.id = t1.id
WHERE t1.col1 = 'someValue'
Portanto, a ordem das tabelas mudou, mas o predicado ainda é aplicado a t1, mas agora está na cláusula 'ON'. Se t1.col1 for definido como NOT NULL
coluna, essa consulta será rejeitada por nulo .
Qualquer junção externa (esquerda, direita, cheia) que seja rejeitada por nulo é convertida em uma junção interna pelo MySql.
Portanto, os resultados que você pode esperar podem ser completamente diferentes do que o MySql está retornando. Você pode pensar que é um bug com o RIGHT JOIN do MySql, mas isso não está certo. É assim que o otimizador de consultas do MySql funciona. Portanto, o desenvolvedor responsável deve prestar atenção a essas nuances quando estiver construindo a consulta.
No SQLite, você deve fazer isso:
SELECT *
FROM leftTable lt
LEFT JOIN rightTable rt ON lt.id = rt.lrid
UNION
SELECT lt.*, rl.* -- To match column set
FROM rightTable rt
LEFT JOIN leftTable lt ON lt.id = rt.lrid
Nenhuma das respostas acima está realmente correta, porque elas não seguem a semântica quando há valores duplicados.
Para uma consulta como (desta duplicata ):
SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.Name = t2.Name;
O equivalente correto é:
SELECT t1.*, t2.*
FROM (SELECT name FROM t1 UNION -- This is intentionally UNION to remove duplicates
SELECT name FROM t2
) n LEFT JOIN
t1
ON t1.name = n.name LEFT JOIN
t2
ON t2.name = n.name;
Se você precisar que isso funcione com NULL
valores (o que também pode ser necessário), use o NULL
operador de comparação -safe, em <=>
vez de =
.
FULL OUTER JOIN
sempre que a name
coluna for nula. A union all
consulta com padrão anti-junção deve reproduzir o comportamento da junção externa corretamente, mas qual solução é mais apropriada depende do contexto e das restrições ativas nas tabelas.
union all
, mas essa resposta perde um padrão de anti-junção na primeira ou na segunda consulta que manterá as duplicatas existentes, mas impede a adição de novas. Dependendo do contexto, outras soluções (como esta) podem ser mais apropriadas.
Consulta do shA.t modificada para maior clareza:
-- t1 left join t2
SELECT t1.value, t2.value
FROM t1 LEFT JOIN t2 ON t1.value = t2.value
UNION ALL -- include duplicates
-- t1 right exclude join t2 (records found only in t2)
SELECT t1.value, t2.value
FROM t1 RIGHT JOIN t2 ON t1.value = t2.value
WHERE t2.value IS NULL
o que você disse sobre a solução de junção cruzada ?
SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2
ON 1=1;
select (select count(*) from t1) * (select count(*) from t2))
linhas no conjunto de resultados.
SELECT
a.name,
b.title
FROM
author AS a
LEFT JOIN
book AS b
ON a.id = b.author_id
UNION
SELECT
a.name,
b.title
FROM
author AS a
RIGHT JOIN
book AS b
ON a.id = b.author_id
Também é possível, mas você deve mencionar os mesmos nomes de campo em select.
SELECT t1.name, t2.name FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT t1.name, t2.name FROM t2
LEFT JOIN t1 ON t1.id = t2.id
Corrijo a resposta e os trabalhos incluem todas as linhas (com base na resposta de Pavle Lekic)
(
SELECT a.* FROM tablea a
LEFT JOIN tableb b ON a.`key` = b.key
WHERE b.`key` is null
)
UNION ALL
(
SELECT a.* FROM tablea a
LEFT JOIN tableb b ON a.`key` = b.key
where a.`key` = b.`key`
)
UNION ALL
(
SELECT b.* FROM tablea a
right JOIN tableb b ON b.`key` = a.key
WHERE a.`key` is null
);
tablea
que não possuem correspondência tableb
e vice-versa. Você tenta UNION ALL
, o que só funcionaria se essas duas tabelas tivessem colunas ordenadas equivalentemente, o que não é garantido.
Responda:
SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.id = t2.id;
Pode ser recriado da seguinte maneira:
SELECT t1.*, t2.*
FROM (SELECT * FROM t1 UNION SELECT name FROM t2) tmp
LEFT JOIN t1 ON t1.id = tmp.id
LEFT JOIN t2 ON t2.id = tmp.id;
O uso de uma resposta UNION ou UNION ALL não cobre o caso de borda em que as tabelas base têm entradas duplicadas.
Explicação:
Há um caso extremo que uma UNION ou UNION ALL não pode cobrir. Nós não podemos testar isso no mysql, pois ele não suporta JOINS EXTERIORES COMPLETOS, mas podemos ilustrar isso em um banco de dados que o suporta:
WITH cte_t1 AS
(
SELECT 1 AS id1
UNION ALL SELECT 2
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 6
),
cte_t2 AS
(
SELECT 3 AS id2
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 6
)
SELECT * FROM cte_t1 t1 FULL OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2;
This gives us this answer:
id1 id2
1 NULL
2 NULL
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
A solução UNION:
SELECT * FROM cte_t1 t1 LEFT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2
UNION
SELECT * FROM cte_t1 t1 RIGHT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2
Dá uma resposta incorreta:
id1 id2
NULL 3
NULL 4
1 NULL
2 NULL
5 5
6 6
A solução UNION ALL:
SELECT * FROM cte_t1 t1 LEFT OUTER join cte_t2 t2 ON t1.id1 = t2.id2
UNION ALL
SELECT * FROM cte_t1 t1 RIGHT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2
Também está incorreto.
id1 id2
1 NULL
2 NULL
5 5
6 6
6 6
6 6
6 6
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
Considerando que esta consulta:
SELECT t1.*, t2.*
FROM (SELECT * FROM t1 UNION SELECT name FROM t2) tmp
LEFT JOIN t1 ON t1.id = tmp.id
LEFT JOIN t2 ON t2.id = tmp.id;
Fornece o seguinte:
id1 id2
1 NULL
2 NULL
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
A ordem é diferente, mas corresponde à resposta correta.
UNION ALL
solução. Além disso, ele apresenta uma solução UNION
que seria mais lenta em grandes tabelas de origem devido à deduplicação necessária. Finalmente, não seria compilado, porque o campo id
não existe na subconsulta tmp
.
UNION ALL
incorretamente : "A solução: ... Também está incorreta." O código que você apresenta deixa de fora a exclusão de interseção da join-right ( where t1.id1 is null
) que deve ser fornecida no UNION ALL
. Ou seja, sua solução supera todas as outras, somente quando uma dessas outras soluções é implementada incorretamente. Sobre "fofura", ponto em questão. Isso foi gratuito, minhas desculpas.
O padrão SQL diz full join on
é inner join on
linhas union all
ímpares linhas da tabela esquerda estendidos por nulos union all
linhas da tabela direita estendida por valores nulos. Ou seja, inner join on
linhas union all
linhas, left join on
mas não inner join on
union all
linhas, right join on
mas nãoinner join on
.
Ou seja, left join on
linhas union all
right join on
linhas fora inner join on
. Ou, se você souber que seu inner join on
resultado não pode ter nulo em uma coluna específica da tabela à direita, " right join on
linhas que não inner join on
estão" são linhas right join on
com a on
condição estendida por and
essa coluna is null
.
Ou seja, linhas igualmente right join on
union all
apropriadas left join on
.
De Qual é a diferença entre "INNER JOIN" e "OUTER JOIN"? :
(Regras de sintaxe 1 do SQL Standard 2006 SQL / Foundation 7.7, Regras gerais 1 b, 3 c & d, 5 b.)