Clause INNER JOIN ON vs WHERE


941

Pour simplifier, supposez que tous les champs pertinents le sont NOT NULL.

Tu peux faire:

SELECT
    table1.this, table2.that, table2.somethingelse
FROM
    table1, table2
WHERE
    table1.foreignkey = table2.primarykey
    AND (some other conditions)

Ou sinon:

SELECT
    table1.this, table2.that, table2.somethingelse
FROM
    table1 INNER JOIN table2
    ON table1.foreignkey = table2.primarykey
WHERE
    (some other conditions)

Ces deux fonctionnent-ils de la même manière MySQL?


1
@Marco: le voici
Alexander Malakhov


18
Si j'ai bien compris, la première variante est la syntaxe implicite ANSI SQL-89 et la deuxième variante est la syntaxe de jointure explicite ANSI SQL-92. Les deux entraîneront le même résultat dans les implémentations SQL conformes et les deux entraîneront le même plan de requête dans les implémentations SQL bien faites. Personnellement, je préfère la syntaxe SQL-89 mais beaucoup de gens préfèrent la syntaxe SQL-92.
Mikko Rantalainen

11
@Hogan Je désignais les noms officiels de différentes syntaxes. Aucune des réponses n'énumérant explicitement les noms complets, j'ai donc décidé de les ajouter en tant que commentaires. Cependant, mon commentaire n'a pas répondu à la question réelle, j'ai donc ajouté cela comme commentaire, pas comme réponse. (Les réponses ayant fait l'objet d'un vote élevé ont des allégations telles que "INNER JOIN est une syntaxe ANSI" et "la syntaxe implicite ANSI est plus ancienne" qui ne dit rien du tout car les deux syntaxes sont des syntaxes ANSI différentes.)
Mikko Rantalainen

Réponses:


710

INNER JOIN est la syntaxe ANSI que vous devez utiliser.

Il est généralement considéré comme plus lisible, surtout lorsque vous joignez de nombreuses tables.

Il peut également être facilement remplacé par un dès OUTER JOINque le besoin s'en fait sentir.

La WHEREsyntaxe est davantage orientée modèle relationnel.

Le résultat de deux tables JOINed est un produit cartésien des tables auxquelles un filtre est appliqué qui ne sélectionne que les lignes avec des colonnes de jointure correspondantes.

Il est plus facile de voir cela avec la WHEREsyntaxe.

Comme pour votre exemple, dans MySQL (et dans SQL en général), ces deux requêtes sont synonymes.

Notez également que MySQL a également une STRAIGHT_JOINclause.

En utilisant cette clause, vous pouvez contrôler l' JOINordre: quelle table est analysée dans la boucle externe et laquelle se trouve dans la boucle interne.

Vous ne pouvez pas contrôler cela dans MySQL en utilisant la WHEREsyntaxe.


10
Merci, Quassnoi. Vous avez beaucoup de détails dans vos années; est-il juste de dire que "oui, ces requêtes sont équivalentes, mais vous devez utiliser la jointure interne car elle est plus lisible et plus facile à modifier"?
allyourcode le

8
@allyourcode: pour Oracle, SQL Server, MySQLet PostgreSQL- oui. Pour d'autres systèmes, probablement aussi, mais vous feriez mieux de vérifier.
Quassnoi

13
FWIW, l'utilisation de virgules avec des conditions de jointure dans la WHEREclause est également dans la norme ANSI.
Bill Karwin

1
@Bill Karwin: le JOINmot clé ne faisait pas partie des normes propriétaires jusqu'à ce que le passé soit plus récent qu'il n'y paraît. Il n'a fait son chemin Oraclequ'en version 9et en PostgreSQLversion 7.2(tous deux publiés en 2001). L'apparition de ce mot-clé faisait partie de ANSIl'adoption standard, et c'est pourquoi ce mot-clé est généralement associé à ANSI, malgré le fait que ce dernier prend également en charge la virgule comme synonyme CROSS JOIN.
Quassnoi

