Il est intéressant de noter que la plupart des autres réponses souffrent de ces deux problèmes :
J'ai récemment écrit un article sur le sujet : Un guide complet, probablement incomplet, des différentes façons de JOINER des tables en SQL que je vais résumer ici.
Avant toute chose : Les JOINs sont des produits cartésiens
C'est pourquoi les diagrammes de Venn les expliquent de manière si inexacte, parce qu'une JOIN crée une produit cartésien entre les deux tables jointes. Wikipedia l'illustre joliment :
La syntaxe SQL pour les produits cartésiens est CROSS JOIN
. Par exemple :
SELECT *
-- This just generates all the days in January 2017
FROM generate_series(
'2017-01-01'::TIMESTAMP,
'2017-01-01'::TIMESTAMP + INTERVAL '1 month -1 day',
INTERVAL '1 day'
) AS days(day)
-- Here, we're combining all days with all departments
CROSS JOIN departments
qui combine toutes les lignes d'une table avec toutes les lignes de l'autre table :
Source :
+--------+ +------------+
| day | | department |
+--------+ +------------+
| Jan 01 | | Dept 1 |
| Jan 02 | | Dept 2 |
| ... | | Dept 3 |
| Jan 30 | +------------+
| Jan 31 |
+--------+
Résultat :
+--------+------------+
| day | department |
+--------+------------+
| Jan 01 | Dept 1 |
| Jan 01 | Dept 2 |
| Jan 01 | Dept 3 |
| Jan 02 | Dept 1 |
| Jan 02 | Dept 2 |
| Jan 02 | Dept 3 |
| ... | ... |
| Jan 31 | Dept 1 |
| Jan 31 | Dept 2 |
| Jan 31 | Dept 3 |
+--------+------------+
Si nous écrivons simplement une liste de tableaux séparés par des virgules, nous obtiendrons la même chose :
-- CROSS JOINing two tables:
SELECT * FROM table1, table2
INNER JOIN (Theta-JOIN)
Un site INNER JOIN
est juste un filtre CROSS JOIN
où le prédicat du filtre est appelé Theta
en algèbre relationnelle.
Par exemple :
SELECT *
-- Same as before
FROM generate_series(
'2017-01-01'::TIMESTAMP,
'2017-01-01'::TIMESTAMP + INTERVAL '1 month -1 day',
INTERVAL '1 day'
) AS days(day)
-- Now, exclude all days/departments combinations for
-- days before the department was created
JOIN departments AS d ON day >= d.created_at
Notez que le mot-clé INNER
est facultative (sauf dans MS Access).
( consultez l'article pour des exemples de résultats )
EQUI JOIN
Un type particulier de Theta-JOIN est equi JOIN, que nous utilisons le plus. Le prédicat joint la clé primaire d'une table avec la clé étrangère d'une autre table. Si nous utilisons le prédicat Base de données Sakila à titre d'illustration, on peut écrire :
SELECT *
FROM actor AS a
JOIN film_actor AS fa ON a.actor_id = fa.actor_id
JOIN film AS f ON f.film_id = fa.film_id
Cela combine tous les acteurs avec leurs films.
Ou aussi, sur certaines bases de données :
SELECT *
FROM actor
JOIN film_actor USING (actor_id)
JOIN film USING (film_id)
Le site USING()
permet de spécifier une colonne qui doit être présente de chaque côté des tables d'une opération JOIN et crée un prédicat d'égalité sur ces deux colonnes.
JOINT NATUREL
D'autres réponses ont listé ce "type de JOIN" séparément, mais cela n'a pas de sens. C'est juste une forme de sucre syntaxique pour equi JOIN, qui est un cas spécial de Theta-JOIN ou INNER JOIN. NATURAL JOIN rassemble simplement tous les colonnes qui sont communes aux deux tables jointes et les jointures USING()
ces colonnes. Ce qui n'est presque jamais utile, à cause des correspondances accidentelles (comme les LAST_UPDATE
dans les colonnes de l Base de données Sakila ).
Voici la syntaxe :
SELECT *
FROM actor
NATURAL JOIN film_actor
NATURAL JOIN film
OUTER JOIN
Maintenant, OUTER JOIN
est un peu différent de INNER JOIN
car il crée un UNION
de plusieurs produits cartésiens. On peut écrire :
-- Convenient syntax:
SELECT *
FROM a LEFT JOIN b ON <predicate>
-- Cumbersome, equivalent syntax:
SELECT a.*, b.*
FROM a JOIN b ON <predicate>
UNION ALL
SELECT a.*, NULL, NULL, ..., NULL
FROM a
WHERE NOT EXISTS (
SELECT * FROM b WHERE <predicate>
)
Personne ne veut écrire cette dernière, donc nous écrivons OUTER JOIN
(ce qui est généralement mieux optimisé par les bases de données).
Comme INNER
le mot-clé OUTER
est facultatif, ici.
OUTER JOIN
existe en trois saveurs :
-
LEFT [ OUTER ] JOIN
: La table gauche du JOIN
est ajoutée à l'union comme indiqué ci-dessus.
-
RIGHT [ OUTER ] JOIN
: La table de droite du JOIN
est ajoutée à l'union comme indiqué ci-dessus.
-
FULL [ OUTER ] JOIN
: Les deux tableaux du JOIN
sont ajoutés à l'union comme indiqué ci-dessus.
Tous ces éléments peuvent être combinés avec le mot-clé USING()
ou avec NATURAL
( J'ai en fait un cas réel d'utilisation d'un NATURAL FULL JOIN
récemment )
Syntaxes alternatives
Il y a quelques syntaxes historiques, dépréciées dans Oracle et SQL Server, qui supportaient OUTER JOIN
déjà avant que la norme SQL ait une syntaxe pour cela :
-- Oracle
SELECT *
FROM actor a, film_actor fa, film f
WHERE a.actor_id = fa.actor_id(+)
AND fa.film_id = f.film_id(+)
-- SQL Server
SELECT *
FROM actor a, film_actor fa, film f
WHERE a.actor_id *= fa.actor_id
AND fa.film_id *= f.film_id
Cela dit, n'utilisez pas cette syntaxe. Je l'ai simplement listée ici pour que vous puissiez la reconnaître dans les anciens articles de blog / le code hérité.
Partitionné OUTER JOIN
Peu de gens le savent, mais le standard SQL spécifie des partitions. OUTER JOIN
(et Oracle l'implémente). Vous pouvez écrire des choses comme ceci :
WITH
-- Using CONNECT BY to generate all dates in January
days(day) AS (
SELECT DATE '2017-01-01' + LEVEL - 1
FROM dual
CONNECT BY LEVEL <= 31
),
-- Our departments
departments(department, created_at) AS (
SELECT 'Dept 1', DATE '2017-01-10' FROM dual UNION ALL
SELECT 'Dept 2', DATE '2017-01-11' FROM dual UNION ALL
SELECT 'Dept 3', DATE '2017-01-12' FROM dual UNION ALL
SELECT 'Dept 4', DATE '2017-04-01' FROM dual UNION ALL
SELECT 'Dept 5', DATE '2017-04-02' FROM dual
)
SELECT *
FROM days
LEFT JOIN departments
PARTITION BY (department) -- This is where the magic happens
ON day >= created_at
Des parties du résultat :
+--------+------------+------------+
| day | department | created_at |
+--------+------------+------------+
| Jan 01 | Dept 1 | | -- Didn't match, but still get row
| Jan 02 | Dept 1 | | -- Didn't match, but still get row
| ... | Dept 1 | | -- Didn't match, but still get row
| Jan 09 | Dept 1 | | -- Didn't match, but still get row
| Jan 10 | Dept 1 | Jan 10 | -- Matches, so get join result
| Jan 11 | Dept 1 | Jan 10 | -- Matches, so get join result
| Jan 12 | Dept 1 | Jan 10 | -- Matches, so get join result
| ... | Dept 1 | Jan 10 | -- Matches, so get join result
| Jan 31 | Dept 1 | Jan 10 | -- Matches, so get join result
L'idée est que toutes les lignes de la partie partitionnée de la jointure se retrouveront dans le résultat, que la méthode de partitionnement soit ou non utilisée. JOIN
correspond à tout ce qui se trouve de "l'autre côté du JOIN". Pour faire court : il s'agit de remplir les données éparses dans les rapports. Très utile !
SEMI JOIN
Sérieusement ? Aucune autre réponse n'a eu ça ? Bien sûr que non, parce qu'elle n'a pas de syntaxe native en SQL, malheureusement (tout comme ANTI JOIN ci-dessous). Mais nous pouvons utiliser IN()
et EXISTS()
pour trouver, par exemple, tous les acteurs qui ont joué dans des films :
SELECT *
FROM actor a
WHERE EXISTS (
SELECT * FROM film_actor fa
WHERE a.actor_id = fa.actor_id
)
Le site WHERE a.actor_id = fa.actor_id
agit comme le prédicat de semi-jonction. Si vous ne le croyez pas, vérifiez les plans d'exécution, par exemple dans Oracle. Vous verrez que la base de données exécute une opération SEMI JOIN, et non pas l'opération EXISTS()
prédicat.
ANTI JOIN
C'est exactement le contraire de SEMI JOIN ( veillez à ne pas utiliser NOT IN
bien que car il comporte un avertissement important)
Voici tous les acteurs sans film :
SELECT *
FROM actor a
WHERE NOT EXISTS (
SELECT * FROM film_actor fa
WHERE a.actor_id = fa.actor_id
)
Certaines personnes (en particulier les utilisateurs de MySQL) écrivent également ANTI JOIN comme ceci :
SELECT *
FROM actor a
LEFT JOIN film_actor fa
USING (actor_id)
WHERE film_id IS NULL
Je pense que la raison historique est la performance.
JOINT LATERAL
OMG, celui-là est trop cool. Je suis le seul à le mentionner ? Voici une requête cool :
SELECT a.first_name, a.last_name, f.*
FROM actor AS a
LEFT OUTER JOIN LATERAL (
SELECT f.title, SUM(amount) AS revenue
FROM film AS f
JOIN film_actor AS fa USING (film_id)
JOIN inventory AS i USING (film_id)
JOIN rental AS r USING (inventory_id)
JOIN payment AS p USING (rental_id)
WHERE fa.actor_id = a.actor_id -- JOIN predicate with the outer query!
GROUP BY f.film_id
ORDER BY revenue DESC
LIMIT 5
) AS f
ON true
Il trouvera le TOP 5 des films générant des revenus par acteur. Chaque fois que vous avez besoin d'une requête TOP-N-par-chose, LATERAL JOIN
sera votre ami. Si vous êtes une personne de SQL Server, alors vous savez que JOIN
sous le nom de APPLY
SELECT a.first_name, a.last_name, f.*
FROM actor AS a
OUTER APPLY (
SELECT f.title, SUM(amount) AS revenue
FROM film AS f
JOIN film_actor AS fa ON f.film_id = fa.film_id
JOIN inventory AS i ON f.film_id = i.film_id
JOIN rental AS r ON i.inventory_id = r.inventory_id
JOIN payment AS p ON r.rental_id = p.rental_id
WHERE fa.actor_id = a.actor_id -- JOIN predicate with the outer query!
GROUP BY f.film_id
ORDER BY revenue DESC
LIMIT 5
) AS f
OK, peut-être que c'est de la triche, parce qu'un LATERAL JOIN
ou APPLY
est en réalité une "sous-requête corrélée" qui produit plusieurs lignes. Mais si on autorise les "sous-requêtes corrélées", on peut aussi parler de...
MULTISET
Ceci n'est réellement implémenté que par Oracle et Informix (à ma connaissance), mais il peut être émulé dans PostgreSQL en utilisant des tableaux et/ou XML et dans SQL Server en utilisant XML.
MULTISET
produit une sous-requête corrélée et imbrique le jeu de lignes résultant dans la requête externe. La requête ci-dessous sélectionne tous les acteurs et, pour chacun d'eux, rassemble leurs films dans une collection imbriquée :
SELECT a.*, MULTISET (
SELECT f.*
FROM film AS f
JOIN film_actor AS fa USING (film_id)
WHERE a.actor_id = fa.actor_id
) AS films
FROM actor
Comme vous l'avez vu, il existe d'autres types de JOIN que le "classique". INNER
, OUTER
et CROSS JOIN
qui sont habituellement mentionnés. Plus de détails dans mon article . Et s'il vous plaît, arrêtez d'utiliser des diagrammes de Venn pour les illustrer.