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

Desafio T-SQL das Ilhas


Recentemente, fui apresentado a um novo desafio de ilhas pelo meu amigo Erland Sommarskog. É baseado em uma pergunta de um fórum de banco de dados público. O desafio em si não é complicado de lidar ao usar técnicas conhecidas, que empregam principalmente funções de janela. No entanto, essas técnicas exigem classificação explícita, apesar da presença de um índice de suporte. Isso afeta a escalabilidade e o tempo de resposta das soluções. Apaixonado por desafios, decidi encontrar uma solução para minimizar o número de operadores de classificação explícitos no plano, ou melhor ainda, eliminar completamente a necessidade deles. E encontrei essa solução.

Começarei apresentando uma forma generalizada do desafio. Em seguida, mostrarei duas soluções baseadas em técnicas conhecidas, seguidas pela nova solução. Por fim, vou comparar o desempenho das diferentes soluções.

Eu recomendo que você tente encontrar uma solução antes de implementar o meu.

O desafio


Vou apresentar uma forma generalizada do desafio das ilhas originais de Erland.

Use o código a seguir para criar uma tabela chamada T1 e preenchê-la com um pequeno conjunto de dados de exemplo:
SET NOCOUNT ON; USE tempdb; DROP TABLE SE EXISTE dbo.T1 CREATE TABLE dbo.T1( grp VARCHAR(10) NOT NULL, ord INT NOT NULL, val VARCHAR(10) NOT NULL, CONSTRAINT PK_T1 PRIMARY KEY(grp, ord));GO INSERT INTO dbo. T1(grp, ord, val) VALORES ('Grupo A', 1002, 'Y'), ('Grupo A', 1003, 'Y'), ('Grupo A', 1005, 'Y'), (' Grupo A', 1007, 'N'), ('Grupo A', 1011, 'N'), ('Grupo A', 1013, 'N'), ('Grupo A', 1017, 'Y'), ('Grupo A', 1019, 'Y'), ('Grupo A', 1023, 'N'), ('Grupo A', 1029, 'N'), ('Grupo B', 1001, 'X' ), ('Grupo B', 1002, 'X'), ('Grupo B', 1003, 'Z'), ('Grupo B', 1005, 'Z'), ('Grupo B', 1008, ' Z'), ('Grupo B', 1013, 'Z'), ('Grupo B', 1021, 'Y'), ('Grupo B', 1034, 'Y');

O desafio é o seguinte:

Assumindo o particionamento com base na coluna grp e a ordenação com base na coluna ord, calcule os números de linha sequenciais começando com 1 dentro de cada grupo consecutivo de linhas com o mesmo valor na coluna val. A seguir está o resultado desejado para o pequeno conjunto de dados de amostra fornecido:
grp ord val seqno -------- ----- ---- ------Grupo A 1002 Y 1Grupo A 1003 Y 2Grupo A 1005 Y 3Grupo A 1007 N 1Grupo A 1011 N 2GRUPO A 1013 N 3GROUP A 1017 Y 1GRUPO A 1019 Y 2 GRUPO A 1023 N 1 GRUPO A 1029 N 2 GRUPO B 1001 X 1GROUP B 1002 X 2 GRUPO B 1003 Z 1 GRUPO B 1005 Z 2 Grupo B 1008 Z 3Group B 1013 Z 4Group B 1021 Y 1Group 1034 Y 2

Observe a definição da restrição de chave primária baseada na chave composta (grp, ord), que resulta em um índice clusterizado baseado na mesma chave. Este índice pode potencialmente suportar funções de janela particionadas por grp e ordenadas por ord.

Para testar o desempenho de sua solução, você precisará preencher a tabela com volumes maiores de dados de amostra. Use o código a seguir para criar a função auxiliar GetNums:
CREATE FUNCTION dbo.GetNums(@low AS BIGINT =1, @high AS BIGINT) RETURNS TABLEASRETURN WITH L0 AS ( SELECT 1 AS c FROM (VALUES(1),(1),(1),(1), (1),(1),(1),(1), (1),(1),(1),(1),(1),(1),(1),(1)) AS D (c) ), L1 AS ( SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B ), L2 AS ( SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B ), L3 AS ( SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B ), Nums AS ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum FROM L3 ) SELECT TOP(@high - @low + 1) rownum AS rn, @high + 1 - rownum AS op, @low - 1 + rownum AS n FROM Nums ORDER BY rownum;GO

