Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

SQL Server wählt einen zufälligen (oder ersten) Wert mit Aggregation aus

Es gibt einen undokumentiertes Aggregat namens ANY Dies ist keine gültige Syntax, kann aber in Ihren Ausführungsplänen erscheinen. Dies bringt jedoch keinen Performance-Vorteil.

Unter der Annahme der folgenden Tabellen- und Indexstruktur

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

Ich habe auch Beispieldaten so aufgefüllt, dass es viele Zeilen pro Gruppe gibt.

Ihre ursprüngliche Anfrage

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

Ergibt Table 'T'. Scan count 1, logical reads 1367 und der Plan

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

Umgeschrieben, um den ANY zu erhalten aggregieren...

;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

Ergibt Table 'T'. Scan count 1, logical reads 1367 und der Plan

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

Auch wenn SQL Server möglicherweise die Verarbeitung der Gruppe stoppen könnte, sobald der erste Wert gefunden wird, und zum nächsten überspringt, tut dies dies nicht. Es verarbeitet immer noch alle Zeilen und die logischen Lesevorgänge sind die gleichen.

Für dieses spezielle Beispiel mit vielen Zeilen in der Gruppe wäre eine effizientere Version ein rekursiver CTE.

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

Was gibt

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

Die logischen Lesevorgänge sind viel geringer, da die erste Zeile pro Gruppe abgerufen wird und dann in der nächsten Gruppe gesucht wird, anstatt eine Menge Datensätze zu lesen, die nicht zum Endergebnis beitragen.