Ceci est une rediffusion de ma question sur Stack Overflow. Ils ont suggéré de la poser ici:
J'ai trouvé un article en ligne de 2005, où l'auteur affirme que de nombreux développeurs utilisent mal GROUP BY et que vous devriez mieux le remplacer par une sous-requête.
Je l'ai testé sur l'une de mes requêtes, où j'ai besoin de trier le résultat d'une recherche par le nombre d'entrées jointes d'une autre table (les plus courantes devraient apparaître en premier). Mon approche originale et classique consistait à joindre les deux tables sur un ID commun, à grouper par chaque champ de la liste de sélection et à classer le résultat en fonction du nombre de sous-tables.
Maintenant, Jeff Smith du blog lié affirme que vous devriez mieux utiliser une sous-sélection, qui fait tout le regroupement, que de vous joindre à cette sous-sélection. En vérifiant les plans d'exécution des deux approches, SSMS déclare que le grand groupe requiert 52% du temps et la sous-sélection 48%, donc d'un point de vue technique, il semble que l'approche sous-sélection est en fait légèrement plus rapide. Cependant, la commande SQL "améliorée" semble générer un plan d'exécution plus compliqué (en termes de nœuds)
Qu'est-ce que tu penses? Pouvez-vous me donner quelques détails sur la façon d'interpréter les plans d'exécution dans ce cas spécifique et lequel est généralement l'option préférable?
SELECT
a.ID,
a.ID_AddressType,
a.Name1,
a.Name2,
a.Street,
a.Number,
a.ZipCode,
a.City,
a.Country
FROM dbo.[Address] a
INNER JOIN CONTAINSTABLE(
dbo.[Address],
FullAddress,
'"ZIE*"',
5
) s ON a.ID = s.[KEY]
LEFT JOIN dbo.Haul h ON h.ID_DestinationAddress = a.ID
GROUP BY
a.ID,
a.ID_AddressType,
a.Name1,
a.Name2,
a.Street,
a.Number,
a.ZipCode,
a.City,
a.Country,
s.RANK
ORDER BY s.RANK DESC, COUNT(*) DESC;
SELECT
a.ID,
a.ID_AddressType,
a.Name1,
a.Name2,
a.Street,
a.Number,
a.ZipCode,
a.City,
a.Country
FROM dbo.[Address] a
INNER JOIN CONTAINSTABLE(
dbo.[Address],
FullAddress,
'"ZIE*"',
5
) s ON a.ID = s.[KEY]
LEFT JOIN (
SELECT ID_DestinationAddress, COUNT(*) Cnt
FROM dbo.Haul
GROUP BY ID_DestinationAddress
) h ON h.ID_DestinationAddress = a.ID
ORDER BY s.RANK DESC, h.Cnt DESC;
Si vous modifiez la jointure gauche avec dbo.Haul
à une sous-requête, il calculera ces valeurs distinctes de ID_DestinationAddress
(Stream Aggregate) et comptez-les (Compute scalar) directement après avoir obtenu les données de l'analyse.
Voici ce que vous voyez dans le plan d'exécution:
Alors que lorsque vous utilisez le GROUP BY
méthode, il ne fait le regroupement qu'après que les données ont traversé la jointure gauche entre dbo.Haul
et dbo.[Address]
.
L'amélioration dépendra du rapport de valeur unique de dbo.Haul. Moins de valeurs uniques signifie un meilleur résultat pour le deuxième plan d'exécution, car la jointure gauche doit traiter moins de valeurs.
L'autre résultat positif de la deuxième requête est que seule l'unicité de ID_DestinationAddress
est calculé, et non l'unicité de toutes les colonnes dans leur ensemble dans le groupe par.
Encore une fois, vous devez tester et valider les résultats de votre requête, jeu de données et index. L'une des façons de tester si vous n'êtes pas familier avec les plans d'exécution consiste à définir SET STATISTICS IO, TIME ON;
avant d'exécuter les requêtes et de rendre ces statistiques d'exécution plus lisibles en les collant dans un outil tel que statistiquesparser .
Un petit test pour montrer quelles différences de données peuvent faire pour ces requêtes.
Si la dbo.Haul
la table n'a pas beaucoup de correspondances avec les 5 enregistrements retournés par le filtrage d'index FULLTEXT, la différence n'est pas si grande:
Plan de requête de sous-requête
1000 lignes pourraient être filtrées plus tôt, mais le temps d'exécution est de l'ordre de 15 ms pour les deux requêtes de toute façon sur ma machine.
Maintenant, si je modifie mes données, ces 5 enregistrements ont bien plus de correspondances avec dbo.Haul
à gauche:
La différence entre le grouper par requête
<QueryTimeStats CpuTime="1564" ElapsedTime="1566" />
Et le Subquery devient plus clair
et les statistiques:
<QueryTimeStats CpuTime="680" ElapsedTime="690"/>