9
Néanmoins, ANSI SQL-89 spécifiait les jointures à effectuer avec des virgules et des conditions dans une WHEREclause (sans conditions, une jointure équivaut à une jointure croisée, comme vous l'avez dit). ANSI SQL-92 a ajouté le JOINmot clé et la syntaxe associée, mais la syntaxe de style virgule est toujours prise en charge pour la compatibilité descendante.
Bill Karwin

182

D'autres ont souligné que cela INNER JOINaide la lisibilité humaine, et c'est une priorité absolue, je suis d'accord.
Permettez-moi d'essayer d'expliquer pourquoi la syntaxe de jointure est plus lisible.

Une SELECTrequête de base est la suivante:

SELECT stuff
FROM tables
WHERE conditions

La SELECTclause nous dit ce que nous recevons; la FROMclause nous dit d' nous la recevons, et la WHEREclause nous dit lesquelles nous obtenons.

JOIN est une déclaration sur les tables, comment elles sont liées ensemble (conceptuellement, en fait, dans une seule table).

Tous les éléments de requête qui contrôlent les tables - d'où nous obtenons des éléments - appartiennent sémantiquement à la FROMclause (et bien sûr, c'est là que les JOINéléments vont). Mettre des éléments de jonction dans la WHEREclause confond le qui et le d' , c'est pourquoi la JOINsyntaxe est préférée.


7
Merci d'avoir clarifié pourquoi la jointure intérieure est préférée Carl. Je pense que votre ans était implicite dans les autres, mais explicite est généralement mieux (oui, je suis un fan de Python).
allyourcode

