Database
 sql >> Base de Dados >  >> RDS >> Database

Números de linha com ordem não determinística


A função de janela ROW_NUMBER tem inúmeras aplicações práticas, muito além das necessidades óbvias de classificação. Na maioria das vezes, quando você calcula números de linha, você precisa calculá-los com base em alguma ordem e fornece a especificação de ordenação desejada na cláusula de ordem da janela da função. No entanto, há casos em que você precisa calcular os números das linhas em nenhuma ordem específica; em outras palavras, com base na ordem não determinística. Isso pode ocorrer em todo o resultado da consulta ou em partições. Os exemplos incluem atribuir valores exclusivos a linhas de resultados, desduplicar dados e retornar qualquer linha por grupo.

Observe que a necessidade de atribuir números de linha com base na ordem não determinística é diferente da necessidade de atribuí-los com base na ordem aleatória. Com o primeiro, você simplesmente não se importa com a ordem em que eles são atribuídos e se as execuções repetidas da consulta continuam atribuindo os mesmos números de linha às mesmas linhas ou não. Com o último, você espera que execuções repetidas continuem alterando quais linhas são atribuídas a quais números de linha. Este artigo explora diferentes técnicas para calcular números de linha com ordem não determinística. A esperança é encontrar uma técnica que seja confiável e ótima.

Agradecimento especial ao Paul White pela dica sobre a dobra constante, pela técnica de constante de tempo de execução e por ser sempre uma ótima fonte de informação!

Quando a ordem importa


Começarei com casos em que a ordem do número da linha importa.

Usarei uma tabela chamada T1 em meus exemplos. Use o código a seguir para criar esta tabela e preenchê-la com dados de exemplo:
SET NOCOUNT ON;
 
USE tempdb;
 
DROP TABLE IF EXISTS dbo.T1;
GO
 
CREATE TABLE dbo.T1
(
  id INT NOT NULL CONSTRAINT PK_T1 PRIMARY KEY,
  grp VARCHAR(10) NOT NULL,
  datacol INT NOT NULL
);
 
INSERT INTO dbo.T1(id, grp, datacol) VALUES
  (11, 'A', 50),
  ( 3, 'B', 20),
  ( 5, 'A', 40),
  ( 7, 'B', 10),
  ( 2, 'A', 50);

Considere a seguinte consulta (vamos chamá-la de Consulta 1):
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n 
FROM dbo.T1;

Aqui você quer que os números de linha sejam atribuídos dentro de cada grupo identificado pela coluna grp, ordenados pela coluna datacol. Quando executei esta consulta no meu sistema, obtive a seguinte saída:
id  grp  datacol  n
--- ---- -------- ---
5   A    40       1
2   A    50       2
11  A    50       3
7   B    10       1
3   B    20       2

Os números de linha são atribuídos aqui em uma ordem parcialmente determinística e parcialmente não determinística. O que quero dizer com isso é que você tem a garantia de que, na mesma partição, uma linha com um valor de datacol maior obterá um valor de número de linha maior. No entanto, como datacol não é exclusivo dentro da partição grp, a ordem de atribuição dos números de linha entre as linhas com os mesmos valores grp e datacol não é determinística. Tal é o caso das linhas com os valores de id 2 e 11. Ambas têm o valor grp A e o valor datacol 50. Quando executei esta consulta no meu sistema pela primeira vez, a linha com id 2 obteve o número 2 e o a linha com id 11 obteve a linha número 3. Não importa a probabilidade de isso acontecer na prática no SQL Server; se eu executar a consulta novamente, teoricamente, a linha com id 2 poderia ser atribuída com a linha número 3 e a linha com id 11 poderia ser atribuída com a linha número 2.

Se você precisar atribuir números de linha com base em uma ordem completamente determinística, garantindo resultados repetíveis nas execuções da consulta, desde que os dados subjacentes não sejam alterados, você precisará que a combinação de elementos nas cláusulas de particionamento e ordenação da janela seja exclusiva. Isso pode ser alcançado em nosso caso adicionando o id da coluna à cláusula de ordem da janela como um desempate. A cláusula OVER seria então:
OVER (PARTITION BY grp ORDER BY datacol, id)

