Jointure vs sous-requête

838

Je suis un ancien utilisateur de MySQL et j'ai toujours préféré JOINla sous-requête. Mais de nos jours, tout le monde utilise la sous-requête, et je déteste ça; Je ne sais pas pourquoi.

Je n'ai pas les connaissances théoriques pour juger par moi-même s'il y a une différence. Une sous-requête est-elle aussi bonne qu'un JOINet donc n'y a-t-il rien à craindre?

Votre bon sens
la source
23
Les sous-requêtes sont parfois excellentes. Ils sont nuls en termes de performances dans MySQL. Ne les utilisez pas.
runrig
8
J'avais toujours l'impression que les sous-requêtes étaient implicitement exécutées en tant que jointures lorsqu'elles étaient disponibles dans certaines technologies DB.
Kezzer
18
Les sous-requêtes ne sont pas toujours nulles, lors de la jonction avec des tables assez grandes, la manière préférée est de faire une sous-sélection à partir de cette grande table (en limitant le nombre de lignes) puis de se joindre.
ovais.tariq
136
"de nos jours tout le monde utilise la sous-requête" [citation nécessaire]
Piskvor a quitté le bâtiment
3
Potentiellement lié (bien que beaucoup plus spécifique): stackoverflow.com/questions/141278/subqueries-vs-joins/…
Leigh Brenecki

Réponses:

191

Tiré du manuel MySQL ( 13.2.10.11 Réécriture des sous-requêtes en jointures ):

Une jointure gauche [externe] peut être plus rapide qu'une sous-requête équivalente car le serveur peut être en mesure de mieux l'optimiser, ce qui n'est pas spécifique au serveur MySQL seul.

Les sous-requêtes peuvent donc être plus lentes que LEFT [OUTER] JOIN, mais à mon avis, leur force est une lisibilité légèrement supérieure.

simhumileco
la source
45
@ user1735921 IMO ça dépend ... Généralement, c'est très important la lisibilité du code, car c'est d'une grande importance pour sa gestion ultérieure ... Rappelons-nous la fameuse déclaration de Donald Knuth: "L'optimisation prématurée est la racine de tout mal (ou du moins la plupart) dans la programmation " . Cependant, naturellement, il y a des domaines de programmation où les performances sont primordiales ... Idéalement, quand on réussit à se réconcilier :)
simhumileco
31
Dans les requêtes plus complexes, je trouve les jointures beaucoup plus faciles à lire que les sous-requêtes. les sous-requêtes se transforment en un bol de nouilles dans ma tête.
Zahra du
6
@ user1735921 bien sûr, surtout lorsque la requête devient si compliquée qu'elle fait la mauvaise chose et que vous passez une journée à la réparer ... il y a un équilibre entre les deux, comme d'habitude.
fabio.sussetto
6
@ user1735921 Uniquement si les gains de performances valent l'augmentation du temps de maintenance nécessaire à l'avenir
Joshua Schlichting
3
Mon avis Joinet sub querya une syntaxe différente, donc la lisibilité nous ne pouvons pas comparer, les deux ont une meilleure lisibilité tant que vous êtes bon en syntaxe SQL. La performance est plus importante.
Thavaprakash Swaminathan
843

Les sous-requêtes sont le moyen logiquement correct de résoudre les problèmes du formulaire "Obtenir des faits de A, conditionnels aux faits de B". Dans de tels cas, il est plus logique de coller B dans une sous-requête que de faire une jointure. Il est également plus sûr, dans un sens pratique, car vous n'avez pas à être prudent lorsque vous obtenez des faits en double de A en raison de plusieurs correspondances contre B.

En pratique, cependant, la réponse se résume généralement à la performance. Certains optimiseurs aspirent des citrons lorsqu'ils reçoivent une jointure par rapport à une sous-requête, et certains aspirent des citrons dans l'autre sens, et cela est spécifique à l'optimiseur, à la version du SGBD et à la requête.

Historiquement, les jointures explicites gagnent généralement, d'où la sagesse établie selon laquelle les jointures sont meilleures, mais les optimiseurs s'améliorent tout le temps, et je préfère donc écrire les requêtes d'abord de manière logique et cohérente, puis restructurer si les contraintes de performances le justifient.

