No meu post anterior, falei sobre maneiras de gerar uma sequência de números contíguos de 1 a 1.000. Agora eu gostaria de falar sobre os próximos níveis de escala:conjuntos geradores de 50.000 e 1.000.000 de números.
Gerando um conjunto de 50.000 números
Ao iniciar esta série, eu estava genuinamente curioso sobre como as diferentes abordagens seriam dimensionadas para conjuntos maiores de números. Na extremidade inferior, fiquei um pouco desanimado ao descobrir que minha abordagem favorita - usando
sys.all_objects
– não foi o método mais eficiente. Mas como essas diferentes técnicas escalariam para 50.000 linhas? Tabela de números
Como já criamos uma tabela Numbers com 1.000.000 de linhas, essa consulta permanece praticamente idêntica:
SELECT TOP (50000) n FROM dbo.Numbers ORDER BY n;
Plano:
spt_values
Como existem apenas cerca de 2.500 linhas em
spt_values
, precisamos ser um pouco mais criativos se quisermos usá-lo como fonte do nosso gerador de conjuntos. Uma maneira de simular uma tabela maior é CROSS JOIN
isso contra si mesmo. Se fizéssemos isso bruto, acabaríamos com ~ 2.500 linhas ao quadrado (mais de 6 milhões). Precisando de apenas 50.000 linhas, precisamos de cerca de 224 linhas ao quadrado. Então podemos fazer isso:;WITH x AS ( SELECT TOP (224) número FROM [master]..spt_values)SELECT TOP (50000) n =ROW_NUMBER() OVER (ORDER BY x.number) FROM x CROSS JOIN x AS yORDER BY n;
Observe que isso é equivalente, mas mais conciso do que esta variação:
SELECT TOP (50000) n =ROW_NUMBER() OVER (ORDER BY x.number) FROM (SELECT TOP (224) number FROM [master]..spt_values) AS xCROSS JOIN(SELECT TOP (224) number FROM [master] ]..spt_values) AS yORDER BY n;
Em ambos os casos, o plano fica assim:
sys.all_objects
Como
spt_values
, sys.all_objects
não satisfaz totalmente nosso requisito de 50.000 linhas por conta própria, portanto, precisaremos executar um CROSS JOIN
semelhante . ;;WITH x AS ( SELECT TOP (224) [object_id] FROM sys.all_objects)SELECT TOP (50000) n =ROW_NUMBER() OVER (ORDER BY x.[object_id]) FROM x CROSS JOIN x AS y ORDER POR n;
Plano:
CTEs empilhados
Precisamos apenas fazer um pequeno ajuste em nossos CTEs empilhados para obter exatamente 50.000 linhas:
;WITH e1(n) AS( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) , -- 10e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10e3(n) AS (SELECT 1 FROM e2 CROSS JOIN e2 AS b), -- 100*100e4(n) AS (SELECT 1 FROM e3 CROSS JOIN (SELECT TOP 5 n FROM e1) AS b) -- 5*10000 SELECT n =ROW_NUMBER() OVER (ORDER BY n) FROM e4 ORDER BY n;
Plano:
CTEs recursivos
Uma alteração ainda menos substancial é necessária para obter 50.000 linhas de nossa CTE recursiva:altere o
WHERE
cláusula para 50.000 e altere o MAXRECURSION
opção para zero. ;COM n(n) AS( SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n <50000)SELECT n FROM n ORDER nOPTION (MAXRECURSION 0);
Plano:
Nesse caso, há um ícone de aviso na classificação - como se vê, no meu sistema, a classificação precisava ser derramada no tempdb. Você pode não ver um vazamento em seu sistema, mas isso deve ser um aviso sobre os recursos necessários para essa técnica.
Desempenho
Assim como no último conjunto de testes, compararemos cada técnica, incluindo a tabela Numbers com cache frio e quente e compactada e descompactada:
Tempo de execução, em milissegundos, para gerar 50.000 números contíguos
Para obter um visual melhor, vamos remover o CTE recursivo, que foi um cão total neste teste e que distorce os resultados:
Tempo de execução, em milissegundos, para gerar 50.000 números contíguos (excluindo recursivos CTE)
Em 1.000 linhas, a diferença entre compactado e descompactado foi marginal, pois a consulta precisava apenas ler 8 e 9 páginas, respectivamente. Com 50.000 linhas, a lacuna aumenta um pouco:74 páginas versus 113. No entanto, o custo geral de descompactar os dados parece superar a economia de E/S. Assim, com 50.000 linhas, uma tabela de números não compactada parece ser o método mais eficiente de derivar um conjunto contíguo – embora, reconhecidamente, a vantagem seja marginal.
Gerando um conjunto de 1.000.000 números
Embora eu não possa imaginar muitos casos de uso em que você precisaria de um conjunto contíguo de números tão grande, eu queria incluí-lo para ser completo e porque fiz algumas observações interessantes nessa escala.
Tabela de números
Sem surpresas aqui, nossa consulta agora é:
SELECT TOP 1000000 n FROM dbo.Numbers ORDER BY n;
O
TOP
não é estritamente necessário, mas isso é apenas porque sabemos que nossa tabela Numbers e nossa saída desejada têm o mesmo número de linhas. O plano ainda é bastante semelhante aos testes anteriores:spt_values
Para obter um
CROSS JOIN
que rende 1.000.000 de linhas, precisamos tomar 1.000 linhas ao quadrado:;WITH x AS ( SELECT TOP (1000) número FROM [master]..spt_values)SELECT n =ROW_NUMBER() OVER (ORDER BY x.number) FROM x CROSS JOIN x AS y ORDER BY n;
Plano:
sys.all_objects
Novamente, precisamos do produto vetorial de 1.000 linhas:
;WITH x AS ( SELECT TOP (1000) [object_id] FROM sys.all_objects)SELECT n =ROW_NUMBER() OVER (ORDER BY x.[object_id]) FROM x CROSS JOIN x AS y ORDER BY n;
Plano:
CTEs empilhados
Para o CTE empilhado, precisamos apenas de uma combinação ligeiramente diferente deCROSS JOIN
s para chegar a 1.000.000 de linhas:
;WITH e1(n) AS( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) , -- 10e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2 AS b), -- 10*100e4(n) AS (SELECT 1 FROM e3 CROSS JOIN e3 AS b) -- 1000*1000 SELECT n =ROW_NUMBER() OVER (ORDER BY n) FROM e4 ORDER BY n;
Plano:
Nesse tamanho de linha, você pode ver que a solução CTE empilhada fica paralela. Então eu também executei uma versão comMAXDOP 1
para obter uma forma de plano semelhante à anterior e ver se o paralelismo realmente ajuda:
CTE recursiva
O CTE recursivo novamente tem apenas uma pequena alteração; apenas oWHERE
cláusula precisa ser alterada:
;COM n(n) AS( SELECT 1 UNION ALL SELECT n+1 FROM n WHERE n <1000000)SELECT n FROM n ORDER nOPTION (MAXRECURSION 0);
Plano:
Desempenho
Mais uma vez vemos que o desempenho do CTE recursivo é abismal:
Tempo de execução, em milissegundos, para gerar 1.000.000 números contíguos
Removendo esse valor discrepante do gráfico, obtemos uma imagem melhor sobre o desempenho:
Tempo de execução, em milissegundos, para gerar 1.000.000 números contíguos (excluindo recursivos CTE)
Enquanto novamente vemos a tabela de números não compactada (pelo menos com um cache quente) como a vencedora, a diferença mesmo nessa escala não é tão notável.
Continua…
Agora que exploramos minuciosamente um punhado de abordagens para gerar uma sequência de números, passaremos para as datas. Na postagem final desta série, veremos a construção de um intervalo de datas como um conjunto, incluindo o uso de uma tabela de calendário e alguns casos de uso em que isso pode ser útil.
[ Parte 1 | Parte 2 | Parte 3]
Apêndice:Contagens de linhas
Você pode não estar tentando gerar um número exato de linhas; você pode querer apenas uma maneira direta de gerar muitas linhas. A seguir está uma lista de combinações de visualizações de catálogo que lhe darão várias contagens de linhas se você simplesmenteSELECT
sem umWHERE
cláusula. Observe que esses números dependerão de você estar em um RTM ou em um service pack (já que alguns objetos do sistema são adicionados ou modificados) e também se você tem um banco de dados vazio.
Fonte | Contagens de linhas | ||
---|---|---|---|
SQL Server 2008 R2 | SQL Server 2012 | SQL Server 2014 | |
master..spt_values | 2.508 | 2.515 | 2.519 |
master..spt_values CROSS JOIN master..spt_values | 6.290.064 | 6.325.225 | 6.345.361 |
sys.all_objects | 1.990 | 2.089 | 2.165 |
sys.all_columns | 5.157 | 7.276 | 8.560 |
sys.all_objects CROSS JOIN sys.all_objects | 3.960.100 | 4.363.921 | 4.687.225 |
sys.all_objects CROSS JOIN sys.all_columns | 10.262.430 | 15.199.564 | 18.532.400 |
sys.all_columns CROSS JOIN sys.all_columns | 26.594.649 | 52.940.176 | 73.273.600 |
Tabela 1:contagens de linhas para várias consultas de exibição de catálogo