web-dev-qa-db-fra.com

Remplacer la longue liste GROUP BY par une sous-requête

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;

enter image description here

8
André Reichelt

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:

enter image description here

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].

enter image description here

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 .


Essai

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:

Grouper par plan de requête

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êteenter image description here

    <QueryTimeStats CpuTime="1564" ElapsedTime="1566" />

Et le Subquery devient plus clair

enter image description here

et les statistiques:

<QueryTimeStats CpuTime="680" ElapsedTime="690"/>
8
Randi Vertongen