Use o seguinte código para preencher T1, após alterar os parâmetros que representam o número de grupos, número de linhas por grupo e número de valores distintos conforme desejar:
DECLARE @numgroups AS INT =1000, @rowspergroup AS INT =10000, -- teste com 1000 a 10000 aqui @uniquevalues ​​AS INT =5; ALTER TABLE dbo.T1 DROP CONSTRAINT PK_T1; TRUNCATE TABLE dbo.T1; INSERT INTO dbo.T1 WITH(TABLOCK) (grp, ord, val) SELECT CAST(G.n AS VARCHAR(10)) AS grp, CAST(R.n AS INT) AS ord, CAST(ABS(CHECKSUM(NEWID())) % @uniquevalues ​​+ 1 AS VARCHAR(10)) AS val FROM dbo.GetNums(1, @numgroups) AS G CROSS JOIN dbo.GetNums(1, @rowspergroup) AS R; ALTER TABLE dbo.T1 ADD CONSTRAINT PK_T1 PRIMARY KEY CLUSTERED(grp, ord);

Em meus testes de desempenho, preenchi a tabela com 1.000 grupos, entre 1.000 e 10.000 linhas por grupo (de 1 a 10 milhões de linhas) e 5 valores distintos. Eu usei um SELECT INTO comando para escrever o resultado em uma tabela temporária.

Minha máquina de teste tem quatro CPUs lógicas, executando o SQL Server 2019 Enterprise.

Presumo que você esteja usando um ambiente projetado para oferecer suporte ao modo de lote no armazenamento de linhas diretamente, por exemplo, usando o SQL Server 2019 Enterprise Edition como o meu, ou indiretamente, criando um índice de armazenamento de colunas fictício na tabela.

Lembre-se, pontos extras se você conseguir encontrar uma solução eficiente sem classificação explícita no plano. Boa sorte!

É necessário um operador Sort na otimização das funções da janela?


Antes de abordar as soluções, um pouco de fundo de otimização para que o que você verá nos planos de consulta mais tarde faça mais sentido.

As técnicas mais comuns para resolver tarefas de ilhas como a nossa envolvem o uso de alguma combinação de funções de janela agregadas e/ou de classificação. O SQL Server pode processar essas funções de janela usando uma série de operadores de modo de linha mais antigos (Segment, Sequence Project, Segment, Window Spool, Stream Aggregate) ou o operador Window Aggregate de modo de lote mais recente e geralmente mais eficiente.

Em ambos os casos, os operadores que lidam com o cálculo da função de janela precisam ingerir os dados ordenados pelos elementos de particionamento e ordenação da janela. Se você não tiver um índice de suporte, naturalmente o SQL Server precisará introduzir um operador Sort no plano. Por exemplo, se você tiver várias funções de janela em sua solução com mais de uma combinação exclusiva de particionamento e ordenação, você terá uma classificação explícita no plano. Mas e se você tiver apenas uma combinação exclusiva de particionamento e ordenação e um índice de suporte?

O método de modo de linha mais antigo pode contar com dados pré-ordenados ingeridos de um índice sem a necessidade de um operador Sort explícito nos modos serial e paralelo. O novo operador do modo de lote elimina muitas das ineficiências da otimização do modo de linha mais antigo e tem os benefícios inerentes do processamento do modo de lote. No entanto, sua implementação paralela atual requer um operador Sort paralelo de modo de lote intermediário, mesmo quando um índice de suporte está presente. Apenas sua implementação serial pode contar com a ordem do índice sem um operador Sort. Isso é tudo para dizer quando o otimizador precisa escolher uma estratégia para lidar com sua função de janela, supondo que você tenha um índice de suporte, geralmente será uma das quatro opções a seguir:
  1. Modo de linha, serial, sem classificação
  2. Modo de linha, paralelo, sem classificação
  3. Modo de lote, serial, sem classificação
  4. Modo de lote, paralelo, classificação

O que resultar no menor custo do plano será escolhido, assumindo, é claro, que os pré-requisitos para paralelismo e modo de lote sejam atendidos ao considerá-los. Normalmente, para o otimizador justificar um plano paralelo, os benefícios do paralelismo precisam superar o trabalho extra, como sincronização de threads. Com a opção 4 acima, os benefícios do paralelismo precisam superar o trabalho extra usual envolvido com o paralelismo, além da ordenação extra.