Marcelo Cantos
la source
105
Très bonne réponse. J'ajouterais également que les développeurs (en particulier les amateurs) ne sont pas toujours compétents en SQL.
Álvaro González
4
+1 Vous cherchez une explication logique à ce problème depuis longtemps, c'est la seule réponse qui me semble logique
Ali Umair
1
@Marcelo Cantos, pourriez-vous s'il vous plaît donner un exemple de votre déclaration "Il est également plus sûr, dans un sens pratique, car vous n'avez pas à faire attention à ne pas dupliquer les faits de A en raison de plusieurs correspondances contre B."? J'ai trouvé cela très perspicace mais un peu trop abstrait. Merci.
Jinghui Niu
6
@JinghuiNiu Les clients qui ont acheté des articles coûteux: select custid from cust join bought using (custid) where price > 500. Si un client a acheté plusieurs articles coûteux, vous obtiendrez des doublons. Pour résoudre ce problème, select custid from cust where exists (select * from bought where custid = cust.custid and price > 500). Vous pouvez utiliser à la select distinct …place, mais c'est souvent plus de travail, que ce soit pour l'optimiseur ou l'évaluateur.
Marcelo Cantos
1
@MatTheWhale oui j'ai utilisé une réponse trop simplifiée car j'étais paresseux. Dans un scénario réel, vous tireriez plus de colonnes que simplement custid de cust.
Marcelo Cantos
357

Dans la plupart des cas, les JOINs sont plus rapides que les sous-requêtes et il est très rare qu'une sous-requête soit plus rapide.

Dans JOIN s, le SGBDR peut créer un plan d'exécution qui convient mieux à votre requête et peut prédire quelles données doivent être chargées pour être traitées et gagner du temps, contrairement à la sous-requête où il exécutera toutes les requêtes et chargera toutes leurs données pour effectuer le traitement. .

La bonne chose dans les sous-requêtes est qu'elles sont plus lisibles que JOINs: c'est pourquoi la plupart des nouveaux SQL les préfèrent; c'est la voie facile; mais quand il s'agit de performances, les JOINS sont meilleurs dans la plupart des cas, même s'ils ne sont pas difficiles à lire aussi.

Kronass
la source
14
Oui, la plupart des bases de données l'incluent donc comme étape d'optimisation pour convertir les sous-requêtes en jointures lors de l'analyse de votre requête.
Ciné
16
Cette réponse est un peu trop simplifiée pour la question qui a été posée. Comme vous le dites: certaines sous-requêtes sont correctes et certaines ne le sont pas. La réponse n'aide pas vraiment à distinguer les deux. (aussi le «très rare» dépend vraiment de vos données / application).
Unreason
21
pouvez-vous prouver l'un de vos points avec des références de documentation ou des résultats de test?
Uğur Gümüşhan
62
J'ai fait de très bonnes expériences avec des sous-requêtes qui contiennent une référence arrière à la requête supérieure, surtout quand il s'agit de nombres de lignes supérieurs à 100 000. La chose semble être l'utilisation de la mémoire et la pagination vers le fichier d'échange. Une jointure produirait une très grande quantité de données, qui peuvent ne pas tenir en mémoire et doivent être paginées dans le fichier d'échange. Dans tous les cas, les temps de requête de petits sous-sélections comme select * from a where a.x = (select b.x form b where b.id = a.id)sont extrêmement petits par rapport à une jointure. C'est un problème très spécifique, mais dans certains cas, cela vous amène de quelques heures à quelques minutes.
zuloo
13
J'ai de l'expérience avec Oracle et je peux dire que les sous-requêtes sont bien meilleures sur les grandes tables si vous n'avez aucun filtrage ou tri sur elles.
Amir Pashazadeh
130

Utilisez EXPLAIN pour voir comment votre base de données exécute la requête sur vos données. Il y a un énorme "ça dépend" dans cette réponse ...

PostgreSQL peut réécrire une sous-requête dans une jointure ou une jointure dans une sous-requête lorsqu'il pense que l'une est plus rapide que l'autre. Tout dépend des données, des index, de la corrélation, de la quantité de données, de la requête, etc.

Frank Heikens
la source
6
c'est exactement pourquoi postgresql est si bon et utile qu'il comprend quel est le but et fixera une requête en fonction de ce qu'il pense être le mieux et que postgresql est très bon pour savoir comment regarder ses données
WojonsTech
heww. Je suppose que pas besoin de réécrire des tonnes de requêtes pour moi! postgresql pour la victoire.
Daniel Shin
77

En 2010, j'aurais rejoint l'auteur de ces questions et j'aurais fortement voté pour JOIN, mais avec beaucoup plus d'expérience (en particulier dans MySQL), je peux dire: Oui, les sous-requêtes peuvent être meilleures. J'ai lu plusieurs réponses ici; certaines sous-requêtes déclarées sont plus rapides, mais il manquait une bonne explication. J'espère pouvoir en fournir une avec cette réponse (très) tardive:

