があります文書化されていない集計
ANY
と呼ばれます これは有効な構文ではありませんが、実行プランに表示される可能性があります。ただし、これによってパフォーマンスが向上することはありません。
次のテーブルとインデックス構造を想定しています
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
また、グループごとに多くの行があるように、サンプルデータを入力しました。
元のクエリ
SELECT MAX(id),
[group]
FROM T
GROUP BY [group]
Table 'T'. Scan count 1, logical reads 1367
と計画
|--Stream Aggregate(GROUP BY:([[T].[group]) DEFINE:([Expr1003]=MAX([[T].[id])))
|--Index Scan(OBJECT:([[T].[ix]), ORDERED FORWARD)
ANY
を取得するように書き直されました 集計...
;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
Table 'T'. Scan count 1, logical reads 1367
と計画
|--Stream Aggregate(GROUP BY:([[T].[group]) DEFINE:([[T].[id]=ANY([[T].[id])))
|--Index Scan(OBJECT:([[T].[ix]), ORDERED FORWARD)
SQL Serverは、最初の値が見つかるとすぐにグループの処理を停止し、次の値にスキップする可能性がありますが、そうではありません。それでもすべての行を処理し、論理読み取りは同じです。
グループ内に多くの行があるこの特定の例では、より効率的なバージョンは再帰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);
Table 'Worktable'. Scan count 2, logical reads 19
Table 'T'. Scan count 4, logical reads 12
論理的な読み取りは、グループごとに最初の行を取得してから、最終結果に寄与しない大量のレコードを読み取るのではなく、次のグループを探すため、はるかに少なくなります。