Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

SQL Server seleziona un valore casuale (o primo) con l'aggregazione

Esiste un aggregato non documentato chiamato ANY che non è una sintassi valida ma è possibile apparire nei tuoi piani di esecuzione. Tuttavia, ciò non fornisce alcun vantaggio in termini di prestazioni.

Assumendo la seguente tabella e struttura dell'indice

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

Ho anche popolato con dati di esempio in modo tale che ci siano molte righe per gruppo.

La tua domanda originale

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

Fornisce Table 'T'. Scan count 1, logical reads 1367 e il piano

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

Riscritto per ottenere ANY aggregato...

;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

Fornisce Table 'T'. Scan count 1, logical reads 1367 e il piano

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

Anche se potenzialmente SQL Server potrebbe interrompere l'elaborazione del gruppo non appena viene trovato il primo valore e passare a quello successivo, non è così. Elabora ancora tutte le righe e le letture logiche sono le stesse.

Per questo esempio particolare con molte righe nel gruppo, una versione più efficiente sarebbe un CTE ricorsivo.

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);

Che dà

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

Le letture logiche sono molto inferiori in quanto recupera la prima riga per gruppo, quindi cerca nel gruppo successivo anziché leggere un carico di record che non contribuiscono al risultato finale.