Tout d'abord, permettez-moi de dire le plus important: il existe différentes formes de sous-requêtes

Et la deuxième déclaration importante: la taille compte

Si vous utilisez des sous-requêtes, vous devez savoir comment le serveur DB exécute la sous-requête. Surtout si la sous-requête est évaluée une ou pour chaque ligne! D'un autre côté, un DB-Server moderne est capable d'optimiser beaucoup. Dans certains cas, une sous-requête permet d'optimiser une requête, mais une version plus récente du serveur DB peut rendre l'optimisation obsolète.

Sous-requêtes dans Select-Fields

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

Sachez qu'une sous-requête est exécutée pour chaque ligne résultante de foo.
Évitez cela si possible; cela peut considérablement ralentir votre requête sur d'énormes ensembles de données. Cependant, si la sous-requête n'a aucune référence, fooelle peut être optimisée par le serveur DB en tant que contenu statique et ne peut être évaluée qu'une seule fois.

Sous-requêtes dans l'instruction Where

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

Si vous êtes chanceux, la DB optimise cela en interne en un JOIN. Sinon, votre requête deviendra très, très lente sur des ensembles de données énormes car elle exécutera la sous-requête pour chaque ligne foo, et pas seulement les résultats comme dans le type de sélection.

Sous-requêtes dans l'instruction Join

SELECT moo, bar 
  FROM foo 
    LEFT JOIN (
      SELECT MIN(bar), me FROM wilco GROUP BY me
    ) ON moo = me

C'est intéressant. Nous combinons JOINavec une sous-requête. Et ici, nous obtenons la véritable force des sous-requêtes. Imaginez un ensemble de données avec des millions de lignes wilcomais seulement quelques-unes distinctes me. Au lieu de nous joindre à une immense table, nous avons maintenant une table temporaire plus petite à laquelle nous joindre. Cela peut entraîner des requêtes beaucoup plus rapides en fonction de la taille de la base de données. Vous pouvez avoir le même effet avec CREATE TEMPORARY TABLE ...et INSERT INTO ... SELECT ..., ce qui pourrait offrir une meilleure lisibilité sur les requêtes très complexes (mais peut verrouiller les jeux de données dans un niveau d'isolation de lecture répétable).

Sous-requêtes imbriquées

SELECT moo, bar
  FROM (
    SELECT moo, CONCAT(roger, wilco) AS bar
      FROM foo
      GROUP BY moo
      HAVING bar LIKE 'SpaceQ%'
  ) AS temp_foo
  ORDER BY bar

Vous pouvez imbriquer des sous-requêtes à plusieurs niveaux. Cela peut vous aider sur d'énormes ensembles de données si vous devez regrouper ou trier les résultats. Habituellement, le serveur DB crée une table temporaire pour cela, mais parfois vous n'avez pas besoin de trier sur la table entière, uniquement sur l'ensemble de résultats. Cela peut fournir des performances bien meilleures en fonction de la taille de la table.

Conclusion

Les sous-requêtes ne remplacent pas a JOINet vous ne devez pas les utiliser comme cela (bien que cela soit possible). À mon humble avis, l'utilisation correcte d'une sous-requête est l'utilisation comme remplacement rapide de CREATE TEMPORARY TABLE .... Une bonne sous-requête réduit un ensemble de données d'une manière que vous ne pouvez pas accomplir dans une ONdéclaration de a JOIN. Si une sous-requête a l'un des mots clés GROUP BYou DISTINCTet n'est de préférence pas située dans les champs de sélection ou dans l'instruction where, cela peut améliorer considérablement les performances.

