SQL Server выбирает случайное (или первое) значение с агрегацией - PullRequest
3 голосов
/ 13 октября 2011

Как я могу заставить SQL Server возвращать первое значение (любое, мне все равно, оно должно быть быстрым), с которым оно сталкивается при агрегировании?

Например, допустим, у меня есть:

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

и мне нужно получить один из идентификаторов для каждой группы.Я могу сделать это следующим образом:

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

, который возвращает

ID      Group
3       A
5       B

Это делает работу, но мне кажется глупым просить SQL Server вычислить самый высокий идентификатор, когда все этона самом деле нужно выбрать первый встреченный идентификатор.

Спасибо

PS - поля проиндексированы, так что, может быть, это не имеет значения?

1 Ответ

5 голосов
/ 13 октября 2011

Существует недокументированный агрегат с именем 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

Логических чтений намного меньше, поскольку он получает первую строку для каждой группы, а затем ищет следующую группу, а не читает множество записей, которые не влияют на конечный результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...