De qualquer forma, ao calcular números de linha com base em alguma especificação de ordenação significativa, como na Consulta 1, o SQL Server precisa processar as linhas ordenadas pela combinação de particionamento de janela e elementos de ordenação. Isso pode ser obtido extraindo os dados pré-ordenados de um índice ou classificando os dados. No momento não há índice em T1 para dar suporte ao cálculo ROW_NUMBER na Consulta 1, então o SQL Server tem que optar por ordenar os dados. Isso pode ser visto no plano da Consulta 1 mostrado na Figura 1.

Figura 1:planejar a consulta 1 sem um índice de suporte

Observe que o plano verifica os dados do índice clusterizado com uma propriedade Ordered:False. Isso significa que a varredura não precisa retornar as linhas ordenadas pela chave de índice. Esse é o caso, pois o índice clusterizado é usado aqui apenas porque cobre a consulta e não por causa de sua ordem de chave. O plano então aplica uma classificação, resultando em custo extra, escala N Log N e tempo de resposta atrasado. O operador Segment produz um sinalizador indicando se a linha é a primeira na partição ou não. Por fim, o operador Sequence Project atribui números de linha começando com 1 em cada partição.

Se você quiser evitar a necessidade de classificação, poderá preparar um índice de cobertura com uma lista de chaves baseada nos elementos de particionamento e ordenação e uma lista de inclusão baseada nos elementos de cobertura. Eu gosto de pensar neste índice como um índice POC (para particionamento , pedido e cobertura ). Aqui está a definição do POC que suporta nossa consulta:
CREATE INDEX idx_grp_data_i_id ON dbo.T1(grp, datacol) INCLUDE(id);

Execute a Consulta 1 novamente:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n 
FROM dbo.T1;

O plano para esta execução é mostrado na Figura 2.

Figura 2:planejar a consulta 1 com um índice POC

Observe que desta vez o plano varre o índice POC com uma propriedade Ordered:True. Isso significa que a verificação garante que as linhas serão retornadas na ordem da chave de índice. Como os dados são extraídos pré-ordenados do índice, como a função de janela precisa, não há necessidade de classificação explícita. O dimensionamento deste plano é linear e o tempo de resposta é bom.

Quando a ordem não importa


As coisas ficam um pouco complicadas quando você precisa atribuir números de linha com uma ordem completamente não determinística. A coisa natural a fazer nesse caso é usar a função ROW_NUMBER sem especificar uma cláusula de ordem de janela. Primeiro, vamos verificar se o padrão SQL permite isso. Aqui está a parte relevante do padrão que define as regras de sintaxe para funções de janela:
Regras de sintaxe



5) Seja WNS o . Seja WDX um descritor de estrutura de janela que descreve a janela definida pelo WNS.

6) Se for especificado , , ou ROW_NUMBER, então:

a) Se , , RANK ou DENSE_RANK for especificado, então a cláusula de ordenação de janela WOC do WDX deve estar presente.



f) ROW_NUMBER() OVER WNS é equivalente à :COUNT (*) OVER (WNS1 ROWS UNBOUNDED PRECEDING)


Observe que o item 6 lista as funções , , ou ROW_NUMBER, e então o item 6a diz que para as funções , , RANK ou DENSE_RANK a cláusula de ordem de janela deve estar presente. Não há linguagem explícita informando se ROW_NUMBER requer ou não uma cláusula de ordem de janela, mas a menção da função no item 6 e sua omissão em 6a pode implicar que a cláusula é opcional para esta função. É bastante óbvio porque funções como RANK e DENSE_RANK exigiriam uma cláusula de pedido de janela, já que essas funções se especializam em lidar com empates, e os empates só existem quando há especificação de pedido. No entanto, você certamente pode ver como a função ROW_NUMBER pode se beneficiar de uma cláusula opcional de ordem de janela.

Então, vamos tentar e tentar calcular números de linha sem ordenação de janela no SQL Server:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER() AS n 
FROM dbo.T1;

Essa tentativa resulta no seguinte erro:
Msg 4112, Level 15, State 1, Line 53
A função 'ROW_NUMBER' deve ter uma cláusula OVER com ORDER BY.
De fato, se você verificar a documentação do SQL Server da função ROW_NUMBER, encontrará o seguinte texto:
“order_by_clause