Ao experimentar diferentes soluções para nosso desafio, tive casos em que o otimizador escolheu a opção 2 acima. Ele o escolheu apesar do fato de que o método de modo de linha envolve algumas ineficiências, porque os benefícios do paralelismo e da não classificação resultaram em um plano com um custo menor do que as alternativas. Em alguns desses casos, forçar um plano serial com uma dica resultou na opção 3 acima e em melhor desempenho.

Com este pano de fundo em mente, vamos olhar para as soluções. Vou começar com duas soluções baseadas em técnicas conhecidas para tarefas de ilhas que não podem escapar da classificação explícita no plano.

Solução baseada na técnica conhecida 1


Segue a primeira solução para o nosso desafio, que se baseia em uma técnica já conhecida há algum tempo:
WITH C AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY ord) - ROW_NUMBER() OVER(PARTITION BY grp, val ORDER BY ord) AS island FROM dbo.T1)SELECT grp, ord, val , ROW_NUMBER() OVER(PARTITION BY grp, val, ilha ORDER BY ord) AS seqnoFROM C;

Vou me referir a ela como Solução Conhecida 1.

O CTE chamado C é baseado em uma consulta que calcula dois números de linha. O primeiro (vou me referir a ele como rn1) é particionado por grp e ordenado por ord. O segundo (vou me referir a ele como rn2) é particionado por grp e val e ordenado por ord. Como rn1 tem intervalos entre diferentes grupos do mesmo val e rn2 não, a diferença entre rn1 e rn2 (coluna chamada ilha) é um valor constante único para todas as linhas com os mesmos valores de grp e val. A seguir está o resultado da consulta interna, incluindo os resultados dos cálculos do número de duas linhas, que não são retornados como colunas separadas:
grp ord val rn1 rn2 ilha------- ----- ---- ---- ---- -------Grupo A 1002 Y 1 1 0Grupo A 1003 Y 2 2 0Grupo A 1005 Y 3 3 0Grupo A 1007 N 4 1 3Grupo A 1011 N 5 2 3Grupo A 1013 N 6 3 3Grupo A 1017 Y 7 4 3Grupo A 1019 Y 8 5 3Grupo A 1023 N 9 4 5Grupo A 10 5 5Grupo B 1001 X 1 1 0Grupo B 1002 X 2 2 0Grupo B 1003 Z 3 1 2Grupo B 1005 Z 4 2 2Grupo B 1008 Z 5 3 2Grupo B 1013 Z 6 4 2Grupo B 1021 Y 7 1 6Grupo B 1034 Y 8 2 6 

O que resta para a consulta externa é calcular o seqno da coluna de resultado usando a função ROW_NUMBER, particionada por grp, val e island, e ordenada por ord, gerando o resultado desejado.

Observe que você pode obter o mesmo valor de ilha para diferentes valores de val na mesma partição, como na saída acima. É por isso que é importante usar grp, val e island como elementos de particionamento de janela e não somente grp e island.

Da mesma forma, se você estiver lidando com uma tarefa de ilhas exigindo que você agrupe os dados pela ilha e calcule os agregados do grupo, você agruparia as linhas por grp, val e ilha. Mas este não é o caso do nosso desafio. Aqui você foi encarregado de apenas computar números de linha independentemente para cada ilha.

A Figura 1 tem o plano padrão que obtive para esta solução em minha máquina após preencher a tabela com 10 milhões de linhas.

Figura 1:plano paralelo para solução conhecida 1

O cálculo de rn1 pode depender da ordem do índice. Assim, o otimizador escolheu a estratégia sem classificação + modo de linha paralela para este (primeiros operadores de Projeto de Segmento e Sequência no plano). Como os cálculos de rn2 e seqno usam suas próprias combinações exclusivas de elementos de particionamento e ordenação, a classificação explícita é inevitável para aqueles, independentemente da estratégia usada. Assim, o otimizador escolheu a estratégia ordenar + modo de lote paralelo para ambos. Este plano envolve dois operadores de classificação explícitos.

No meu teste de desempenho, essa solução levou 3,68 segundos para ser concluída em 1 milhão de linhas e 43,1 segundos em 10 milhões de linhas.

Como mencionado, testei todas as soluções também forçando um plano serial (com uma dica MAXDOP 1). O plano serial para esta solução é mostrado na Figura 1.

Figura 2:plano serial para solução conhecida 1

