web-dev-qa-db-fra.com

Comment faire un FULL OUTER JOIN dans 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?

581
Spencer

Vous n’avez pas de FULL JOINS sur MySQL, mais vous pouvez certainement les émuler .

Pour un échantillon de code transcrit à partir de this SO question 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 dans les cas spéciaux où une opération FULL OUTER JOIN ne produirait aucune ligne en double. La requête ci-dessus dépend de l'opérateur UNION 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 en utilisant un opérateur UNION ALL pour combiner les deux ensembles. Dans le cas plus général, où FULL OUTER JOIN renvoie des lignes en double, nous pouvons procéder comme suit:

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
591
Pablo Santa Cruz

La réponse que Pablo Santa Cruz a donnée est correcte; Cependant, au cas où quelqu'un trébucherait sur cette page et voudrait plus de précisions, voici une ventilation détaillée.

Exemple de tableaux

Supposons que nous ayons les tables suivantes:

-- t1
id  name
1   Tim
2   Marta

-- t2
id  name
1   Tim
3   Katarina

Joints intérieurs

Une jointure interne, comme ceci:

SELECT *
FROM `t1`
INNER JOIN `t2` ON `t1`.`id` = `t2`.`id`;

Nous obtiendrions uniquement 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.

Jointures extérieures

Les jointures externes, quant à elles, permettent de rechercher des enregistrements qui ne correspondent peut-être pas à l'autre table. En tant que tel, vous devez spécifier quel côté de la jointure est autorisé à avoir un enregistrement manquant.

LEFT JOIN et RIGHT JOIN sont un raccourci pour LEFT OUTER JOIN et RIGHT OUTER JOIN; Je vais utiliser leurs noms complets ci-dessous pour renforcer le concept de jointure externe par opposition à une jointure interne.

Jointure externe gauche

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

Jointure extérieure droite

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

Jointure complète

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 NULL des deux côtés sans 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 supporte pas. Nous pouvons l'imiter en faisant une UNION composée de gauche et de 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 comme signifiant "exécutez ces deux requêtes, puis empilez 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 est à noter qu’une variable UNION dans MySQL éliminera les doublons exacts: Tim apparaîtrait dans les deux requêtes ici, mais le résultat de la UNION ne le liste qu’une seule fois. Mon collègue gourou de la base de données pense qu'il ne faut pas compter sur ce comportement. Donc, pour être plus explicite à ce sujet, nous pourrions ajouter une clause WHERE à 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 les doublons pour une raison quelconque, vous pourriez utiliser UNION ALL.

334
Nathan Long

L'utilisation d'une requête union supprimera les doublons, ce qui est différent du comportement de _full outer join_ qui n'élimine 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
_

Ceci est le résultat de l'utilisation de left et _right Join_ avec union:

_value | value
------+-------
Null  | 5 
1     | 1
2     | 2
4     | Null
_

[SQL Fiddle]

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 à celui attendu:

_value | value
------+-------
1     | 1
2     | 2
2     | 2
4     | NULL
4     | NULL
NULL  | 5
_

[SQL Fiddle]


@ Steve Chambers[De commentaires, avec beaucoup merci!]
Remarque: Il peut s'agir de la meilleure solution, tant pour l'efficacité que pour générer les mêmes résultats qu'un _FULL OUTER JOIN_. Cet article de blog explique aussi bien - pour citer la méthode 2: "Ceci gère correctement les lignes en double et n'inclut pas 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 conserver. Cela peut s'avérer beaucoup plus efficace sur les ensembles de résultats volumineux, car il n'est pas nécessaire de trier ni de supprimer les doublons. "


J'ai décidé d'ajouter une autre solution issue de la visualisation _full outer join_ et des mathématiques, ce n'est pas mieux que ci-dessus mais plus lisible:

Une jointure externe complète signifie _(t1 ∪ t2)_: tout en _t1_ ou en _t2_
_(t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only_: tous les deux dans _t1_ et _t2_ plus tous dans _t1_ qui ne sont pas dans _t2_ et plus tous dans _t2_ qui ne sont pas dans _t1_:

_-- (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)
_

[SQL Fiddle]

30
shA.t

Dans SQLite, vous devriez 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
4
Rami Jamleh

Aucune des réponses ci-dessus n'est en réalité correcte, car elles ne suivent pas la sémantique lorsqu'il existe des valeurs dupliquées.

Pour une requête telle que (de this duplicate ):

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 les valeurs NULL (qui peuvent également l'être), utilisez l'opérateur de comparaison sécurisé NULL-, <=> plutôt que =.

4
Gordon Linoff

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 MySql simplification de la jointure externe , la jointure droite est convertie en jointure gauche équivalente en commutant t1 et t2 dans les clauses FROM et ON de la requête. Ainsi, l’optimiseur de requêtes MySql traduit la requête initiale 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’ya aucun mal à écrire la requête originale telle quelle, mais disons si vous avez des prédicats comme la clause WHERE, qui est un avant-join prédicat ou AND sur la clause ON, qui est un pendant une jointure = prédicat, alors vous voudrez peut-être jeter un regard sur le diable; qui est dans les détails.

L’optimiseur de requêtes MySql vérifie régulièrement les prédicats s’ils sont , null-rejetés . Null-Rejected Definition and Examples Maintenant, si vous avez fait la jointure droite, mais avec le prédicat WHERE sur la colonne de t1, vous courez le risque de tomber dans un scénario null-rejeté .

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 en suivant par l’optimiseur de requêtes

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'

Donc, 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 en tant que colonne NOT NULL, cette requête sera null-rejetée .

Toute jointure externe (gauche, droite, complète) qui est null-rejeté est convertie en jointure interne par MySql.

Ainsi, les résultats que vous pouvez attendre pourraient être complètement différents de ce que MySQL renvoie. Vous pourriez penser que c'est un bogue avec RIGHT JOIN de MySQL, mais ce n'est pas correct. C'est exactement comment fonctionne l'optimiseur de requêtes MySql. Le développeur responsable doit donc tenir compte de ces nuances lors de la construction de la requête.

4
Akshayraj Kore

Vous pouvez faire ce qui suit:

(SELECT 
    *
FROM
    table1 t1
        LEFT JOIN
    table2 t2 ON t1.id = t2.id
WHERE
    t2.id IS NULL)
UNION ALL
 (SELECT 
    *
FROM
    table1 t1
        RIGHT JOIN
    table2 t2 ON t1.id = t2.id
WHERE
    t1.id IS NULL);
3
lolo

Requête modifiée 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 
3
a20
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
0
Alex Pliutau

qu'avez-vous dit à propos de Cross join solution?

SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2 
ON 1=1;
0
Super Mario