A cláusula ORDER BY determina a sequência na qual as linhas recebem seu ROW_NUMBER exclusivo em uma partição especificada. É obrigatório.”
Então, aparentemente, a cláusula de ordem da janela é obrigatória para a função ROW_NUMBER no SQL Server. Esse também é o caso da Oracle, a propósito.

Devo dizer que não tenho certeza se entendi o raciocínio por trás desse requisito. Lembre-se de que você está permitindo definir números de linha com base em uma ordem parcialmente não determinística, como na Consulta 1. Então, por que não permitir o não determinismo o tempo todo? Talvez haja algum motivo no qual não estou pensando. Se você pode pensar em tal motivo, por favor, compartilhe.

De qualquer forma, você pode argumentar que, se não se importa com o pedido, já que a cláusula de pedido da janela é obrigatória, você pode especificar qualquer pedido. O problema com essa abordagem é que, se você ordenar por alguma coluna da(s) tabela(s) consultada(s), isso pode envolver uma penalidade de desempenho desnecessária. Quando não houver um índice de suporte, você pagará pela classificação explícita. Quando há um índice de suporte em vigor, você está limitando o mecanismo de armazenamento a uma estratégia de verificação de ordem de índice (seguindo a lista vinculada de índice). Você não permite mais flexibilidade como geralmente ocorre quando a ordem não importa na escolha entre uma verificação de ordem de índice e uma verificação de ordem de alocação (com base em páginas do IAM).

Uma ideia que vale a pena tentar é especificar uma constante, como 1, na cláusula de ordem da janela. Se suportado, você espera que o otimizador seja inteligente o suficiente para perceber que todas as linhas têm o mesmo valor, portanto, não há relevância real de ordenação e, portanto, não há necessidade de forçar uma classificação ou varredura de ordem de índice. Aqui está uma consulta tentando essa abordagem:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1) AS n 
FROM dbo.T1;

Infelizmente, o SQL Server não oferece suporte a essa solução. Ele gera o seguinte erro:
Msg 5308, Level 16, State 1, Line 56
Funções em janela, agregações e funções NEXT VALUE FOR não suportam índices inteiros como expressões de cláusula ORDER BY.
Aparentemente, o SQL Server assume que se você estiver usando uma constante inteira na cláusula window order, ela representa uma posição ordinal de um elemento na lista SELECT, como quando você especifica um inteiro na cláusula ORDER BY de apresentação. Se for esse o caso, outra opção que vale a pena tentar é especificar uma constante não inteira, assim:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 'No Order') AS n 
FROM dbo.T1;

Acontece que esta solução também não é suportada. O SQL Server gera o seguinte erro:
Msg 5309, Level 16, State 1, Line 65
Funções em janela, agregações e funções NEXT VALUE FOR não suportam constantes como expressões de cláusula ORDER BY.
Aparentemente, a cláusula window order não suporta nenhum tipo de constante.

Até agora, aprendemos o seguinte sobre a relevância de ordenação da janela da função ROW_NUMBER no SQL Server:
  1. ORDER BY é obrigatório.
  2. Não é possível ordenar por uma constante inteira, pois o SQL Server acha que você está tentando especificar uma posição ordinal no SELECT.
  3. Não é possível ordenar por nenhum tipo de constante.

A conclusão é que você deve ordenar por expressões que não são constantes. Obviamente, você pode ordenar por uma lista de colunas da(s) tabela(s) consultada(s). Mas estamos em busca de uma solução eficiente onde o otimizador possa perceber que não há relevância de ordenação.

Dobragem constante


A conclusão até agora é que você não pode usar constantes na cláusula de ordem de janela do ROW_NUMBER, mas e as expressões baseadas em constantes, como na consulta a seguir:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+0) AS n 
FROM dbo.T1;

No entanto, essa tentativa é vítima de um processo conhecido como dobra constante, que normalmente tem um impacto positivo no desempenho das consultas. A ideia por trás dessa técnica é melhorar o desempenho da consulta dobrando alguma expressão baseada em constantes para suas constantes de resultado em um estágio inicial do processamento da consulta. Você pode encontrar detalhes sobre quais tipos de expressões podem ser dobradas constantemente aqui. Nossa expressão 1+0 é dobrada para 1, resultando no mesmo erro que você obteve ao especificar a constante 1 diretamente:
Msg 5308, Level 16, State 1, Line 79
Funções em janela, agregações e funções NEXT VALUE FOR não suportam índices inteiros como expressões de cláusula ORDER BY.
Você enfrentaria uma situação semelhante ao tentar concatenar dois literais de cadeia de caracteres, assim:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 'No' + ' Order') AS n 
FROM dbo.T1;

