Je veux faire une jointure externe complète dans MySQL. Est-ce possible? Une jointure externe complète est-elle prise en charge par MySQL?
Je veux faire une jointure externe complète dans MySQL. Est-ce possible? Une jointure externe complète est-elle prise en charge par MySQL?
Réponses:
Vous n'avez pas FULL JOINS sur MySQL, mais vous pouvez certainement les émuler .
Pour un exemple de code transcrit à partir de cette question SO, vous avez:
avec deux tables 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 requête ci-dessus fonctionne pour les cas spéciaux où une opération FULL OUTER JOIN ne produira pas de lignes en double. La requête ci-dessus dépend de l' UNION
opérateur set pour supprimer les lignes en double introduites par le modèle de requête. Nous pouvons éviter d'introduire des lignes en double en utilisant un modèle anti-jointure pour la deuxième requête, puis utiliser un opérateur d'ensemble UNION ALL pour combiner les deux ensembles. Dans le cas plus général, où une FULL OUTER JOIN renverrait des lignes en double, nous pouvons le faire:
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
et t2
, la requête dans cette réponse renvoie un jeu de résultats qui émule FULL OUTER JOIN. Mais dans le cas plus général, par exemple, la liste SELECT ne contient pas suffisamment de colonnes / expressions pour rendre les lignes retournées uniques, alors ce modèle de requête est insuffisant pour reproduire l'ensemble qui serait produit par a FULL OUTER JOIN
. Pour obtenir une émulation plus fidèle, nous aurions besoin d'un UNION ALL
opérateur d'ensemble et l'une des requêtes aurait besoin d'un modèle anti-jointure . Le commentaire de Pavle Lekic (ci-dessus) donne le modèle de requête correct .
La réponse que Pablo Santa Cruz a donnée est correcte; cependant, au cas où quelqu'un serait tombé sur cette page et voudrait plus de précisions, voici une ventilation détaillée.
Supposons que nous ayons les tableaux suivants:
-- t1
id name
1 Tim
2 Marta
-- t2
id name
1 Tim
3 Katarina
Une jointure interne, comme ceci:
SELECT *
FROM `t1`
INNER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Nous obtiendrait seulement les enregistrements qui apparaissent dans les deux tableaux, comme ceci:
1 Tim 1 Tim
Les jointures internes n'ont pas de direction (comme gauche ou droite) car elles sont explicitement bidirectionnelles - nous avons besoin d'une correspondance des deux côtés.
Les jointures externes, d'autre part, servent à rechercher des enregistrements qui peuvent ne pas avoir de correspondance dans l'autre table. En tant que tel, vous devez spécifier de quel côté de la jointure est autorisé à avoir un enregistrement manquant.
LEFT JOIN
et RIGHT JOIN
sont un raccourci pour LEFT OUTER JOIN
etRIGHT OUTER JOIN
; J'utiliserai leurs noms complets ci-dessous pour renforcer le concept de jointures externes vs jointures internes.
Une jointure externe gauche, comme ceci:
SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
... nous obtiendrait tous les enregistrements de la table de gauche, qu'ils aient ou non une correspondance dans la table de droite, comme ceci:
1 Tim 1 Tim
2 Marta NULL NULL
Une jointure externe droite, comme ceci:
SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
... nous obtiendrait tous les enregistrements de la table de droite, qu'ils aient ou non une correspondance dans la table de gauche, comme ceci:
1 Tim 1 Tim
NULL NULL 3 Katarina
Une jointure externe complète nous donnerait tous les enregistrements des deux tables, qu'ils aient ou non une correspondance dans l'autre table, avec des valeurs NULL des deux côtés où il n'y a pas de correspondance. Le résultat ressemblerait à ceci:
1 Tim 1 Tim
2 Marta NULL NULL
NULL NULL 3 Katarina
Cependant, comme l'a souligné Pablo Santa Cruz, MySQL ne le prend pas en charge. Nous pouvons l'imiter en faisant UNION d'une jointure gauche et d'une jointure droite, comme ceci:
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`;
Vous pouvez penser à un UNION
qui signifie «exécuter ces deux requêtes, puis empiler les résultats les uns sur les autres»; certaines des lignes proviendront de la première requête et d'autres de la seconde.
Il convient de noter qu'un UNION
dans MySQL éliminera les doublons exacts: Tim apparaîtrait dans les deux requêtes ici, mais le résultat de la UNION
seule liste une fois. Mon collègue gourou de la base de données estime que ce comportement ne doit pas être invoqué. Donc, pour être plus explicite, nous pourrions ajouter une WHERE
clause à la deuxième requête:
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'autre part, si vous vouliez voir des doublons pour une raison quelconque, vous pouvez utiliser UNION ALL
.
FULL OUTER JOIN
. Il n'y a rien de mal à faire des requêtes de cette façon et à utiliser UNION pour supprimer ces doublons. Mais pour vraiment répliquer un FULL OUTER JOIN
, nous avons besoin que l'une des requêtes soit un anti-jointure.
UNION
opération supprimera ces doublons; mais il supprime également TOUTES les lignes en double, y compris les lignes en double qui seraient renvoyées par une FULL OUTER JOIN. Pour émuler a FULL JOIN b
, le motif correct est (a LEFT JOIN b) UNION ALL (b ANTI JOIN a)
.
L'utilisation d'une union
requête supprimera les doublons, ce qui est différent du comportement full outer join
qui ne supprime jamais les doublons:
[Table: t1] [Table: t2]
value value
------- -------
1 1
2 2
4 2
4 5
C'est le résultat attendu de full outer join
:
value | value
------+-------
1 | 1
2 | 2
2 | 2
Null | 5
4 | Null
4 | Null
C'est le résultat de l'utilisation left
et right Join
avec union
:
value | value
------+-------
Null | 5
1 | 1
2 | 2
4 | Null
Ma requête suggérée est:
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
Résultat de la requête ci-dessus identique au résultat attendu:
value | value
------+-------
1 | 1
2 | 2
2 | 2
4 | NULL
4 | NULL
NULL | 5
@Steve Chambers : [D'après les commentaires, merci beaucoup!]
Remarque: Cela peut être la meilleure solution, à la fois pour l'efficacité et pour générer les mêmes résultats qu'un aFULL OUTER JOIN
. Ce billet de blog l' explique également bien - pour citer la méthode 2: "Cela gère correctement les lignes en double et n'inclut rien de ce qu'il ne devrait pas. Il est nécessaire d'utiliser à laUNION ALL
place de plainUNION
, ce qui éliminerait les doublons que je veux conserver. Cette peut être beaucoup plus efficace sur les grands ensembles de résultats, car il n'est pas nécessaire de trier et de supprimer les doublons. "
J'ai décidé d'ajouter une autre solution qui vient de la full outer join
visualisation et des mathématiques, ce n'est pas mieux que ci-dessus mais plus lisible:
La jointure externe complète signifie
(t1 ∪ t2)
: tout ent1
ou ent2
(t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only
: tous les deuxt1
ett2
plus tous ceuxt1
qui ne sont past2
et plus tous ceuxt2
qui ne sont past1
:
-- (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
. Ce billet de blog l' explique également bien - pour citer la méthode 2: «Cela gère correctement les lignes en double et n'inclut rien de ce qu'il ne devrait pas. Il est nécessaire d'utiliser UNION ALL au lieu de plain UNION, ce qui éliminerait les doublons que je veux Cela peut être nettement plus efficace sur les grands ensembles de résultats, car il n'est pas nécessaire de trier et de supprimer les doublons. "
MySql n'a pas de syntaxe FULL-OUTER-JOIN. Vous devez émuler en faisant à la fois LEFT JOIN et RIGHT JOIN comme suit-
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
Mais MySql n'a pas non plus de syntaxe RIGHT JOIN. Selon la simplification de la jointure externe de MySql , la jointure droite est convertie en la jointure gauche équivalente en commutant les t1 et t2 dans la clause FROM
and ON
de la requête. Ainsi, l'optimiseur de requête MySql traduit la requête d'origine en ce qui suit -
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t2
LEFT JOIN t1 ON t2.id = t1.id
Maintenant, il n'y a aucun mal à écrire la requête d'origine telle quelle, mais dites que si vous avez des prédicats comme la clause WHERE, qui est un prédicat avant jointure ou un prédicat AND sur la ON
clause, qui est un prédicat pendant la jointure , alors vous pourrait vouloir jeter un oeil au diable; ce qui est en détail.
L'optimiseur de requêtes MySql vérifie régulièrement les prédicats s'ils sont rejetés par null . Maintenant, si vous avez effectué le RIGHT JOIN, mais avec le prédicat WHERE sur la colonne de t1, vous risquez de courir dans un scénario rejeté par null .
Par exemple, la requête suivante -
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'
est traduit comme suit par l'Optimiseur de requête -
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'
Ainsi, l'ordre des tables a changé, mais le prédicat est toujours appliqué à t1, mais t1 est maintenant dans la clause 'ON'. Si t1.col1 est défini comme NOT NULL
colonne, cette requête sera rejetée par null .
Toute jointure externe (gauche, droite, pleine) qui est rejetée par null est convertie en jointure interne par MySql.
Ainsi, les résultats que vous attendez peuvent être complètement différents de ce que le MySql renvoie. Vous pourriez penser que c'est un bug avec RIGHT JOIN de MySql, mais ce n'est pas correct. C'est juste comment fonctionne l'optimiseur de requêtes MySql. Le développeur en charge doit donc faire attention à ces nuances lors de la construction de la requête.
Dans SQLite, vous devez faire ceci:
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
Aucune des réponses ci-dessus n'est réellement correcte, car elles ne suivent pas la sémantique lorsqu'il y a des valeurs dupliquées.
Pour une requête telle que (à partir de ce doublon ):
SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.Name = t2.Name;
L'équivalent correct est:
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;
Si vous avez besoin que cela fonctionne avec des NULL
valeurs (qui peuvent également être nécessaires), utilisez plutôt l' NULL
opérateur de comparaison -safe .<=>
=
FULL OUTER JOIN
chaque fois que la name
colonne est nulle. La union all
requête avec un motif anti-jointure doit reproduire correctement le comportement de jointure externe, mais la solution la plus appropriée dépend du contexte et des contraintes actives sur les tables.
union all
, mais cette réponse manque un modèle anti-jointure dans la première ou la deuxième requête qui conservera les doublons existants mais empêche d'en ajouter de nouveaux. Selon le contexte, d'autres solutions (comme celle-ci) pourraient être plus appropriées.
Modification de la requête de shA.t pour plus de clarté:
-- 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
que pensez-vous de la solution de jointure croisée
SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2
ON 1=1;
select (select count(*) from t1) * (select count(*) from t2))
lignes dans l'ensemble de résultats.
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
C'est également possible, mais vous devez mentionner les mêmes noms de champs dans 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
Je corrige la réponse, et les travaux incluent toutes les lignes (basé sur la réponse 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
qui n'ont pas de correspondance tableb
et vice versa. Vous essayez de UNION ALL
, ce qui ne fonctionnerait que si ces deux tables ont des colonnes ordonnées de manière équivalente, ce qui n'est pas garanti.
Réponse:
SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.id = t2.id;
Peut être recréé comme suit:
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'utilisation d'une réponse UNION ou UNION ALL ne couvre pas le cas de bord où les tables de base ont des entrées en double.
Explication:
Il y a un cas de bord qu'un UNION ou UNION ALL ne peut pas couvrir. Nous ne pouvons pas tester cela sur mysql car il ne prend pas en charge FULL OUTER JOINs, mais nous pouvons illustrer cela sur une base de données qui le prend en charge:
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 solution 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
Donne une réponse incorrecte:
id1 id2
NULL 3
NULL 4
1 NULL
2 NULL
5 5
6 6
La solution 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
Est également incorrect.
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
Attendu que cette requête:
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;
Donne ce qui suit:
id1 id2
1 NULL
2 NULL
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
L'ordre est différent, mais correspond sinon à la bonne réponse.
UNION ALL
solution. En outre, il présente une solution UNION
qui serait plus lente sur les grandes tables source en raison de la déduplication requise. Enfin, il ne serait pas compilé, car le champ id
n'existe pas dans la sous-requête tmp
.
UNION ALL
solution: ... est également incorrecte." Le code que vous présentez exclut l'intersection-exclusion de la jointure droite ( where t1.id1 is null
) qui doit être fournie dans le UNION ALL
. Autrement dit, votre solution l'emporte sur toutes les autres, uniquement lorsque l'une de ces autres solutions est incorrectement implémentée. Sur la "gentillesse", point pris. C'était gratuit, mes excuses.
Le standard SQL dit full join on
est inner join on
lignes union all
inégalées lignes de la table gauche prolongée par nulls union all
lignes de table droite étendue par nulls. C'est-à-dire des inner join on
rangées de union all
rangées left join on
mais pas de inner join on
union all
rangées right join on
mais pas inner join on
.
C'est-à-dire que les left join on
rangées union all
right join on
ne sont pas dedans inner join on
. Ou si vous savez que votre inner join on
résultat ne peut pas avoir la valeur null dans une colonne de tableau de droite particulière, alors "les right join on
lignes qui ne inner join on
sont pas " sont des lignes right join on
avec la on
condition étendue par and
cette colonne is null
.
C'est-à-dire right join on
union all
des left join on
lignes appropriées de manière similaire .
De Quelle est la différence entre «INNER JOIN» et «OUTER JOIN»? :
(SQL Standard 2006 SQL / Foundation 7.7 Syntaxe Rules 1, General Rules 1 b, 3 c & d, 5 b.)