2
La sémantique de ON et WHERE signifie que pour JOINs après la dernière OUTER JOIN , peu importe ce que vous utilisez. Bien que vous caractérisiez ON comme faisant partie du JOIN, il s'agit également d' un filtrage après un produit cartésien. Les deux ON et WHERE filtre un produit cartésien. Mais ON ou une sous-sélection avec WHERE doit être utilisée avant la dernière OUTER JOIN. (Les JOINs ne sont pas des paires de colonnes "on". Deux tables peuvent être JOINES À n'importe quelle condition. C'est juste une façon d'interpréter spécifiquement
JOINs

Même lorsque vous utilisez WHERE pour le même effet de INNER JOIN, vous allez mentionner vos deux tables dans la partie FROM de la requête. Donc, fondamentalement, vous indiquez toujours où vous obtenez vos données dans la clause FROM, donc je suppose que vous ne pouvez pas nécessairement dire qu'il "confond le qui et le d'où"
cybergeek654

@ArsenKhachaturyan Ce n'est pas parce qu'un mot-clé ou un identifiant est utilisé dans du texte qu'il s'agit d'un code et qu'il a besoin d'un format de code. C'est un choix de mise en forme qui pourrait aller de n'importe quelle façon et s'il est raisonnable de le modifier ici, il est alors justifié que chaque publication soit constamment modifiée vers l'autre format - c'est-à-dire, ce n'est pas justifiable. (De plus, le format de code par mot en ligne peut être difficile à lire.) Même chose pour les sauts de paragraphe ici - ils ne sont pas particulièrement clairs. Idem avec «qui» vs «ça». Et les noms des langages de programmation ne doivent pas être au format de code. PS Vous avez ajouté un saut de ligne par erreur.
philipxy

@philipxy comme vous l'avez dit "ça ne veut pas dire ...", mais évidemment cela ne signifiait pas non plus qu'il ne pouvait pas être marqué avec un mot-clé de code. Oui, c'est un choix à faire, mais beaucoup de messages sont faits sans le savoir. Par conséquent, ma décision d'apporter les modifications ne vise pas à casser quoi que ce soit, mais à la rendre plus lisible. Si vous avez remarqué une interruption après la mise en forme des modifications, désolé pour cela, et vous pouvez évidemment annuler ces modifications.
Arsen Khachaturyan

143

Appliquer des instructions conditionnelles dans ON / WHERE

Ici, j'ai expliqué les étapes de traitement des requêtes logiques.


Référence: Inside Microsoft® SQL Server ™ 2005 T-SQL Querying
Publisher: Microsoft Press
Pub Date: 07 mars 2006
Print ISBN-10: 0-7356-2313-9
Print ISBN-13: 978-0-7356-2313-2
Pages: 640

À l'intérieur de Microsoft® SQL Server ™ 2005 Requête T-SQL

(8)  SELECT (9) DISTINCT (11) TOP <top_specification> <select_list>
(1)  FROM <left_table>
(3)       <join_type> JOIN <right_table>
(2)       ON <join_condition>
(4)  WHERE <where_condition>
(5)  GROUP BY <group_by_list>
(6)  WITH {CUBE | ROLLUP}
(7)  HAVING <having_condition>
(10) ORDER BY <order_by_list>

Le premier aspect notable de SQL différent des autres langages de programmation est l'ordre dans lequel le code est traité. Dans la plupart des langages de programmation, le code est traité dans l'ordre dans lequel il est écrit. En SQL, la première clause qui est traitée est la clause FROM, tandis que la clause SELECT, qui apparaît en premier, est traitée presque en dernier.

Chaque étape génère une table virtuelle qui est utilisée comme entrée pour l'étape suivante. Ces tables virtuelles ne sont pas disponibles pour l'appelant (application client ou requête externe). Seule la table générée par l'étape finale est renvoyée à l'appelant. Si une certaine clause n'est pas spécifiée dans une requête, l'étape correspondante est simplement ignorée.

Brève description des phases de traitement des requêtes logiques

Ne vous inquiétez pas trop si la description des étapes ne semble pas avoir beaucoup de sens pour l'instant. Ils sont fournis à titre de référence. Les sections qui suivent l'exemple de scénario couvriront les étapes de manière beaucoup plus détaillée.

  1. FROM: un produit cartésien (jointure croisée) est effectué entre les deux premières tables de la clause FROM et, par conséquent, la table virtuelle VT1 est générée.

  2. ON: Le filtre ON est appliqué à VT1. Seules les lignes pour lesquelles la <join_condition>valeur est TRUE sont insérées dans VT2.

  3. OUTER (jointure): si une OUTER JOIN est spécifiée (par opposition à une CROSS JOIN ou une INNER JOIN), les lignes de la ou des tables préservées pour lesquelles aucune correspondance n'a été trouvée sont ajoutées aux lignes de VT2 en tant que lignes externes, générant VT3. Si plus de deux tables apparaissent dans la clause FROM, les étapes 1 à 3 sont appliquées à plusieurs reprises entre le résultat de la dernière jointure et la table suivante de la clause FROM jusqu'à ce que toutes les tables soient traitées.

  4. WHERE: Le filtre WHERE est appliqué à VT3. Seules les lignes pour lesquelles le <where_condition>est VRAI sont insérées dans VT4.

  5. GROUP BY: Les lignes de VT4 sont organisées en groupes en fonction de la liste de colonnes spécifiée dans la clause GROUP BY. VT5 est généré.

  6. CUBE | ROLLUP: des supergroupes (groupes de groupes) sont ajoutés aux lignes de VT5, générant VT6.

  7. HAVING: Le filtre HAVING est appliqué à VT6. Seuls les groupes dont le <having_condition>est VRAI sont insérés dans VT7.

  8. SELECT: La liste SELECT est traitée, générant VT8.

  9. DISTINCT: les lignes en double sont supprimées de VT8. VT9 est généré.

  10. ORDER BY: Les lignes de VT9 sont triées selon la liste de colonnes spécifiée dans la clause ORDER BY. Un curseur est généré (VC10).

  11. TOP: Le nombre ou pourcentage spécifié de lignes est sélectionné depuis le début de VC10. La table VT11 est générée et renvoyée à l'appelant.



Par conséquent, (INNER JOIN) ON filtrera les données (le nombre de données de VT sera réduit ici même) avant d'appliquer la clause WHERE. Les conditions de jointure suivantes seront exécutées avec des données filtrées, ce qui améliore les performances. Après cela, seule la condition WHERE appliquera les conditions de filtrage.

(L'application d'instructions conditionnelles dans ON / WHERE ne fera pas beaucoup de différence dans quelques cas. Cela dépend du nombre de tables que vous avez jointes et du nombre de lignes disponibles dans chaque table de jointure)


10
"Par conséquent, (INNER JOIN) ON filtrera les données (le nombre de données de VT sera réduit ici même) avant d'appliquer la clause WHERE." Pas nécessairement. L'article concerne l' ordre logique de traitement. Lorsque vous dites qu'une implémentation particulière fera une chose avant une autre, vous parlez de l' ordre de traitement implémenté . Les implémentations sont autorisées à effectuer les optimisations qu'elles souhaitent, tant que le résultat est le même que si l'implémentation suit l'ordre logique. Joe Celko a beaucoup écrit à ce sujet sur Usenet.
Mike Sherrill 'Cat Recall'

@rafidheen "(INNER JOIN) ON filtrera les données ... avant d'appliquer la clause WHERE ... ce qui améliore les performances." Bon point. "Après cela, seule la condition WHERE appliquera les conditions de filtrage" Qu'en est-il de la clause HAVING?
James

@James Cette affirmation de rafidheen est fausse. Voir «optimisation des jointures» dans le manuel. Aussi mes autres commentaires sur cette page. (Et MikeSherrill'CatRecall''s.) Ces descriptions "logiques" décrivent la valeur du résultat, pas comment elle est réellement calculée. Et un tel comportement d'implémentation n'est pas garanti de ne pas changer.
philipxy

67

La syntaxe implicite de jointure ANSI est plus ancienne, moins évidente et déconseillée.

De plus, l'algèbre relationnelle permet l'interchangeabilité des prédicats dans la WHEREclause et la INNER JOIN, donc même les INNER JOINrequêtes avecWHERE clauses peuvent avoir les prédicats réarrangés par l'optimiseur.

Je vous recommande d'écrire les requêtes de la manière la plus lisible possible.

Parfois, cela implique de rendre le INNER JOINrelativement "incomplet" et de mettre certains des critèresWHERE simple pour rendre les listes de critères de filtrage plus faciles à maintenir.

Par exemple, au lieu de:

SELECT *
FROM Customers c
INNER JOIN CustomerAccounts ca
    ON ca.CustomerID = c.CustomerID
    AND c.State = 'NY'
INNER JOIN Accounts a
    ON ca.AccountID = a.AccountID
    AND a.Status = 1

Écrire:

SELECT *
FROM Customers c
INNER JOIN CustomerAccounts ca
    ON ca.CustomerID = c.CustomerID
INNER JOIN Accounts a
    ON ca.AccountID = a.AccountID
WHERE c.State = 'NY'
    AND a.Status = 1

Mais cela dépend, bien sûr.


16
Votre premier extrait me fait définitivement plus mal au cerveau. Est-ce que quelqu'un fait ça? Si je rencontre quelqu'un qui fait ça, est-ce que je peux le battre à la tête?
allyourcode

3
Je repère les critères là où cela a le plus de sens. Si je me joins à une table de recherche d'instantanés cohérente dans le temps (et que je n'ai pas de vue ou d'UDF qui impose la sélection d'une date valide), j'inclurai la date effective dans la jointure et non dans le WHERE car c'est moins susceptibles d'être accidentellement retirés.
Cade Roux

14
@allyourcode: bien qu'il soit rare de voir ce type de syntaxe de jointure dans les jointures internes, il est assez courant pour les jointures droites et les jointures gauches - spécifier plus de détails dans le prédicat de jointure élimine le besoin d'une sous-requête et empêche vos jointures externes d'être tournées par inadvertance en JOINTS INTÉRIEURS. (Bien que je convienne que pour INNER JOINs, je mettrais presque toujours c.State = 'NY' dans la clause WHERE)
Dave Markle

1
@allyourcode Je fais définitivement ça! Et je suis d'accord avec Cade .. Je suis curieux de savoir s'il y a une bonne raison de ne pas le faire
Arth

31

Les jointures implicites (ce que l'on appelle votre première requête) deviennent beaucoup plus confuses, difficiles à lire et difficiles à maintenir une fois que vous devez commencer à ajouter plus de tables à votre requête. Imaginez faire la même requête et le même type de jointure sur quatre ou cinq tables différentes ... c'est un cauchemar.

L'utilisation d'une jointure explicite (votre deuxième exemple) est beaucoup plus lisible et facile à entretenir.


48
Je ne pouvais pas être plus en désaccord. La syntaxe JOIN est extrêmement verbeuse et difficile à organiser. J'ai beaucoup de requêtes joignant 5, 10, voire 15 tables utilisant des jointures de clause WHERE et elles sont parfaitement lisibles. La réécriture d'une telle requête à l'aide d'une syntaxe JOIN entraîne un désordre. Ce qui montre simplement qu'il n'y a pas de bonne réponse à cette question et qu'elle dépend davantage de ce avec quoi vous êtes à l'aise.
Noah Yetter

33
Noah, je pense que vous pourriez être en minorité ici.
mat b

2
Je reçois +1 pour Matt et Noah. J'aime la diversité :). Je peux voir d'où vient Noé; la jointure interne n'ajoute rien de nouveau à la langue et est certainement plus verbeuse. D'un autre côté, cela peut rendre votre condition «où» beaucoup plus courte, ce qui signifie généralement qu'elle est plus facile à lire.
allyourcode

5
Je suppose que tout SGBD sensé traduira les deux requêtes dans le même plan d'exécution; cependant, en réalité, chaque SGBD est différent et la seule façon de savoir avec certitude est d'examiner réellement le plan d'exécution (c'est-à-dire que vous devrez le tester vous-même).
mat le

Est-il vrai que @rafidheen a suggéré dans une autre réponse (celle avec la séquence détaillée d'exécution SQL) que les JOIN sont filtrés un à la fois, ce qui réduit la taille des opérations de jointure par rapport à une jointure cartésienne complète de 3 tables ou plus, avec le filtre WHERE appliqué rétroactivement? Dans l'affirmative, cela suggérerait que JOIN offre une amélioration des performances (ainsi que des avantages dans les jointures gauche / droite, comme indiqué également dans une autre réponse).
James

26

Je soulignerai également que l'utilisation de l'ancienne syntaxe est plus sujette à erreur. Si vous utilisez des jointures internes sans clause ON, vous obtiendrez une erreur de syntaxe. Si vous utilisez l'ancienne syntaxe et oubliez l'une des conditions de jointure dans la clause where, vous obtiendrez une jointure croisée. Les développeurs corrigent souvent cela en ajoutant le mot-clé distinct (plutôt que de réparer la jointure car ils ne réalisent toujours pas que la jointure elle-même est rompue), ce qui peut sembler résoudre le problème, mais ralentira considérablement la requête.

De plus, pour la maintenance, si vous avez une jointure croisée dans l'ancienne syntaxe, comment le responsable saura-t-il si vous vouliez en avoir une (il y a des situations où des jointures croisées sont nécessaires) ou si c'est un accident qui devrait être corrigé?

Permettez-moi de vous indiquer cette question pour voir pourquoi la syntaxe implicite est mauvaise si vous utilisez des jointures gauches. Sybase * = selon Ansi Standard avec 2 tables extérieures différentes pour la même table intérieure

De plus (diatribe personnelle ici), la norme utilisant les jointures explicites a plus de 20 ans, ce qui signifie que la syntaxe de jointure implicite est dépassée depuis 20 ans. Souhaitez-vous écrire du code d'application en utilisant une syntaxe obsolète depuis 20 ans? Pourquoi voulez-vous écrire du code de base de données?


3
@HLGEM: Bien que je convienne complètement que les JOIN explicites sont meilleurs, il y a des cas où vous avez juste besoin d'utiliser l'ancienne syntaxe. Un exemple concret: ANSI JOIN est entré dans Oracle uniquement dans la version 9i qui a été publiée en 2001, et jusqu'à il y a seulement un an (16 ans à partir du moment où la norme a été publiée), je devais prendre en charge un tas d'installations 8i pour lesquelles nous avions pour publier des mises à jour critiques. Je ne voulais pas maintenir deux ensembles de mises à jour, nous avons donc développé et testé les mises à jour par rapport à toutes les bases de données, y compris 8i, ce qui signifie que nous n'avons pas pu utiliser les jointures ANSI.
Quassnoi

+1 point intéressant lorsque vous signalez que la sintax sans INNER JOIN est plus sujette aux erreurs. Je suis confus à propos de votre dernière phrase lorsque vous dites "... la norme utilisant les jointures explicites a 17 ans". proposez-vous alors d'utiliser le mot-clé INNER JOIN ou non?
Marco Demaio

1
@Marco Demaio, oui, utilisez toujours INNER JOIN ou JOIN (ces deux sont les mêmes) ou LEFT JOIN ou RIGHT JOIN ou CROSS JOIN et n'utilisez jamais les virgules implicites.
HLGEM

2
"Pourquoi voulez-vous écrire du code de base de données qui a [20 ans]?" - Je remarque que vous écrivez SQL en utilisant HAVINGce qui est «obsolète» depuis que SQL a commencé à prendre en charge les tables dérivées. Je remarque également que vous n'utilisez pas NATURAL JOINmême si je dirais que c'est devenu INNER JOIN«obsolète». Oui, vous avez vos raisons (pas besoin de les répéter ici!): Ce que je veux dire, c'est que ceux qui aiment utiliser l'ancienne syntaxe ont leurs raisons aussi et l'âge relatif de la syntaxe est peu ou pas pertinent.
quand

1
O is est toujours dans la norme (montrez-moi où il n'est pas). Donc, rien de démodé, apparemment. De plus, "plutôt que de corriger la jointure" me montre un développeur qui devrait être tenu à l'écart des SGBD en général, très loin.
Jürgen A. Erhard

12

Ils ont une signification différente, lisible par l'homme.

Cependant, selon l'optimiseur de requêtes, ils peuvent avoir la même signification pour la machine.

Vous devez toujours coder pour être lisible.

Autrement dit, s'il s'agit d'une relation intégrée, utilisez la jointure explicite. si vous correspondez à des données faiblement liées, utilisez la clause where.


11

La norme SQL: 2003 a modifié certaines règles de priorité de sorte qu'une instruction JOIN a priorité sur une jointure «virgule». Cela peut en fait changer les résultats de votre requête en fonction de la façon dont elle est configurée. Cela peut causer des problèmes à certaines personnes lorsque MySQL 5.0.12 est passé au respect de la norme.

Ainsi, dans votre exemple, vos requêtes fonctionneraient de la même manière. Mais si vous avez ajouté une troisième table: SELECT ... FROM table1, table2 JOIN table3 ON ... WHERE ...

Avant MySQL 5.0.12, table1 et table2 étaient jointes en premier, puis table3. Maintenant (5.0.12 et suivants), table2 et table3 sont jointes en premier, puis table1. Cela ne change pas toujours les résultats, mais cela peut et vous ne le réalisez peut-être même pas.

Je n'utilise plus la syntaxe "virgule", optant pour votre deuxième exemple. C'est beaucoup plus lisible de toute façon, les conditions JOIN sont avec les JOIN, pas séparées dans une section de requête séparée.


Le SQL standard n'a pas changé. MySQL avait tout simplement tort et a maintenant raison. Voir le manuel MySQL.
philipxy

4

Je sais que vous parlez de MySQL, mais de toute façon: dans Oracle 9, les jointures explicites et implicites généreraient des plans d'exécution différents. AFAIK qui a été résolu dans Oracle 10+: il n'y a plus une telle différence.


1

La syntaxe de jointure ANSI est nettement plus portable.

Je passe par une mise à niveau de Microsoft SQL Server, et je mentionnerais également que la syntaxe = * et * = pour les jointures externes dans SQL Server n'est pas prise en charge (sans mode de compatibilité) pour 2005 sql server et versions ultérieures.


2
Même dans SQL Server 2000, = et = peuvent donner des résultats erronés et ne doivent jamais être utilisés.
HLGEM

2
*= et =* n'ont jamais été ANSI et n'ont jamais été une bonne notation. C'est pourquoi ON était nécessaire - pour les JOINTS EXTÉRIEURS en l'absence de sous-sélections (qui ont été ajoutés en même temps, donc ils ne sont pas réellement nécessaires dans les
JOINTS CROISÉS ET

1

Si vous programmez souvent des procédures stockées dynamiques, vous tomberez amoureux de votre deuxième exemple (en utilisant où). Si vous avez différents paramètres d'entrée et beaucoup de dégâts de morphing, alors c'est la seule façon. Sinon, ils exécuteront tous les deux le même plan de requête, il n'y a donc aucune différence évidente dans les requêtes classiques.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.