Você obtém o mesmo erro ao especificar o literal 'Sem pedido' diretamente:
Msg 5309, Level 16, State 1, Line 55
Funções em janela, agregações e funções NEXT VALUE FOR não suportam constantes como expressões de cláusula ORDER BY.

Mundo bizarro – erros que previnem erros


A vida é cheia de surpresas…

Uma coisa que impede a dobra constante é quando a expressão normalmente resulta em um erro. Por exemplo, a expressão 2147483646+1 pode ser dobrada constantemente, pois resulta em um valor válido do tipo INT. Consequentemente, uma tentativa de executar a seguinte consulta falha:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 2147483646+1) AS n 
FROM dbo.T1;
Msg 5308, Level 16, State 1, Line 109
Funções em janela, agregações e funções NEXT VALUE FOR não suportam índices inteiros como expressões de cláusula ORDER BY.
No entanto, a expressão 2147483647+1 não pode ser dobrada constantemente porque tal tentativa resultaria em um erro de estouro de INT. A implicação na ordenação é bastante interessante. Tente a seguinte consulta (chamaremos esta de Consulta 2):
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 2147483647+1) AS n 
FROM dbo.T1;

Estranhamente, esta consulta é executada com sucesso! O que acontece é que, por um lado, o SQL Server não consegue aplicar a dobra constante e, portanto, a ordenação é baseada em uma expressão que não é uma única constante. Por outro lado, o otimizador calcula que o valor de ordenação é o mesmo para todas as linhas, portanto, ignora completamente a expressão de ordenação. Isso é confirmado ao examinar o plano para essa consulta, conforme mostrado na Figura 3.

Figura 3:plano para consulta 2

Observe que o plano varre algum índice de cobertura com uma propriedade Ordered:False. Este era exatamente o nosso objetivo de desempenho.

De maneira semelhante, a consulta a seguir envolve uma tentativa de dobra constante bem-sucedida e, portanto, falha:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1/1) AS n 
FROM dbo.T1;
Msg 5308, Level 16, State 1, Line 123
Funções em janela, agregações e funções NEXT VALUE FOR não suportam índices inteiros como expressões de cláusula ORDER BY.
A consulta a seguir envolve uma tentativa de dobra constante com falha e, portanto, é bem-sucedida, gerando o plano mostrado anteriormente na Figura 3:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1/0) AS n 
FROM dbo.T1;

A consulta a seguir envolve uma tentativa de dobra constante bem-sucedida (o literal VARCHAR '1' é convertido implicitamente para INT 1 e, em seguida, 1 + 1 é dobrado para 2) e, portanto, falha:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+'1') AS n 
FROM dbo.T1;
Msg 5308, Level 16, State 1, Line 134
Funções em janela, agregações e funções NEXT VALUE FOR não suportam índices inteiros como expressões de cláusula ORDER BY.
A consulta a seguir envolve uma tentativa de dobra constante com falha (não é possível converter 'A' em INT) e, portanto, é bem-sucedida, gerando o plano mostrado anteriormente na Figura 3:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+'A') AS n 
FROM dbo.T1;

Para ser honesto, mesmo que essa técnica bizarra atinja nosso objetivo de desempenho original, não posso dizer que a considero segura e, portanto, não me sinto tão confortável em confiar nela.

Constantes de tempo de execução baseadas em funções


Continuando a busca por uma boa solução para calcular números de linha com ordem não determinística, existem algumas técnicas que parecem mais seguras do que a última solução peculiar:usar constantes de tempo de execução baseadas em funções, usar uma subconsulta baseada em uma constante, usar uma coluna com alias baseada em uma constante e usando uma variável.

