Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

SQL Server sélectionne une valeur aléatoire (ou première) avec agrégation

Il existe un agrégat non documenté appelé ANY qui n'est pas une syntaxe valide mais qui peut apparaître dans vos plans d'exécution. Cependant, cela n'offre aucun avantage en termes de performances.

En supposant la structure de table et d'index suivante

CREATE TABLE T
(
id int identity primary key,
[group] char(1) 
)

CREATE NONCLUSTERED INDEX ix ON T([group])

INSERT INTO T
SELECT TOP 1000000 CHAR( 65 + ROW_NUMBER() OVER (ORDER BY @@SPID) % 3)
FROM sys.all_objects o1, sys.all_objects o2, sys.all_objects o3

J'ai également rempli des exemples de données de sorte qu'il y ait de nombreuses lignes par groupe.

Votre requête d'origine

SELECT MAX(id),
       [group]
FROM   T
GROUP  BY [group]  

Donne Table 'T'. Scan count 1, logical reads 1367 et le plan

  |--Stream Aggregate(GROUP BY:([[T].[group]) DEFINE:([Expr1003]=MAX([[T].[id])))
       |--Index Scan(OBJECT:([[T].[ix]), ORDERED FORWARD)

Réécrit pour obtenir le ANY agrégé...

;WITH cte AS
(
SELECT *,
        ROW_NUMBER() OVER (PARTITION BY [group] ORDER BY [group] ) AS RN
FROM T)
SELECT id,
       [group]
FROM    cte     
WHERE RN=1

Donne Table 'T'. Scan count 1, logical reads 1367 et le plan

  |--Stream Aggregate(GROUP BY:([[T].[group]) DEFINE:([[T].[id]=ANY([[T].[id])))
       |--Index Scan(OBJECT:([[T].[ix]), ORDERED FORWARD)

Même si potentiellement SQL Server pourrait arrêter de traiter le groupe dès que la première valeur est trouvée et passer à la suivante, ce n'est pas le cas. Il traite toujours toutes les lignes et les lectures logiques sont les mêmes.

Pour cet exemple particulier avec de nombreuses lignes dans le groupe, une version plus efficace serait un CTE récursif.

WITH    RecursiveCTE
AS      (
        SELECT TOP 1 id, [group]
        FROM T
        ORDER BY [group]
        UNION   ALL
        SELECT  R.id, R.[group]
        FROM    (
                SELECT  T.*,
                        rn = ROW_NUMBER() OVER (ORDER BY (SELECT 0))
                FROM    T
                JOIN    RecursiveCTE R
                        ON  R.[group] < T.[group]
                ) R
        WHERE   R.rn = 1
        )
SELECT  *
FROM    RecursiveCTE
OPTION  (MAXRECURSION 0);

Ce qui donne

Table 'Worktable'. Scan count 2, logical reads 19
Table 'T'. Scan count 4, logical reads 12

Les lectures logiques sont bien moindres car il récupère la première ligne par groupe puis cherche dans le groupe suivant plutôt que de lire une charge d'enregistrements qui ne contribuent pas au résultat final.