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.