Como esperado, desta vez também o cálculo de rn1 usa a estratégia de modo de lote sem um operador Sort anterior, mas o plano ainda tem dois operadores Sort para os cálculos de número de linha subsequentes. O plano serial teve um desempenho pior que o paralelo na minha máquina com todos os tamanhos de entrada que testei, levando 4,54 segundos para ser concluído com 1 milhão de linhas e 61,5 segundos com 10 milhões de linhas.

Solução baseada na técnica conhecida 2


A segunda solução que apresentarei é baseada em uma técnica brilhante de detecção de ilhas que aprendi com Paul White alguns anos atrás. A seguir está o código completo da solução com base nessa técnica:
WITH C1 AS( SELECT *, CASE WHEN val =LAG(val) OVER(PARTITION BY grp ORDER BY ord) THEN 0 ELSE 1 END AS isstart FROM dbo.T1),C2 AS( SELECT *, SUM(isstart) OVER(PARTITION BY grp ORDER BY ord ROWS UNBOUNDED PRECEDING) AS island FROM C1)SELECT grp, ord, val, ROW_NUMBER() OVER(PARTITION BY grp, island ORDER BY ord) AS seqnoFROM C2;

Vou me referir a esta solução como Solução Conhecida 2.

A consulta que define os cálculos CTE C1 usa uma expressão CASE e a função de janela LAG (particionada por grp e ordenada por ord) para calcular uma coluna de resultado chamada isstart. Esta coluna tem o valor 0 quando o valor val atual é igual ao anterior e 1 caso contrário. Em outras palavras, é 1 quando a linha é a primeira em uma ilha e 0 caso contrário.

A seguir está a saída da consulta que define C1:
grp ord val isstart-------- ----- ---- --------Grupo A 1002 Y 1Grupo A 1003 Y 0Grupo A 1005 Y 0Grupo A 1007 N 1Grupo A 1011 n 0Group A 1013 n 0Group a 1017 y 1Group a 1019 y 0Group A 1023 n 1Group A 1029 n 0Group B 1001 x 1Group B 1002 X 0Group B 1003 Z 1Group B 1005 Z 0Group B 1008 Z 0Group B 1013 Z 0Group B 1021 1Grupo B 1034 Y 0

A mágica no que diz respeito à detecção de ilhas acontece no CTE C2. A consulta que a define calcula um identificador de ilha usando a função de janela SUM (também particionada por grp e ordenada por ord) aplicada à coluna isstart. A coluna de resultado com o identificador da ilha é chamada de ilha. Dentro de cada partição, você obtém 1 para a primeira ilha, 2 para a segunda ilha e assim por diante. Portanto, a combinação das colunas grp e ilha é um identificador de ilha, que você pode usar em tarefas de ilhas que envolvem agrupamento por ilha quando relevante.

A seguir está a saída da consulta que define C2:
grp ord val isstart island -------- ----- ---- -------- -------Grupo A 1002 Y 1 1Grupo A 1003 Y 0 1Grupo A 1005 Y 0 1Grupo A 1007 N 1 2Grupo A 1011 N 0 2Grupo A 1013 N 0 2Grupo A 1017 Y 1 3Grupo A 1019 Y 0 3Grupo A 1023 N 1 4Grupo A 1029 N 0 4Grupo B X 1001 X 1 1Grupo A 1029 N 0 4Grupo B X 1001 X 1 1 1Grupo B 1003 Z 1 2Grupo B 1005 Z 0 2Grupo B 1008 Z 0 2Grupo B 1013 Z 0 2Grupo B 1021 Y 1 3Grupo B 1034 Y 0 3

Por fim, a consulta externa calcula o seqno da coluna de resultado desejado com uma função ROW_NUMBER, particionada por grp e ilha e ordenada por ord. Observe que essa combinação de elementos de particionamento e ordenação é diferente daquela usada pelas funções de janela anteriores. Enquanto o cálculo das duas primeiras funções de janela pode depender potencialmente da ordem do índice, a última não pode.

A Figura 3 tem o plano padrão que obtive para esta solução.

Figura 3:plano paralelo para solução conhecida 2

Como você pode ver no plano, o cálculo das duas primeiras funções de janela usa a estratégia de modo sem classificação + linha paralela, e o cálculo da última usa a estratégia de modo de classificação + modo de lote paralelo.

Os tempos de execução que obtive para esta solução variaram de 2,57 segundos em 1 milhão de linhas a 46,2 segundos em 10 milhões de linhas.

Ao forçar o processamento serial, obtive o plano mostrado na Figura 4.

Figura 4:plano serial para solução conhecida 2

