【问题标题】:SQL Server pick random (or first) value with aggregationSQL Server 使用聚合选择随机(或第一个)值
【发布时间】:2011-12-06 21:18:55
【问题描述】:

如何让 SQL Server 返回聚合时遇到的第一个值(任何一个,我不在乎,它只需要快)?

例如,假设我有:

ID      Group
1       A
2       A
3       A
4       B
5       B

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

Select 
max(id)
,group 
from Table 
group by group

返回

ID      Group
3       A
5       B

这样就可以了,但在我真正需要做的只是选择遇到的第一个 ID 时,让 SQL Server 计算最高 ID 对我来说似乎很愚蠢。

谢谢

PS - 字段已编入索引,所以也许它并没有真正的区别?

【问题讨论】:

    标签: sql-server sql-server-2008 random aggregation sql-execution-plan


    【解决方案1】:

    有一个名为ANYundocumented aggregate 语法无效,但可能会出现在您的执行计划中。然而,这并没有提供任何性能优势。

    假设如下表和索引结构

    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
    

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

    【讨论】:

    • 谢谢,这是一个很好的答案。我有一个额外的复杂性 - [group] 字段实际上是多个字段(在运行时动态确定,可以是一到十个字段)。我该如何调整这段代码来处理这个问题?
    • @Karl - 听起来你需要动态 SQL 来处理这个问题。
    猜你喜欢
    • 1970-01-01
    • 2012-11-06
    • 2018-12-04
    • 2012-04-15
    • 1970-01-01
    • 1970-01-01
    • 2019-12-17
    • 1970-01-01
    • 2018-04-06
    相关资源
    最近更新 更多