SQL se joint aux sous-requêtes SQL (performances)?

110

Je souhaite savoir si j'ai une requête de jointure quelque chose comme ceci -

Select E.Id,E.Name from Employee E join Dept D on E.DeptId=D.Id

et une sous - requête quelque chose comme ça -

Select E.Id,E.Name from Employee Where DeptId in (Select Id from Dept)

Lorsque je considère les performances, laquelle des deux requêtes serait la plus rapide et pourquoi ?

Y a-t-il aussi un moment où je devrais préférer l'un à l'autre?

Désolé si c'est trop trivial et demandé avant mais je suis confus à ce sujet. De plus, ce serait formidable si vous pouviez me suggérer des outils que je devrais utiliser pour mesurer les performances de deux requêtes. Merci beaucoup!

Vishal
la source
5
@Lucero, cette question est étiquetée sql-server-2008, où le message que vous mentionnez est étiqueté MySql. Vous pouvez en déduire que les réponses seront les mêmes. L'optimisation des performances se fait différemment sur les deux SGBDR.
Francois Botha

Réponses:

48

Je m'ATTENDrais à ce que la première requête soit plus rapide, principalement parce que vous avez une équivalence et une jointure explicite. D'après mon expérience, INc'est un opérateur très lent, puisque SQL l'évalue normalement comme une série de WHEREclauses séparées par "OR" ( WHERE x=Y OR x=Z OR...).

Comme pour ALL THINGS SQL cependant, votre kilométrage peut varier. La vitesse dépendra beaucoup des index (avez-vous des index sur les deux colonnes ID? Cela aidera beaucoup ...) entre autres.

La seule VRAIE façon de savoir avec 100% de certitude ce qui est plus rapide est d'activer le suivi des performances (IO Statistics est particulièrement utile) et de les exécuter tous les deux. Assurez-vous de vider votre cache entre les exécutions!

JNK
la source
16
J'ai de sérieux doutes sur cette réponse, car la plupart des SGBD, certainement SQL Server 2008 et versions ultérieures, traduisent la sous-requête d'ID unique (non corrélée, ce qui signifie: ne pas référencer plusieurs colonnes de requête externes) en une semi-jointure relativement rapide. De plus, comme indiqué précédemment dans une autre réponse, la première jointure réelle renverra une ligne pour CHAQUE occurrence de l'ID correspondant dans le département - cela ne fait aucune différence pour un ID unique, mais vous donnera des tonnes de doublons ailleurs. Les trier avec DISTINCT ou GROUP BY sera une autre charge de performance lourde. Vérifiez les plans d'exécution dans SQL Server Management Studio!
Erik Hart
2
La clause IN en tant qu'équivalent de OR s'applique aux listes de paramètres / valeurs, mais pas aux sous-requêtes, qui sont principalement traitées comme des jointures.
Erik Hart
42

Eh bien, je crois que c'est une question «ancienne mais or». La réponse est: "Cela dépend!". Les performances sont un sujet si délicat qu'il serait trop ridicule de dire: "N'utilisez jamais de sous-requêtes, rejoignez toujours". Dans les liens suivants, vous trouverez quelques bonnes pratiques de base que j'ai trouvées très utiles:

J'ai une table avec 50000 éléments, le résultat que je recherchais était de 739 éléments.

Ma question au début était la suivante:

SELECT  p.id,
    p.fixedId,
    p.azienda_id,
    p.categoria_id,
    p.linea,
    p.tipo,
    p.nome
FROM prodotto p
WHERE p.azienda_id = 2699 AND p.anno = (
    SELECT MAX(p2.anno) 
    FROM prodotto p2 
    WHERE p2.fixedId = p.fixedId 
)

et il a fallu 7,9 secondes pour s'exécuter.

Ma question est enfin la suivante:

SELECT  p.id,
    p.fixedId,
    p.azienda_id,
    p.categoria_id,
    p.linea,
    p.tipo,
    p.nome
FROM prodotto p
WHERE p.azienda_id = 2699 AND (p.fixedId, p.anno) IN
(
    SELECT p2.fixedId, MAX(p2.anno)
    FROM prodotto p2
    WHERE p.azienda_id = p2.azienda_id
    GROUP BY p2.fixedId
)

et il a fallu 0,0256 s

Bon SQL, bon.

linuxatico
la source
3
Intéressant, pourriez-vous expliquer comment l'ajout de GROUP BY l'a corrigé?
cozos
6
La table temporaire générée par la sous-requête était plus petite. Par conséquent, l'exécution est plus rapide car il y a moins de données à enregistrer.
Sirmyself
2
Je pense que dans la première requête, vous avez partagé une variable entre la requête externe et la sous-requête, donc pour chaque ligne de la requête principale, la sous-requête s'exécute, mais dans la seconde, la sous-requête ne s'exécute qu'une seule fois et de cette manière les performances améliorées.
Ali Faradjpour
1
Le serveur SQL et MySql et ... Sql (à l'exception de NoSql) sont si similaires dans l'infrastructure. Nous avons une sorte de moteur d'optimisation des requêtes en dessous qui convertit les clauses IN (...) pour les joindre (si c'était possible). Mais quand vous avez un Group by sur une colonne bien indexée (en fonction de sa cardinalité) alors ce sera beaucoup plus rapide. Cela dépend donc vraiment de la situation.
Alix
10

