SQL Server通过聚合选择随机(或第一)值

Kar*_*arl 3 sql-server random aggregation sql-server-2008 sql-execution-plan

我怎样才能让SQL Server返回第一个值(任何一个,我不在乎,它只需要快速)聚合时会遇到什么?

例如,假设我有:

ID      Group
1       A
2       A
3       A
4       B
5       B
Run Code Online (Sandbox Code Playgroud)

我需要为每个组获取任何一个ID.我可以这样做:

Select 
max(id)
,group 
from Table 
group by group
Run Code Online (Sandbox Code Playgroud)

返回

ID      Group
3       A
5       B
Run Code Online (Sandbox Code Playgroud)

这样做,但是当我要求SQL Server计算最高ID时,我真的需要做的就是选择它遇到的第一个ID,这似乎很愚蠢.

谢谢

PS - 字段被编入索引,所以它可能没有什么区别?

Mar*_*ith 5

有一个未记录的聚合调用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
Run Code Online (Sandbox Code Playgroud)

我还填充了样本数据,每组有很多行.

你的原始查询

SELECT MAX(id),
       [group]
FROM   T
GROUP  BY [group]  
Run Code Online (Sandbox Code Playgroud)

给予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)
Run Code Online (Sandbox Code Playgroud)

重写以获得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
Run Code Online (Sandbox Code Playgroud)

给予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)
Run Code Online (Sandbox Code Playgroud)

即使潜在的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);
Run Code Online (Sandbox Code Playgroud)

这使

Table 'Worktable'. Scan count 2, logical reads 19
Table 'T'. Scan count 4, logical reads 12
Run Code Online (Sandbox Code Playgroud)

逻辑读取要少得多,因为它检索每组的第一行然后搜索到下一组,而不是读取对最终结果没有贡献的记录.