Como explico em bugs, armadilhas e práticas recomendadas do T-SQL – determinismo, a maioria das funções no T-SQL são avaliadas apenas uma vez por referência na consulta – não uma vez por linha. Este é o caso mesmo com a maioria das funções não determinísticas como GETDATE e RAND. Há muito poucas exceções a essa regra, como as funções NEWID e CRYPT_GEN_RANDOM, que são avaliadas uma vez por linha. A maioria das funções, como GETDATE, @@SPID e muitas outras, são avaliadas uma vez no início da consulta e seus valores são considerados constantes de tempo de execução. Uma referência a essas funções não é dobrada constantemente. Essas características tornam uma constante de tempo de execução baseada em uma função uma boa escolha como elemento de ordenação de janela e, de fato, parece que o T-SQL o suporta. Ao mesmo tempo, o otimizador percebe que na prática não há relevância de ordenação, evitando penalidades de desempenho desnecessárias.

Aqui está um exemplo usando a função GETDATE:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY GETDATE()) AS n 
FROM dbo.T1;

Essa consulta obtém o mesmo plano mostrado anteriormente na Figura 3.

Aqui está outro exemplo usando a função @@SPID (retornando o ID da sessão atual):
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY @@SPID) AS n 
FROM dbo.T1;

E a função PI? Tente a seguinte consulta:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY PI()) AS n 
FROM dbo.T1;

Este falha com o seguinte erro:
Msg 5309, Level 16, State 1, Line 153
Funções em janela, agregações e funções NEXT VALUE FOR não suportam constantes como expressões de cláusula ORDER BY.
Funções como GETDATE e @@SPID são reavaliadas uma vez por execução do plano, para que não possam ser dobradas constantemente. PI representa sempre a mesma constante e, portanto, fica constante dobrada.

Como mencionado anteriormente, há muito poucas funções que são avaliadas uma vez por linha, como NEWID e CRYPT_GEN_RANDOM. Isso os torna uma má escolha como elemento de ordenação da janela se você precisar de uma ordem não determinística — para não confundir com ordem aleatória. Por que pagar uma multa de classificação desnecessária?

Aqui está um exemplo usando a função NEWID:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY NEWID()) AS n 
FROM dbo.T1;

O plano para esta consulta é mostrado na Figura 4, confirmando que o SQL Server adicionou classificação explícita com base no resultado da função.

Figura 4:planejar a consulta 3

Se você deseja que os números das linhas sejam atribuídos em ordem aleatória, por todos os meios, essa é a técnica que você deseja usar. Você só precisa estar ciente de que isso incorre no custo de classificação.

Usando uma subconsulta


Você também pode usar uma subconsulta baseada em uma constante como a expressão de ordenação da janela (por exemplo, ORDER BY (SELECT 'No Order')). Também com esta solução, o otimizador do SQL Server reconhece que não há relevância de ordenação e, portanto, não impõe uma classificação desnecessária ou limita as escolhas do mecanismo de armazenamento àquelas que devem garantir a ordem. Tente executar a seguinte consulta como exemplo:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 'No Order')) AS n 
FROM dbo.T1;

Você obtém o mesmo plano mostrado anteriormente na Figura 3.

Um dos grandes benefícios desta técnica é que você pode adicionar seu próprio toque pessoal. Talvez você realmente goste de NULLs:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n 
FROM dbo.T1;

Talvez você realmente goste de um determinado número:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 42)) AS n 
FROM dbo.T1;

Talvez você queira enviar uma mensagem a alguém:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 'Lilach, will you marry me?')) AS n 
FROM dbo.T1;

Você entendeu.

Facilável, mas difícil