Commencez à regarder les plans d'exécution pour voir les différences dans la façon dont le serveur SQl les interprétera. Vous pouvez également utiliser Profiler pour exécuter les requêtes plusieurs fois et obtenir la différence.

Je ne m'attendrais pas à ce que ceux-ci soient si horriblement différents, où vous pouvez obtenir des gains de performances réels et importants en utilisant des jointures au lieu de sous-requêtes lorsque vous utilisez des sous-requêtes corrélées.

EXISTS est souvent meilleur que l'un ou l'autre de ces deux et lorsque vous parlez de jointures à gauche où vous voulez que tous les enregistrements ne soient pas dans la table de jointure de gauche, NOT EXISTS est souvent un bien meilleur choix.

HLGEM
la source
9

Les performances sont basées sur la quantité de données sur lesquelles vous exécutez ...

Si c'est moins de données autour de 20k. JOIN fonctionne mieux.

Si les données ressemblent plus à 100k +, IN fonctionne mieux.

Si vous n'avez pas besoin des données de l'autre table, IN est bon, mais il est toujours préférable d'opter pour EXISTS.

J'ai testé tous ces critères et les tables ont des index appropriés.

JP Emvia
la source
4

La performance doit être la même; il est beaucoup plus important d'avoir les bons index et le clustering appliqués sur vos tables (il existe de bonnes ressources sur ce sujet).

(Modifié pour refléter la question mise à jour)

Lucero
la source
4

Les deux requêtes peuvent ne pas être sémantiquement équivalentes. Si un employé travaille pour plus d'un département (possible dans l'entreprise pour laquelle je travaille; certes, cela impliquerait que votre table n'est pas entièrement normalisée), la première requête renverrait des lignes en double alors que la deuxième requête ne le ferait pas. Pour rendre les requêtes équivalentes dans ce cas, il DISTINCTfaudrait ajouter le mot - clé à la SELECTclause, ce qui peut avoir un impact sur les performances.

Notez qu'il existe une règle de conception empirique qui stipule qu'une table doit modéliser une entité / classe ou une relation entre entités / classes, mais pas les deux. Par conséquent, je vous suggère de créer un troisième tableau, par exemple OrgChart, pour modéliser la relation entre les employés et les ministères.

un jour quand
la source
4

Je sais que c'est un ancien message, mais je pense que c'est un sujet très important, surtout de nos jours où nous avons plus de 10 millions d'enregistrements et parlons de téraoctets de données.

Je vais également appuyer les observations suivantes. J'ai environ 45 millions d'enregistrements dans ma table ([data]) et environ 300 enregistrements dans ma table [cats]. J'ai une indexation complète pour toutes les requêtes dont je vais parler.

Prenons l'exemple 1:

UPDATE d set category = c.categoryname
FROM [data] d
JOIN [cats] c on c.id = d.catid

par rapport à l'exemple 2:

UPDATE d set category = (SELECT TOP(1) c.categoryname FROM [cats] c where c.id = d.catid)
FROM [data] d

L'exemple 1 a duré environ 23 minutes. L'exemple 2 a pris environ 5 minutes.

Je conclurais donc que la sous-requête dans ce cas est beaucoup plus rapide. Bien sûr, gardez à l'esprit que j'utilise des disques SSD M.2 capables d'entrées / sorties à 1 Go / s (ce sont des octets et non des bits), donc mes index sont également très rapides. Cela peut donc également affecter les vitesses dans votre situation

S'il s'agit d'un nettoyage de données ponctuel, il est probablement préférable de le laisser s'exécuter et de terminer. J'utilise TOP (10000) et je vois combien de temps cela prend et je multiplie par le nombre d'enregistrements avant de lancer la grande requête.

Si vous optimisez des bases de données de production, je suggérerais fortement de pré-traiter les données, c'est-à-dire d'utiliser des déclencheurs ou un job-broker pour asynchroniser les enregistrements de mise à jour, de sorte que l'accès en temps réel récupère les données statiques.

Arvin Amir
la source
0

Vous pouvez utiliser un plan d'explication pour obtenir une réponse objective.

Pour votre problème, un filtre Exists serait probablement le plus rapide.

Snekse
la source
2
"un filtre Exists serait probablement le plus rapide" - probablement pas, je pense, bien qu'une réponse définitive exigerait des tests par rapport aux données réelles. Les filtres existants sont susceptibles d'être plus rapides lorsqu'il y a plusieurs lignes avec les mêmes valeurs de recherche - un filtre existe peut donc s'exécuter plus rapidement si la requête vérifie si d'autres employés ont été enregistrés dans le même service, mais probablement pas lors d'une recherche par rapport à un service table.
Serait-il plus lent dans ce dernier scénario?
Snekse
Cela dépendrait de l'optimiseur - dans certaines circonstances, cela pourrait, mais normalement, je m'attendrais à des performances très similaires.