Voglio fare un Full Outer Join in MySQL. È possibile? Un join esterno completo è supportato da MySQL?
Voglio fare un Full Outer Join in MySQL. È possibile? Un join esterno completo è supportato da MySQL?
Risposte:
Non hai JOIN COMPLETI su MySQL, ma puoi sicuramente emularli .
Per un codice SAMPLE trascritto da questa domanda SO hai:
con due tabelle 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
La query sopra funziona per casi speciali in cui un'operazione FULL OUTER JOIN non produce righe duplicate. La query sopra dipende UNION
dall'operatore set per rimuovere le righe duplicate introdotte dal modello di query. È possibile evitare di introdurre righe duplicate utilizzando un modello anti-join per la seconda query, quindi utilizzare un operatore di set UNION ALL per combinare i due set. Nel caso più generale, in cui un JOEN FULL OUTER restituisce righe duplicate, possiamo farlo:
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
, la query in questa risposta restituisce un gruppo di risultati che emula FULL OUTER JOIN. Ma nel caso più generale, ad esempio, l'elenco SELECT non contiene colonne / espressioni sufficienti per rendere uniche le righe restituite, quindi questo modello di query non è sufficiente per riprodurre l'insieme che sarebbe prodotto da a FULL OUTER JOIN
. Per ottenere un'emulazione più fedele, avremmo bisogno di un UNION ALL
operatore impostato e una delle query avrebbe bisogno di un modello anti-join . Il commento di Pavle Lekic (sopra) fornisce il modello di query corretto .
La risposta che Pablo Santa Cruz ha dato è corretta; tuttavia, nel caso in cui qualcuno si fosse imbattuto in questa pagina e volesse maggiori chiarimenti, ecco un dettaglio dettagliato.
Supponiamo di avere le seguenti tabelle:
-- t1
id name
1 Tim
2 Marta
-- t2
id name
1 Tim
3 Katarina
Un join interno, come questo:
SELECT *
FROM `t1`
INNER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Ci procurerebbe solo i record che appaiono in entrambe le tabelle, in questo modo:
1 Tim 1 Tim
I join interni non hanno una direzione (come sinistra o destra) perché sono esplicitamente bidirezionali: è necessaria una corrispondenza su entrambi i lati.
I join esterni, d'altra parte, servono per trovare record che potrebbero non avere una corrispondenza nell'altra tabella. Pertanto, è necessario specificare a quale parte del join è consentito avere un record mancante.
LEFT JOIN
e RIGHT JOIN
sono una scorciatoia per LEFT OUTER JOIN
e RIGHT OUTER JOIN
; Userò i loro nomi completi qui sotto per rafforzare il concetto di join esterni rispetto a join interni.
Un join esterno sinistro, in questo modo:
SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
... ci procurerebbe tutti i record dalla tabella di sinistra indipendentemente dal fatto che abbiano o meno una corrispondenza nella tabella di destra, in questo modo:
1 Tim 1 Tim
2 Marta NULL NULL
Un join esterno destro, in questo modo:
SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
... ci prenderebbe tutti i record dalla tabella a destra indipendentemente dal fatto che abbiano o meno una corrispondenza nella tabella a sinistra, in questo modo:
1 Tim 1 Tim
NULL NULL 3 Katarina
Un join esterno completo ci fornirebbe tutti i record di entrambe le tabelle, indipendentemente dal fatto che abbiano o meno una corrispondenza nell'altra tabella, con NULL su entrambi i lati in cui non esiste corrispondenza. Il risultato sarebbe simile al seguente:
1 Tim 1 Tim
2 Marta NULL NULL
NULL NULL 3 Katarina
Tuttavia, come sottolineato da Pablo Santa Cruz, MySQL non lo supporta. Possiamo emularlo facendo un'UNIONE di un join sinistro e un join destro, in questo modo:
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`;
Puoi pensare a un UNION
significato come "esegui entrambe queste query, quindi impila i risultati uno sopra l'altro"; alcune delle righe verranno dalla prima query e alcune dalla seconda.
Va notato che a UNION
in MySQL eliminerà i duplicati esatti: Tim apparirebbe in entrambe le query qui, ma il risultato UNION
dell'unica lo elenca una volta. Il mio collega guru del database ritiene che questo comportamento non debba essere invocato. Quindi, per essere più espliciti al riguardo, potremmo aggiungere una WHERE
clausola alla seconda query:
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;
D'altra parte, se volessi vedere i duplicati per qualche motivo, potresti usare UNION ALL
.
FULL OUTER JOIN
. Non c'è niente di sbagliato nel fare query in quel modo e nell'usare UNION per rimuovere quei duplicati. Ma per replicare davvero un FULL OUTER JOIN
, abbiamo bisogno di una delle query per essere un anti-join.
UNION
un'operazione rimuoverà quei duplicati; ma rimuove anche TUTTE le righe duplicate, incluse le righe duplicate che verrebbero restituite da un FULL OUTER JOIN. Per emulare a FULL JOIN b
, il modello corretto è (a LEFT JOIN b) UNION ALL (b ANTI JOIN a)
.
L'uso di una union
query rimuoverà i duplicati, e questo è diverso dal comportamento full outer join
che non rimuove mai alcun duplicato:
[Table: t1] [Table: t2]
value value
------- -------
1 1
2 2
4 2
4 5
Questo è il risultato atteso di full outer join
:
value | value
------+-------
1 | 1
2 | 2
2 | 2
Null | 5
4 | Null
4 | Null
Questo è il risultato dell'utilizzo left
e right Join
con union
:
value | value
------+-------
Null | 5
1 | 1
2 | 2
4 | Null
La mia domanda suggerita è:
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
Risultato della query precedente uguale al risultato previsto:
value | value
------+-------
1 | 1
2 | 2
2 | 2
4 | NULL
4 | NULL
NULL | 5
@Steve Chambers : [Dai commenti, con molte grazie!]
Nota: questa potrebbe essere la soluzione migliore, sia per efficienza che per generare gli stessi risultati di aFULL OUTER JOIN
. Questo post sul blog lo spiega anche bene - per citare dal Metodo 2: "Questo gestisce correttamente le righe duplicate e non include nulla che non dovrebbe. È necessario utilizzare alUNION ALL
posto di plainUNION
, che eliminerebbe i duplicati che voglio conservare. Questo potrebbe essere significativamente più efficiente su grandi set di risultati, poiché non è necessario ordinare e rimuovere i duplicati ".
Ho deciso di aggiungere un'altra soluzione che viene dalla full outer join
visualizzazione e dalla matematica, non è meglio di quanto sopra ma più leggibile:
Un join esterno completo significa
(t1 ∪ t2)
: all int1
o int2
(t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only
: all in entrambit1
et2
più all int1
che non sono int2
e plus all int2
che non sono int1
:
-- (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
. Questo post del blog lo spiega anche bene - per citare dal Metodo 2: "Questo gestisce correttamente le righe duplicate e non include nulla che non dovrebbe. È necessario utilizzare UNION ALL invece del semplice UNION, che eliminerebbe i duplicati che voglio continua. Questo potrebbe essere significativamente più efficiente su grandi set di risultati, poiché non è necessario ordinare e rimuovere i duplicati. "
MySql non ha sintassi FULL-OUTER-JOIN. Devi emulare facendo sia LEFT JOIN che RIGHT JOIN come segue-
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
Ma anche MySql non ha una sintassi RIGHT JOIN. Secondo la semplificazione del join esterno di MySql , il join destro viene convertito nel join sinistro equivalente commutando t1 e t2 nella clausola FROM
e ON
nella query. Pertanto, MySql Query Optimizer traduce la query originale nel seguente -
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t2
LEFT JOIN t1 ON t2.id = t1.id
Ora, non c'è nulla di male nello scrivere la query originale così com'è, ma dite se avete predicati come la clausola WHERE, che è un predicato prima del join o un predicato AND sulla ON
clausola, che è un predicato durante il join , quindi voi potrebbe voler dare un'occhiata al diavolo; che è nei dettagli.
MySql Query Optimizer controlla regolarmente i predicati se sono respinti nulli . Ora, se hai eseguito RIGHT JOIN, ma con predicato WHERE sulla colonna da t1, potresti essere a rischio di imbatterti in uno scenario respinto nullo .
Ad esempio, la seguente query:
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'
viene tradotto nel seguente Strumento per ottimizzare le query:
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'
Quindi l'ordine delle tabelle è cambiato, ma il predicato è ancora applicato a t1, ma t1 è ora nella clausola 'ON'. Se t1.col1 è definito come NOT NULL
colonna, questa query verrà respinta null .
Qualsiasi join esterno (sinistro, destro, pieno) che viene rifiutato null viene convertito in un join interno da MySql.
Pertanto, i risultati che potresti aspettarti potrebbero essere completamente diversi da quelli restituiti da MySql. Potresti pensare che sia un bug con RIGHT JOIN di MySql, ma non è vero. È proprio come funziona l'ottimizzatore di query MySql. Quindi lo sviluppatore responsabile deve prestare attenzione a queste sfumature quando sta costruendo la query.
In SQLite dovresti fare questo:
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
Nessuna delle risposte precedenti è effettivamente corretta, perché non seguono la semantica quando ci sono valori duplicati.
Per una query come (da questo duplicato ):
SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.Name = t2.Name;
L'equivalente corretto è:
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 è necessario che funzioni con NULL
valori (che possono anche essere necessari), utilizzare l' NULL
operatore di confronto -safe <=>
anziché =
.
FULL OUTER JOIN
quando la name
colonna è nulla. La union all
query con pattern anti-join dovrebbe riprodurre correttamente il comportamento del join esterno, ma quale soluzione è più appropriata dipende dal contesto e dai vincoli attivi sulle tabelle.
union all
, ma quella risposta manca un modello anti-join nella prima o nella seconda query che manterrà i duplicati esistenti ma impedisce di aggiungerne di nuovi. A seconda del contesto, altre soluzioni (come questa) potrebbero essere più appropriate.
Interrogazione di shA.t modificata per maggiore chiarezza:
-- 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
che ne dici della soluzione Cross join ?
SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2
ON 1=1;
select (select count(*) from t1) * (select count(*) from t2))
righe nel set di risultati.
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
È anche possibile, ma devi selezionare gli stessi nomi di campo in 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
Risolvo la risposta e le opere includono tutte le righe (basate sulla risposta di 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
cui non ha una corrispondenza tableb
e viceversa. Si tenta di farlo UNION ALL
, che funzionerebbe solo se queste due tabelle hanno colonne ordinate in modo equivalente, il che non è garantito.
Risposta:
SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.id = t2.id;
Può essere ricreato come segue:
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;
L'uso di una risposta UNION o UNION ALL non copre il caso limite in cui le tabelle di base hanno voci duplicate.
Spiegazione:
C'è un caso limite che UNION o UNION ALL non possono coprire. Non possiamo testarlo su mysql poiché non supporta FULL OUTER JOINs, ma possiamo illustrarlo su un database che lo supporta:
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
La soluzione 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
Fornisce una risposta errata:
id1 id2
NULL 3
NULL 4
1 NULL
2 NULL
5 5
6 6
La soluzione 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
È anche errato.
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 che questa query:
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;
Dà quanto segue:
id1 id2
1 NULL
2 NULL
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
L'ordine è diverso, ma per il resto corrisponde alla risposta corretta.
UNION ALL
soluzione. Inoltre, presenta una soluzione UNION
che potrebbe essere più lenta su tabelle di origine di grandi dimensioni a causa della deduplicazione richiesta. Infine, non verrà compilato, poiché il campo id
non esiste nella sottoquery tmp
.
UNION ALL
soluzione: ... è anche errata." Il codice che presenta esclude l'intersezione-esclusione dal join destro ( where t1.id1 is null
) che deve essere fornito in UNION ALL
. Vale a dire, la tua soluzione supera tutte le altre, solo quando una di quelle altre soluzioni è implementata in modo errato. Sulla "carineria", punto preso. Era gratuito, le mie scuse.
Lo standard SQL dice full join on
è inner join on
righe union all
senza eguali righe della tabella a sinistra estese da null union all
righe della tabella a destra prorogati i null. Vale a dire inner join on
file union all
nelle file left join on
ma non inner join on
union all
nelle file right join on
ma noninner join on
.
Vale a dire left join on
righe union all
right join on
non presenti inner join on
. O se sai che il tuo inner join on
risultato non può avere null in una particolare colonna della tabella di destra, allora " right join on
righe non in inner join on
" sono righe right join on
con la on
condizione estesa da and
quella colonna is null
.
Vale a dire file altrettanto right join on
union all
appropriate left join on
.
Da Qual è la differenza tra "INNER JOIN" e "OUTER JOIN"? :
(SQL Standard 2006 SQL / Foundation 7.7 Sintassi Regole 1, Regole generali 1 b, 3 c & d, 5 b.)