Existem algumas técnicas que funcionam, mas são um pouco estranhas. Uma é definir um alias de coluna para uma expressão com base em uma constante e, em seguida, usar esse alias de coluna como o elemento de ordenação da janela. Você pode fazer isso usando uma expressão de tabela ou com o operador CROSS APPLY e um construtor de valor de tabela. Aqui está um exemplo para o último:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY [I'm a bit ugly]) AS n 
FROM dbo.T1 CROSS APPLY ( VALUES('No Order') ) AS A([I'm a bit ugly]);

Você obtém o mesmo plano mostrado anteriormente na Figura 3.

Outra opção é usar uma variável como elemento de ordenação da janela:
DECLARE @ImABitUglyToo AS INT = NULL;
 
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY @ImABitUglyToo) AS n 
FROM dbo.T1;

Essa consulta também obtém o plano mostrado anteriormente na Figura 3.

E se eu usar minha própria UDF?


Você pode pensar que usar sua própria UDF que retorna uma constante pode ser uma boa escolha como elemento de ordenação da janela quando você deseja uma ordem não determinística, mas não é. Considere a seguinte definição de UDF como exemplo:
DROP FUNCTION IF EXISTS dbo.YouWillRegretThis;
GO
 
CREATE FUNCTION dbo.YouWillRegretThis() RETURNS INT
AS
BEGIN
  RETURN NULL
END;
GO

Tente usar a UDF como a cláusula de ordenação da janela, assim (chamaremos esta de Consulta 4):
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY dbo.YouWillRegretThis()) AS n 
FROM dbo.T1;

Antes do SQL Server 2019 (ou nível de compatibilidade paralela <150), as funções definidas pelo usuário são avaliadas por linha. Mesmo que eles retornem uma constante, eles não são alinhados. Consequentemente, por um lado, você pode usar uma UDF como o elemento de ordenação da janela, mas, por outro lado, isso resulta em uma penalidade de classificação. Isso é confirmado examinando o plano para essa consulta, conforme mostrado na Figura 5.

Figura 5:planejar a consulta 4

A partir do SQL Server 2019, no nível de compatibilidade>=150, essas funções definidas pelo usuário são incorporadas, o que é ótimo, mas no nosso caso resulta em um erro:
Msg 5309, Level 16, State 1, Line 217
Funções em janela, agregações e funções NEXT VALUE FOR não suportam constantes como expressões de cláusula ORDER BY.
Portanto, usar uma UDF baseada em uma constante como o elemento de ordenação da janela força uma classificação ou um erro, dependendo da versão do SQL Server que você está usando e do nível de compatibilidade do banco de dados. Resumindo, não faça isso.

Números de linhas particionados com ordem não determinística


Um caso de uso comum para números de linha particionados com base em ordem não determinística é retornar qualquer linha por grupo. Dado que, por definição, existe um elemento de particionamento neste cenário, você pensaria que uma técnica segura nesse caso seria usar o elemento de particionamento de janela também como o elemento de ordenação de janela. Como primeiro passo, você calcula os números das linhas assim:
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n 
FROM dbo.T1;

O plano para esta consulta é mostrado na Figura 6.

Figura 6:plano para a consulta 5

O motivo pelo qual nosso índice de suporte é verificado com uma propriedade Ordered:True é porque o SQL Server precisa processar as linhas de cada partição como uma única unidade. Esse é o caso antes da filtragem. Se você filtrar apenas uma linha por partição, terá algoritmos baseados em ordem e baseados em hash como opções.

A segunda etapa é colocar a consulta com o cálculo do número da linha em uma expressão de tabela e, na consulta externa, filtrar a linha com o número da linha 1 em cada partição, assim:
WITH C AS
(
  SELECT id, grp, datacol,
    ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n 
  FROM dbo.T1
)
SELECT id, grp, datacol
FROM C
WHERE n = 1;

Teoricamente, essa técnica deve ser segura, mas Paul white encontrou um bug que mostra que, usando esse método, você pode obter atributos de diferentes linhas de origem na linha de resultado retornada por partição. Usar uma constante de tempo de execução baseada em uma função ou uma subconsulta baseada em uma constante como o elemento de ordenação parece ser seguro mesmo com esse cenário, portanto, certifique-se de usar uma solução como a seguinte:
WITH C AS
(
  SELECT id, grp, datacol,
    ROW_NUMBER() OVER(PARTITION BY grp ORDER BY (SELECT 'No Order')) AS n 
  FROM dbo.T1
)
SELECT id, grp, datacol
FROM C
WHERE n = 1;

Ninguém passará por aqui sem minha permissão


Tentar calcular números de linha com base em ordem não determinística é uma necessidade comum. Teria sido bom se o T-SQL simplesmente tornasse a cláusula de ordem da janela opcional para a função ROW_NUMBER, mas isso não acontece. Caso contrário, teria sido bom se pelo menos permitisse usar uma constante como elemento de ordenação, mas essa também não é uma opção suportada. Mas se você perguntar bem, na forma de uma subconsulta baseada em uma constante ou uma constante de tempo de execução baseada em uma função, o SQL Server permitirá. Estas são as duas opções com as quais me sinto mais confortável. Eu realmente não me sinto confortável com as expressões errôneas peculiares que parecem funcionar, então não posso recomendar essa opção.