Como esperado, desta vez todos os cálculos de função de janela dependem da estratégia de modo de lote. Os dois primeiros sem ordenação anterior e o último com. Tanto o plano paralelo quanto o serial envolveram um operador Sort explícito. O plano serial teve um desempenho melhor do que o plano paralelo na minha máquina com os tamanhos de entrada que testei. Os tempos de execução que obtive para o plano serial forçado variaram de 1,75 segundos contra 1 milhão de linhas a 21,7 segundos contra 10 milhões de linhas.

Solução baseada em nova técnica


Quando Erland apresentou esse desafio em um fórum privado, as pessoas ficaram céticas quanto à possibilidade de resolvê-lo com uma consulta que havia sido otimizada com um plano sem classificação explícita. Isso é tudo que eu precisava ouvir para me motivar. Então, aqui está o que eu inventei:
WITH C1 AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY ord) AS rn, LAG(val) OVER(PARTITION BY grp ORDER BY ord) AS anterior FROM dbo.T1),C2 AS( SELECT *, MAX(CASE WHEN val =prev THEN NULL ELSE rn END) OVER(PARTITION BY grp ORDER BY ord ROWS UNBOUNDED PRECEDED) AS firstrn FROM C1)SELECT grp, ord, val, rn - firstrn + 1 AS seqnoFROM C2; 
A solução usa três funções de janela:LAG, ROW_NUMBER e MAX. O importante aqui é que todos os três são baseados em particionamento grp e ordenação ord, que está alinhado com a ordem de chave de índice de suporte.

A consulta que define o CTE C1 usa a função ROW_NUMBER para calcular os números das linhas (rn coluna) e a função LAG para retornar o valor val anterior (coluna anterior).

Aqui está a saída da consulta que define C1:
grp ord val rn anterior-------- ----- ---- --- -----Grupo A 1002 Y 1 NULLGrupo A 1003 Y 2 YGrupo A 1005 Y 3 YGrupo A 1007 N 4 YGrupo A 1011 N 5 NGgrupo A 1013 N 6 NGgrupo A 1017 Y 7 NGgrupo A 1019 Y 8 YGrupo A 1023 N 9 YGrupo A 1029 N 10 NGgrupo B 1001 X 1 NULLGrupo B 1002 X 2 XGrupo B 1003 Z 3 XGrupo B 1003 Z 3 1005 Z 4 ZGrupo B 1008 Z 5 ZGrupo B 1013 Z 6 ZGrupo B 1021 Y 7 ZGrupo B 1034 Y 8 Y

Observe quando val e prev são iguais, não é a primeira linha da ilha, caso contrário é.

Com base nessa lógica, a consulta que define o CTE C2 usa uma expressão CASE que retorna rn quando a linha é a primeira em uma ilha e NULL caso contrário. O código então aplica a função de janela MAX ao resultado da expressão CASE, retornando o primeiro rn da ilha (firstrn coluna).

Aqui está a saída da consulta que define C2, incluindo a saída da expressão CASE:
grp ord val rn anterior CASE firstrn-------- ----- ---- --- ----- ----- --------Grupo A 1002 Y 1 NULO 1 1Grupo A 1003 Y 2 Y NULO 1Grupo A 1005 Y 3 Y NULO 1Grupo A 1007 N 4 Y 4 4Grupo A 1011 N 5 N NULO 4Grupo A 1013 N 6 N NULO 4Grupo A 1017 Y 7 N 7 7Grupo A 19 Y 8 Y NULO 7Grupo A 1023 N 9 Y 9 9Grupo A 1029 N 10 N NULO 9Grupo B 1001 X 1 NULO 1 1Grupo B 1002 X 2 X NULO 1Grupo B 1003 Z 3 X 3 3Grupo B 1005 Z 4 Z NULO 3Grupo B 1008 5 Z NULL 3Grupo B 1013 Z 6 Z NULL 3Grupo B 1021 Y 7 Z 7 7Grupo B 1034 Y 8 Y NULL 7

O que resta para a consulta externa é calcular a coluna de resultado desejada seqno como rn menos firstrn mais 1.

A Figura 5 tem o plano paralelo padrão que obtive para esta solução ao testá-la usando uma instrução SELECT INTO, gravando o resultado em uma tabela temporária.

Figura 5:plano paralelo para uma nova solução