Trendfischer
la source
3
Pour Sub-queries in the Join-statement: (1) la génération d'une table dérivée à partir de la sous-requête elle-même peut prendre très longtemps. (2) la table dérivée résultante n'est pas indexée. ces deux seuls pourraient ralentir considérablement le SQL.
jxc
@jxc Je ne peux parler que pour MySQL (1) Il s'agit d'une table temporaire similaire à une jointure. Le temps dépend de la quantité de données. Si vous ne pouvez pas réduire les données avec une sous-requête, utilisez une jointure. (2) C'est vrai, cela dépend du facteur que vous pouvez réduire les données dans la table temporaire. J'ai eu des cas réels, où je pouvais réduire la taille de la jointure de quelques millions à quelques centaines et réduire le temps de requête de plusieurs secondes (avec utilisation d'index complet) à un quart de seconde avec une sous-requête.
Trendfischer
IMO: (1) une telle table temporaire (table dérivée) n'est pas matérialisée, donc chaque fois que vous exécutez le SQL, la table temporaire doit être recréée, ce qui pourrait être très coûteux et un véritable goulot d'étranglement (c'est-à-dire exécuter un groupe par sur des millions d'enregistrements) (2) même si vous pouvez réduire la taille de la table temporaire en 10enregistrements, car il n'y a pas d'index, cela signifie toujours potentiellement interroger 9 fois plus d'enregistrements de données que sans la table temporaire lorsque vous JOIGNEZ d'autres tables. BTW J'ai eu ce problème auparavant avec ma base de données (MySQL), dans mon cas, l'utilisation de la sous-requête dans SELECT listpourrait être beaucoup plus rapide.
jxc
@jxc Je ne doute pas qu'il existe de nombreux exemples où l'utilisation d'une sous-requête est moins optimale. En tant que bonne pratique, vous devez utiliser EXPLAINune requête avant l'optimisation. Avec l'ancien, set profiling=1vous pouvez facilement voir si une table temporaire est un goulot d'étranglement. Et même un index a besoin de temps de traitement, les arbres B optimisent l'interrogation des enregistrements, mais une table de 10 enregistrements peut être beaucoup plus rapide qu'un index pour des millions d'enregistrements. Mais cela dépend de plusieurs facteurs comme la taille et le type des champs.
Trendfischer
1
J'ai vraiment apprécié votre explication. Je vous remercie.
unpairestgood
43

Tout d'abord, pour comparer les deux, vous devez distinguer les requêtes avec des sous-requêtes de:

  1. une classe de sous-requêtes qui ont toujours une requête équivalente correspondante écrite avec des jointures
  2. une classe de sous-requêtes qui ne peuvent pas être réécrites à l'aide de jointures

Pour la première classe de requêtes, un bon SGBDR verra les jointures et les sous-requêtes comme équivalentes et produira les mêmes plans de requête.

De nos jours, même mysql fait cela.

Pourtant, parfois ce n'est pas le cas, mais cela ne signifie pas que les jointures gagneront toujours - j'ai eu des cas lors de l'utilisation de sous-requêtes dans mysql performance améliorée. (Par exemple, si quelque chose empêche le planificateur mysql d'estimer correctement le coût et si le planificateur ne voit pas la variante de jointure et la variante de sous-requête comme étant identiques, les sous-requêtes peuvent surpasser les jointures en forçant un certain chemin).

La conclusion est que vous devez tester vos requêtes pour les variantes de jointure et de sous-requête si vous voulez être sûr de celle qui fonctionnera mieux.

Pour la deuxième classe, la comparaison n'a aucun sens car ces requêtes ne peuvent pas être réécrites à l'aide de jointures et dans ces cas, les sous-requêtes sont un moyen naturel d'effectuer les tâches requises et vous ne devez pas les discriminer.

Déraisonnable
la source
1
pouvez-vous fournir un exemple de requête écrite à l'aide de sous-requêtes qui ne peuvent pas être converties en jointures (deuxième classe, comme vous l'appelez)?
Zahra
24