Não há operadores de classificação explícitos neste plano. No entanto, todas as três funções de janela são calculadas usando a estratégia sem classificação + modo de linha paralela, portanto, estamos perdendo os benefícios do processamento em lote. Os tempos de execução que obtive para esta solução com o plano paralelo variaram de 2,47 segundos contra 1 milhão de linhas e 41,4 contra 10 milhões de linhas.

Aqui, há uma probabilidade bastante alta de um plano serial com processamento em lote ser significativamente melhor, especialmente quando a máquina não possui muitas CPUs. Lembre-se de que estou testando minhas soluções em uma máquina com 4 CPUs lógicas. A Figura 6 tem o plano que obtive para esta solução ao forçar o processamento serial.

Figura 6:plano serial para uma nova solução

Todas as três funções de janela usam a estratégia de modo de lote sem classificação + serial. E os resultados são bastante impressionantes. Os tempos de execução dessa solução variaram de 0,5 segundos em relação a 1 milhão de linhas e 5,49 segundos em relação a 10 milhões de linhas. O que também é curioso nessa solução é que ao testá-la como uma instrução SELECT normal (com resultados descartados) em oposição a uma instrução SELECT INTO, o SQL Server escolheu o plano serial por padrão. Com as outras duas soluções, obtive um plano paralelo por padrão tanto com SELECT quanto com SELECT INTO.

Consulte a próxima seção para obter os resultados completos do teste de desempenho.

Comparação de desempenho


Aqui está o código que usei para testar as três soluções, é claro descomentando a dica MAXDOP 1 para testar os planos seriais:
-- Testar Solução Conhecida 1 DROP TABLE SE EXISTIR #Result; WITH C AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY ord) - ROW_NUMBER() OVER(PARTITION BY grp, val ORDER BY ord) AS island FROM dbo.T1)SELECT grp, ord, val, ROW_NUMBER( ) OVER(PARTITION BY grp, val, island ORDER BY ord) AS seqnoINTO #ResultFROM C/* OPTION(MAXDOP 1) */; -- descomente para serial testGO -- Test Known Solution 2 DROP TABLE IF EXISTS #Result; WITH C1 AS( SELECT *, CASE WHEN val =LAG(val) OVER(PARTITION BY grp ORDER BY ord) THEN 0 ELSE 1 END AS isstart FROM dbo.T1),C2 AS( SELECT *, SUM(isstart) OVER(PARTITION BY grp ORDER BY ord ROWS UNBOUNDED PRECEDING) AS island FROM C1)SELECT grp, ord, val, ROW_NUMBER() OVER(PARTITION BY grp, island ORDER BY ord) AS seqnoINTO #ResultFROM C2/* OPTION(MAXDOP 1) */; GO -- Testar Nova Solução DROP TABLE IF EXISTS #Result; WITH C1 AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY ord) AS rn, LAG(val) OVER(PARTITION BY grp ORDER BY ord) AS anterior FROM dbo.T1),C2 AS( SELECT *, MAX (CASE WHEN val =prev THEN NULL ELSE rn END) OVER(PARTITION BY grp ORDER BY ord ROWS UNBOUNDED PRECEDING) AS firstrn FROM C1)SELECT grp, ord, val, rn - firstrn + 1 AS seqnoINTO #ResultFROM C2/* OPTION( MAXDOP 1) */;

A Figura 7 apresenta os tempos de execução dos planos paralelo e serial para todas as soluções em relação a diferentes tamanhos de entrada.

Figura 7:comparação de desempenho

A nova solução, usando o modo serial, é a vencedora. Possui ótimo desempenho, dimensionamento linear e tempo de resposta imediato.

Conclusão


As tarefas das ilhas são bastante comuns na vida real. Muitos deles envolvem tanto a identificação de ilhas quanto o agrupamento dos dados por ilha. O desafio das ilhas de Erland, que foi o foco deste artigo, é um pouco mais exclusivo porque não envolve agrupamento, mas sequencia as linhas de cada ilha com números de linha.

Apresentei duas soluções baseadas em técnicas conhecidas de identificação de ilhas. O problema com ambos é que eles resultam em planos que envolvem classificação explícita, o que afeta negativamente o desempenho, a escalabilidade e o tempo de resposta das soluções. Também apresentei uma nova técnica que resultou em um plano sem nenhuma classificação. O plano serial para esta solução, que usa uma estratégia de modo sem classificação + modo lote serial, tem excelente desempenho, dimensionamento linear e tempo de resposta imediato. É lamentável, pelo menos por enquanto, não podermos ter uma estratégia sem classificação + modo de lote paralelo para lidar com funções de janela.