Je pense que ce qui a été sous-souligné dans les réponses citées est la question des doublons et des résultats problématiques qui peuvent résulter de cas (d'utilisation) spécifiques.

(bien que Marcelo Cantos le mentionne)

Je citerai l'exemple des cours Lagunita de Stanford sur SQL.

Table des étudiants

+------+--------+------+--------+
| sID  | sName  | GPA  | sizeHS |
+------+--------+------+--------+
|  123 | Amy    |  3.9 |   1000 |
|  234 | Bob    |  3.6 |   1500 |
|  345 | Craig  |  3.5 |    500 |
|  456 | Doris  |  3.9 |   1000 |
|  567 | Edward |  2.9 |   2000 |
|  678 | Fay    |  3.8 |    200 |
|  789 | Gary   |  3.4 |    800 |
|  987 | Helen  |  3.7 |    800 |
|  876 | Irene  |  3.9 |    400 |
|  765 | Jay    |  2.9 |   1500 |
|  654 | Amy    |  3.9 |   1000 |
|  543 | Craig  |  3.4 |   2000 |
+------+--------+------+--------+

Appliquer la table

(candidatures adressées à des universités et majors spécifiques)

+------+----------+----------------+----------+
| sID  | cName    | major          | decision |
+------+----------+----------------+----------+
|  123 | Stanford | CS             | Y        |
|  123 | Stanford | EE             | N        |
|  123 | Berkeley | CS             | Y        |
|  123 | Cornell  | EE             | Y        |
|  234 | Berkeley | biology        | N        |
|  345 | MIT      | bioengineering | Y        |
|  345 | Cornell  | bioengineering | N        |
|  345 | Cornell  | CS             | Y        |
|  345 | Cornell  | EE             | N        |
|  678 | Stanford | history        | Y        |
|  987 | Stanford | CS             | Y        |
|  987 | Berkeley | CS             | Y        |
|  876 | Stanford | CS             | N        |
|  876 | MIT      | biology        | Y        |
|  876 | MIT      | marine biology | N        |
|  765 | Stanford | history        | Y        |
|  765 | Cornell  | history        | N        |
|  765 | Cornell  | psychology     | Y        |
|  543 | MIT      | CS             | N        |
+------+----------+----------------+----------+

Essayons de trouver les scores GPA pour les étudiants qui ont postulé en CSmajeure (quelle que soit l'université)

Utilisation d'une sous-requête:

select GPA from Student where sID in (select sID from Apply where major = 'CS');

+------+
| GPA  |
+------+
|  3.9 |
|  3.5 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

La valeur moyenne de cet ensemble de résultats est:

select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');

+--------------------+
| avg(GPA)           |
+--------------------+
| 3.6800000000000006 |
+--------------------+

Utilisation d'une jointure:

select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+------+
| GPA  |
+------+
|  3.9 |
|  3.9 |
|  3.5 |
|  3.7 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

valeur moyenne pour cet ensemble de résultats:

select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+-------------------+
| avg(GPA)          |
+-------------------+
| 3.714285714285714 |
+-------------------+

Il est évident que la deuxième tentative donne des résultats trompeurs dans notre cas d'utilisation, étant donné qu'elle compte les doublons pour le calcul de la valeur moyenne. Il est également évident que l'utilisation de distinctl'instruction join-based n'éliminera pas le problème, étant donné qu'elle conservera par erreur une occurrence sur trois du 3.9score. Le cas correct est de prendre en compte DEUX (2) occurrences du 3.9score étant donné que nous avons réellement DEUX (2) étudiants avec ce score qui répondent à nos critères de requête.

Il semble que dans certains cas, une sous-requête soit le moyen le plus sûr, en plus de tout problème de performances.

pkaramol
la source
Je pense que vous ne pouvez pas utiliser une sous-requête ici. Ce n'est pas un cas où vous pouvez logiquement utiliser l'un ou l'autre mais l'un donne une mauvaise réponse en raison de sa mise en œuvre technique. Il s'agit d'un cas où vous NE POUVEZ PAS utiliser une sous-requête car un élève n'appartenant pas à CS peut obtenir un score de 3,9 qui figure dans la liste IN des scores. Le contexte de CS est perdu une fois la sous-requête exécutée, ce qui n'est pas ce que nous voulons logiquement. Ce n'est donc pas un bon exemple où l'un ou l'autre peut être utilisé. L'utilisation de la sous-requête est conceptuellement / logiquement incorrecte pour ce cas d'utilisation même si, heureusement, elle donne le bon résultat pour un autre ensemble de données.
Saurabh Patil
22

La documentation MSDN pour SQL Server indique

De nombreuses instructions Transact-SQL qui incluent des sous-requêtes peuvent également être formulées en tant que jointures. D'autres questions ne peuvent être posées qu'avec des sous-requêtes. Dans Transact-SQL, il n'y a généralement pas de différence de performances entre une instruction qui inclut une sous-requête et une version sémantiquement équivalente qui ne le fait pas. Cependant, dans certains cas où l'existence doit être vérifiée, une jointure donne de meilleures performances. Sinon, la requête imbriquée doit être traitée pour chaque résultat de la requête externe pour garantir l'élimination des doublons. Dans de tels cas, une approche de jointure donnerait de meilleurs résultats.

donc si vous avez besoin de quelque chose comme

select * from t1 where exists select * from t2 where t2.parent=t1.id

essayez plutôt d'utiliser join. Dans d'autres cas, cela ne fait aucune différence.

Je dis: Créer des fonctions pour les sous-requêtes élimine le problème du désordre et vous permet d'implémenter une logique supplémentaire aux sous-requêtes. Je recommande donc de créer des fonctions pour les sous-requêtes autant que possible.

L'encombrement du code est un gros problème et l'industrie s'efforce de l'éviter depuis des décennies.

Uğur Gümüşhan
la source
9
Remplacer les sous-requêtes par des fonctions est une très mauvaise idée en termes de performances dans certains SGBDR (par exemple Oracle), donc je recommanderais tout le contraire - utilisez des sous-requêtes / jointures au lieu de fonctions dans la mesure du possible.
Frank Schmitt
3
@FrankSchmitt veuillez soutenir votre argument avec des références.
Uğur Gümüşhan
2
Il existe également des cas où vous devez utiliser une sous-requête au lieu d'une jointure même si vous vérifiez l'existence: si vous vérifiez NOT EXISTS. A l' NOT EXISTSemporte sur a LEFT OUTER JOIN pour diverses raisons: performances, sécurité intégrée (en cas de colonnes nulles) et lisibilité. sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Tim Schmelter
16

Exécuter sur une très grande base de données à partir d'un ancien CMS Mambo:

SELECT id, alias
FROM
  mos_categories
WHERE
  id IN (
    SELECT
      DISTINCT catid
    FROM mos_content
  );

0 secondes

SELECT
  DISTINCT mos_content.catid,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

~ 3 secondes

Un EXPLAIN montre qu'ils examinent exactement le même nombre de lignes, mais l'un prend 3 secondes et l'autre est presque instantané. Morale de l'histoire? Si les performances sont importantes (quand n'est-ce pas?), Essayez-les de plusieurs façons et voyez laquelle est la plus rapide.

Et...

SELECT
  DISTINCT mos_categories.id,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

0 secondes

Encore une fois, mêmes résultats, même nombre de lignes examinées. Je suppose que DISTINCT mos_content.catid prend beaucoup plus de temps à comprendre que DISTINCT mos_categories.id.

Jason
la source
1
je voudrais en savoir plus sur ce que vous essayez de souligner dans la dernière ligne "Je suppose que DISTINCT mos_content.catid prend beaucoup plus de temps à comprendre que DISTINCT mos_categories.id." . Êtes-vous en train de dire qu'un identifiant doit être nommé uniquement idet non nommé quelque chose comme catid? Essayer d'optimiser mes accès db, et vos apprentissages pourraient vous aider.
bool.dev
2
utiliser SQL IN dans ce cas est une mauvaise pratique et cela ne prouve rien.
Uğur Gümüşhan
15

Selon mon observation, comme dans deux cas, si une table a moins de 100 000 enregistrements, la jointure fonctionnera rapidement.

Mais dans le cas où une table a plus de 100 000 enregistrements, une sous-requête est le meilleur résultat.

J'ai une table qui contient 500 000 enregistrements sur lesquels j'ai créé la requête ci-dessous et son temps de résultat est comme

SELECT * 
FROM crv.workorder_details wd 
inner join  crv.workorder wr on wr.workorder_id = wd.workorder_id;

Résultat: 13,3 secondes

select * 
from crv.workorder_details 
where workorder_id in (select workorder_id from crv.workorder)

Résultat: 1,65 secondes

Vijay Gajera
la source
Je suis d'accord, parfois, casser la requête fonctionne également, lorsque vous avez des millions d'enregistrements, vous ne voulez pas utiliser les jointures car elles prennent pour toujours. Manipulez-le plutôt dans le code et la carte dans le code est meilleure.
user1735921
1
Lier vos jointures ne fonctionne pas assez rapidement, il se peut que vous manquiez un index. L'analyseur de requêtes peut être très utile pour comparer les performances réelles.
digital.aaron
Je suis d'accord avec Ajay Gajera, je l'ai vu par moi-même.
user1735921
14
Comment est-il logique de comparer les performances de deux requêtes qui renvoient des résultats différents?
Paul Spiegel
Oui, ce sont des requêtes différentes mais retournant le même résultat
King
12

Les sous-requêtes sont généralement utilisées pour renvoyer une seule ligne en tant que valeur atomique, bien qu'elles puissent être utilisées pour comparer des valeurs à plusieurs lignes avec le mot-clé IN. Ils sont autorisés à presque n'importe quel point significatif d'une instruction SQL, y compris la liste cible, la clause WHERE, etc. Une simple sous-requête peut être utilisée comme condition de recherche. Par exemple, entre une paire de tables:

   SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo');

Notez que l'utilisation d'un opérateur de valeur normale sur les résultats d'une sous-requête nécessite qu'un seul champ soit renvoyé. Si vous souhaitez vérifier l'existence d'une seule valeur dans un ensemble d'autres valeurs, utilisez IN:

   SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]');

C'est évidemment différent de dire un LEFT-JOIN où vous voulez simplement joindre des éléments des tables A et B même si la condition de jointure ne trouve aucun enregistrement correspondant dans le tableau B, etc.

Si vous êtes juste inquiet à propos de la vitesse, vous devrez vérifier avec votre base de données et écrire une bonne requête et voir s'il y a une différence significative dans les performances.

rkulla
la source
11

Version MySQL: 5.5.28-0ubuntu0.12.04.2-log

J'avais également l'impression que JOIN est toujours meilleur qu'une sous-requête dans MySQL, mais EXPLAIN est un meilleur moyen de porter un jugement. Voici un exemple où les sous-requêtes fonctionnent mieux que les JOIN.

Voici ma requête avec 3 sous-requêtes:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

EXPLAIN montre:

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type        | table    | type   | possible_keys                                       | key          | key_len | ref                                             | rows | Extra                    |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
|  1 | PRIMARY            | vrl      | index  | PRIMARY                                             | moved_date   | 8       | NULL                                            |  200 | Using where              |
|  1 | PRIMARY            | l        | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  1 | PRIMARY            | vrlih    | eq_ref | PRIMARY                                             | PRIMARY      | 9       | ranker.vrl.list_id,ranker.vrl.ontology_id,const |    1 | Using where              |
|  1 | PRIMARY            | lbs      | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  4 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  3 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

La même requête avec JOINs est:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43 
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55 
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

et la sortie est:

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                       | key          | key_len | ref                                         | rows | Extra                                        |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | lt3   | ref    | list_tag_key,list_id,tag_id                         | tag_id       | 5       | const                                       | 2386 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.lt3.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | vrlih | ref    | PRIMARY                                             | PRIMARY      | 4       | ranker.lt3.list_id                          |  103 | Using where                                  |
|  1 | SIMPLE      | vrl   | ref    | PRIMARY                                             | PRIMARY      | 8       | ranker.lt3.list_id,ranker.vrlih.ontology_id |   65 | Using where                                  |
|  1 | SIMPLE      | lt1   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index; Not exists         |
|  1 | SIMPLE      | lbs   | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | lt2   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

Une comparaison des rows colonne indique la différence et la requête avec JOIN utiliseUsing temporary; Using filesort .

Bien sûr, lorsque j'exécute les deux requêtes, la première se fait en 0,02 seconde, la seconde ne se termine pas même après 1 minute, alors EXPLAIN a expliqué ces requêtes correctement.

Si je n'ai pas le INNER JOIN sur la list_tagtable c'est à dire si je retire

AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL  

de la première requête et en conséquence:

INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403

à partir de la deuxième requête, puis EXPLAIN renvoie le même nombre de lignes pour les deux requêtes et ces deux requêtes s'exécutent également rapidement.

arun
la source
J'ai une situation similaire, mais avec plus de jointures que la vôtre, j'essaierai d'expliquer une fois
pahnin
Dans Oracle ou PostgreSQL, j'aurais essayé: AND NOT EXISTS (SELECT 1 FROM list_tag WHERE list_id = l.list_id AND tag_id in (43, 55, 246403))
David Aldridge
11

Les sous-requêtes ont la capacité de calculer des fonctions d'agrégation à la volée. Par exemple, trouvez le prix minimal du livre et obtenez tous les livres qui sont vendus avec ce prix. 1) Utilisation de sous-requêtes:

SELECT titles, price
FROM Books, Orders
WHERE price = 
(SELECT MIN(price)
 FROM Orders) AND (Books.ID=Orders.ID);

2) Utilisation de JOINs

SELECT MIN(price)
     FROM Orders;
-----------------
2.99

SELECT titles, price
FROM Books b
INNER JOIN  Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;
Vlad
la source
Autre cas: plusieurs GROUP BYs avec des tables différentes: stackoverflow.com/questions/11415284/… Les sous-requêtes semblent être strictement plus générales. Voir aussi l'homme MySQL: dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html | dev.mysql.com/doc/refman/5.7/en/rewriting-subqueries.html
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
6
-1 Ceci est trompeur car vous utilisez une sous-requête et rejoignez les deux exemples. Le fait que vous ayez extrait la sous-requête dans une deuxième requête pour déterminer le prix de commande le plus bas n'a aucun effet puisque la base de données fera exactement la même chose. De plus, vous ne réécrivez pas la jointure à l'aide d'une sous-requête; les deux requêtes utilisent une jointure. Vous êtes exact que les sous - requêtes permettent des fonctions d' agrégation, mais cet exemple ne démontre pas ce fait.
David Harkness
Je suis d'accord avec David et vous pouvez utiliser le groupe par pour obtenir le prix minimum.
user1735921
9
  • Une règle générale est que les jointures sont plus rapides dans la plupart des cas (99%).
  • Plus il y a de tables de données, plus les sous - requêtes - sont lentes.
  • Moins les tables de données ont, les sous - requêtes ont une vitesse équivalente aux jointures .
  • Les sous - requêtes sont plus simples, plus faciles à comprendre et à lire.
  • La plupart des infrastructures Web et d'application et leurs "ORM" et "Active record" génèrent des requêtes avec des sous - requêtes , car avec les sous - requêtes, il est plus facile de répartir les responsabilités, de maintenir le code, etc.
  • Pour les petits sites Web ou les applications, les sous - requêtes sont correctes, mais pour les sites Web et les applications plus grands, vous devrez souvent réécrire les requêtes générées pour rejoindre les requêtes, en particulier si une requête utilise de nombreuses sous-requêtes dans la requête.

Certaines personnes disent que "certains SGBDR peuvent réécrire une sous - requête dans une jointure ou une jointure dans une sous - requête quand ils pensent que l'un est plus rapide que l'autre.", Mais cette déclaration s'applique aux cas simples, certainement pas pour les requêtes compliquées avec des sous-requêtes qui provoquent réellement un problèmes de performances.

fico7489
la source
> mais cette déclaration s'applique aux cas simples. Je comprends que c'est soit un cas simple qui peut être réécrit en "JOIN" par RDBMS, soit c'est un cas si complexe que les sous-requêtes sont appropriées ici. :-) Joli point sur les ORM. Je pense que cela a le plus grand impact.
pilat
4

La différence n'est visible que lorsque la deuxième table de jointure contient beaucoup plus de données que la table principale. J'ai eu une expérience comme ci-dessous ...

Nous avions un tableau d'utilisateurs de cent mille entrées et leurs données d'adhésion (amitié) environ 3 cent mille entrées. C'était une déclaration de jointure afin de prendre des amis et leurs données, mais avec beaucoup de retard. Mais cela fonctionnait bien là où il n'y avait qu'une petite quantité de données dans le tableau des membres. Une fois que nous l'avons modifié pour utiliser une sous-requête, cela a bien fonctionné.

Mais en attendant, les requêtes de jointure fonctionnent avec d'autres tables qui ont moins d'entrées que la table principale.

Je pense donc que les instructions de jointure et de sous-requête fonctionnent correctement et cela dépend des données et de la situation.

jpk
la source
3

De nos jours, de nombreux dbs peuvent optimiser les sous-requêtes et les jointures. Ainsi, vous devez simplement examiner votre requête en utilisant Explain et voir laquelle est la plus rapide. S'il n'y a pas beaucoup de différence de performances, je préfère utiliser la sous-requête car elles sont simples et plus faciles à comprendre.

Eunwoo Song
la source
1

Je pense juste au même problème, mais j'utilise la sous-requête dans la partie FROM. J'ai besoin de me connecter et d'interroger à partir de grandes tables, la table "esclave" a un record de 28 millions mais le résultat n'est que de 128 données si petites que le big data! J'utilise la fonction MAX () dessus.

D'abord, j'utilise LEFT JOIN parce que je pense que c'est la bonne façon, le mysql peut être optimisé, etc. Deuxième fois juste pour les tests, je réécris pour sous-sélectionner contre JOIN.

LEFT JOIN runtime: 1.12s SUB-SELECT runtime: 0.06s

La sous-sélection 18 fois plus rapide que la jointure! Juste dans le chokito adv. La sous-sélection a l'air terrible mais le résultat ...

Karoly Szabo
la source
-1

Si vous souhaitez accélérer votre requête à l'aide de la jointure:

Pour "jointure interne / jointure", n'utilisez pas la condition where à la place, utilisez-la dans la condition "ON". Par exemple:

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

Pour "Jointure gauche / droite", ne pas utiliser en condition "ON", car si vous utilisez la jointure gauche / droite, il obtiendra toutes les lignes pour une même table. Donc, essayez d'utiliser la condition "Où"

sam ruben
la source
Cela dépend du serveur SQL et de la complexité de la requête. De nombreuses implémentations SQL optimiseraient des requêtes simples comme celle-ci pour les meilleures performances. Peut-être fournir un exemple de nom et de version de serveur où ce comportement se produit pour améliorer la réponse?
Trendfischer