SQL, Auxiliar de tabela de números
-
08-06-2019 - |
Pergunta
Para determinados tipos de consultas sql, um auxiliar de tabela de números pode ser muito útil.Pode ser criada como uma tabela, com o máximo de linhas que você precisa para uma determinada tarefa ou como uma função definida pelo usuário que retorna o número de linhas necessárias em cada consulta.
Qual é a melhor maneira de criar uma função?
Solução
Heh...desculpe, eu estou tão atrasado responder a um post antigo.E, sim, eu tive que responder porque o mais popular de resposta (na época, o CTE Recursiva de resposta com o link para 14 diferentes métodos) sobre este segmento é, ummm...desempenho desafiado na melhor das hipóteses.
Primeiro, o artigo com o 14 diferentes soluções é bom para ver os diferentes métodos de criação de um Números/Registro de tabela na mosca, mas como apontado no artigo e no referido segmento, há uma muito importante citar...
"sugestões sobre a eficiência e a de desempenho são muitas vezes subjetivos.Independentemente de como uma consulta está sendo usado, a implementação física determina a eficiência de uma consulta.Portanto, em vez de depender de tendenciosa diretrizes, é imperativo que você teste a consulta e determinar que um desempenho melhor."
Ironicamente, o próprio artigo contém muitos subjetiva declarações e "tendenciosa diretrizes", tais como "uma CTE recursiva pode gerar um número da lista muito eficiente" e "Este é um método eficiente de usar o WHILE loop de um grupo de notícias da publicação Itzik Ben-Gen" (que eu tenho certeza que ele postou apenas para fins de comparação).Vamos lá gente...Basta mencionar Itzik bom nome pode levar algum pobre pateta em usando, na verdade, que horrível método.O autor deve praticar aquilo que prega e deve fazer um pouco de testes de desempenho antes de fazer tais ridiculamente instruções incorretas, especialmente em face de qualquer scalablility.
Com o pensamento de realmente fazer alguns testes antes de fazer qualquer subjetivas de declarações sobre o que qualquer código ou o que alguém "curte", aqui algum código que você pode fazer seus próprios testes com.O programa de configuração do profiler para o SPID você estiver executando o teste e confira por si mesmo...basta fazer uma "Pesquisa n''Replace" do número 1000000 para o seu "favorito" e consulte...
--===== Test for 1000000 rows ==================================
GO
--===== Traditional RECURSIVE CTE method
WITH Tally (N) AS
(
SELECT 1 UNION ALL
SELECT 1 + N FROM Tally WHERE N < 1000000
)
SELECT N
INTO #Tally1
FROM Tally
OPTION (MAXRECURSION 0);
GO
--===== Traditional WHILE LOOP method
CREATE TABLE #Tally2 (N INT);
SET NOCOUNT ON;
DECLARE @Index INT;
SET @Index = 1;
WHILE @Index <= 1000000
BEGIN
INSERT #Tally2 (N)
VALUES (@Index);
SET @Index = @Index + 1;
END;
GO
--===== Traditional CROSS JOIN table method
SELECT TOP (1000000)
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS N
INTO #Tally3
FROM Master.sys.All_Columns ac1
CROSS JOIN Master.sys.ALL_Columns ac2;
GO
--===== Itzik's CROSS JOINED CTE method
WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b),
E32(N) AS (SELECT 1 FROM E16 a, E16 b),
cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
SELECT N
INTO #Tally4
FROM cteTally
WHERE N <= 1000000;
GO
--===== Housekeeping
DROP TABLE #Tally1, #Tally2, #Tally3, #Tally4;
GO
Enquanto estamos no assunto, aqui os números que eu recebo de SQL Profiler para os valores de 100, 1000, 10000, 100000, e 1000000...
SPID TextData Dur(ms) CPU Reads Writes
---- ---------------------------------------- ------- ----- ------- ------
51 --===== Test for 100 rows ============== 8 0 0 0
51 --===== Traditional RECURSIVE CTE method 16 0 868 0
51 --===== Traditional WHILE LOOP method CR 73 16 175 2
51 --===== Traditional CROSS JOIN table met 11 0 80 0
51 --===== Itzik's CROSS JOINED CTE method 6 0 63 0
51 --===== Housekeeping DROP TABLE #Tally 35 31 401 0
51 --===== Test for 1000 rows ============= 0 0 0 0
51 --===== Traditional RECURSIVE CTE method 47 47 8074 0
51 --===== Traditional WHILE LOOP method CR 80 78 1085 0
51 --===== Traditional CROSS JOIN table met 5 0 98 0
51 --===== Itzik's CROSS JOINED CTE method 2 0 83 0
51 --===== Housekeeping DROP TABLE #Tally 6 15 426 0
51 --===== Test for 10000 rows ============ 0 0 0 0
51 --===== Traditional RECURSIVE CTE method 434 344 80230 10
51 --===== Traditional WHILE LOOP method CR 671 563 10240 9
51 --===== Traditional CROSS JOIN table met 25 31 302 15
51 --===== Itzik's CROSS JOINED CTE method 24 0 192 15
51 --===== Housekeeping DROP TABLE #Tally 7 15 531 0
51 --===== Test for 100000 rows =========== 0 0 0 0
51 --===== Traditional RECURSIVE CTE method 4143 3813 800260 154
51 --===== Traditional WHILE LOOP method CR 5820 5547 101380 161
51 --===== Traditional CROSS JOIN table met 160 140 479 211
51 --===== Itzik's CROSS JOINED CTE method 153 141 276 204
51 --===== Housekeeping DROP TABLE #Tally 10 15 761 0
51 --===== Test for 1000000 rows ========== 0 0 0 0
51 --===== Traditional RECURSIVE CTE method 41349 37437 8001048 1601
51 --===== Traditional WHILE LOOP method CR 59138 56141 1012785 1682
51 --===== Traditional CROSS JOIN table met 1224 1219 2429 2101
51 --===== Itzik's CROSS JOINED CTE method 1448 1328 1217 2095
51 --===== Housekeeping DROP TABLE #Tally 8 0 415 0
Como você pode ver, o CTE Recursiva do método é o segundo pior apenas para o Loop While para a Duração e a CPU e tem 8 vezes a pressão de memória na forma de leituras lógicas que o Loop While.É RBAR e esteróides deve ser evitado, a todo custo, para qualquer linha de cálculos apenas como um Loop While deve ser evitado. Há lugares em que a recursividade é bastante valioso, mas este NÃO é um deles.
Como uma barra lateral, o Sr.Denny é absolutamente ponto em...um correctamente permanente de tamanho de Números ou Registro da tabela é o caminho a percorrer para a maioria das coisas.O que corretamente dimensionadas significa?Bem, a maioria das pessoas usa um Registro de tabela para gerar datas ou fazer divide em VARCHAR(8000).Se você criar uma linha de 11.000 Tally tabela com o correto índice de cluster em "N", você vai ter o suficiente de linhas para criar mais de 30 anos no valor de datas (eu trabalho com hipotecas um pouco mais justo então, 30 anos é um número chave para mim) e, certamente, o suficiente para lidar com um VARCHAR(8000) a divisão.Porque é que o "direito de dimensionamento" tão importante?Se a Contagem de tabela é muito utilizado, ele se encaixa facilmente em cache, o que torna muito rápida, sem muita pressão memória.
Por último, mas não menos importante, cada um sabe que, se você criar um permanente Registro de mesa, não importa qual método você utilize para criar porque 1) ele só vai ser feita uma vez e 2) se é algo como 11.000 linha da tabela, todos os métodos estão indo para executar "bom o suficiente". Então, por que todas as indigination da minha parte sobre qual método usar???
A resposta é que alguns pobres cara/garota que não sabe de melhor e apenas precisa para começar seu trabalho feito pode ver algo como o CTE Recursiva do método e decidir usá-lo para algo muito maior e muito mais frequência do que a construção de um permanente Registro da tabela e eu estou tentando proteger as pessoas, os servidores que o seu código é executado, e a empresa que detém os dados sobre esses servidores.Sim...é um grande problema.Ele deve ser para todos os outros, também.Ensinar a maneira certa de fazer as coisas em vez de "bom o suficiente".Fazer alguns testes antes de publicar ou utilizar algo a partir de um post ou simplesmente...a vida que salvar pode, na verdade, ser o seu próprio especialmente se você acha que uma CTE recursiva é o caminho para ir para algo como isto.;-)
Obrigado por me ouvir...
Outras dicas
O mais ideal seria a utilização de uma tabela em vez de uma função.Usando uma função de causas extra-carga da CPU para criar os valores para os dados que estão sendo devolvidos, sobretudo se os valores a serem devolvidos cobrir uma gama muito grande.
Este artigo dá-14 diferentes possíveis soluções, com a discussão de cada um.O ponto importante é que:
sugestões sobre a eficiência e a de desempenho são muitas vezes subjetivos.Independentemente de como uma consulta está sendo usado, a implementação física determina a eficiência de uma consulta.Portanto, em vez de depender de tendenciosa diretrizes, é imperativo que você teste a consulta e determinar qual tem o melhor desempenho.
Eu pessoalmente gostei:
WITH Nbrs ( n ) AS (
SELECT 1 UNION ALL
SELECT 1 + n FROM Nbrs WHERE n < 500 )
SELECT n FROM Nbrs
OPTION ( MAXRECURSION 500 )
Este ponto de vista é super rápido e contém todos positivos int
valores.
CREATE VIEW dbo.Numbers
WITH SCHEMABINDING
AS
WITH Int1(z) AS (SELECT 0 UNION ALL SELECT 0)
, Int2(z) AS (SELECT 0 FROM Int1 a CROSS JOIN Int1 b)
, Int4(z) AS (SELECT 0 FROM Int2 a CROSS JOIN Int2 b)
, Int8(z) AS (SELECT 0 FROM Int4 a CROSS JOIN Int4 b)
, Int16(z) AS (SELECT 0 FROM Int8 a CROSS JOIN Int8 b)
, Int32(z) AS (SELECT TOP 2147483647 0 FROM Int16 a CROSS JOIN Int16 b)
SELECT ROW_NUMBER() OVER (ORDER BY z) AS n
FROM Int32
GO
Usando SQL Server 2016+
para gerar a tabela de números que você pode usar OPENJSON
:
-- range from 0 to @max - 1
DECLARE @max INT = 40000;
SELECT rn = CAST([key] AS INT)
FROM OPENJSON(CONCAT('[1', REPLICATE(CAST(',1' AS VARCHAR(MAX)),@max-1),']'));
Ideia tirada do Como podemos usar OPENJSON para gerar séries de números?
editar:ver Conrad comentário abaixo.
Jeff Moden, a resposta é grande ...mas acho que no Postgres que o Itzik método de falha, a menos que você remova o E32 linha.
Ligeiramente mais rápido em postgres (40ms vs 100ms) é outro método que eu encontrei no aqui adaptado para o postgres:
WITH
E00 (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 ),
E01 (N) AS (SELECT a.N FROM E00 a CROSS JOIN E00 b),
E02 (N) AS (SELECT a.N FROM E01 a CROSS JOIN E01 b ),
E03 (N) AS (SELECT a.N FROM E02 a CROSS JOIN E02 b
LIMIT 11000 -- end record 11,000 good for 30 yrs dates
), -- max is 100,000,000, starts slowing e.g. 1 million 1.5 secs, 2 mil 2.5 secs, 3 mill 4 secs
Tally (N) as (SELECT row_number() OVER (ORDER BY a.N) FROM E03 a)
SELECT N
FROM Tally
Como eu estou movendo-se a partir do SQL Server para o Postgres mundo, pode ter perdido uma maneira melhor para fazer tally tabelas na plataforma de ...INTEIRO()?SEQUÊNCIA (o)?
Ainda muito mais tarde, eu gostaria de contribuir um pouco diferente 'tradicional' CTE (não toque da base de dados de tabelas para obter o volume de linhas):
--===== Hans CROSS JOINED CTE method
WITH Numbers_CTE (Digit)
AS
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9)
SELECT HundredThousand.Digit * 100000 + TenThousand.Digit * 10000 + Thousand.Digit * 1000 + Hundred.Digit * 100 + Ten.Digit * 10 + One.Digit AS Number
INTO #Tally5
FROM Numbers_CTE AS One CROSS JOIN Numbers_CTE AS Ten CROSS JOIN Numbers_CTE AS Hundred CROSS JOIN Numbers_CTE AS Thousand CROSS JOIN Numbers_CTE AS TenThousand CROSS JOIN Numbers_CTE AS HundredThousand
Este CTE executa mais Lê, em seguida, Itzik CTE, mas menos do que o Tradicional CTE.No entanto, de forma consistente efectua menos ESCREVE, em seguida, outras consultas. Como você sabe, Escreve são consistentemente muito mais cara que Lê.
A duração depende fortemente do número de núcleos (MAXDOP), mas, no meu 8core, executa consistentemente mais rápido (menor duração em ms), em seguida, outras consultas.
Eu estou usando:
Microsoft SQL Server 2012 - 11.0.5058.0 (X64)
May 14 2014 18:34:29
Copyright (c) Microsoft Corporation
Enterprise Edition (64-bit) on Windows NT 6.3 <X64> (Build 9600: )
no Windows Server 2012 R2, 32 GB, Xeon X3450 @2.67 Ghz, 4 núcleos HT habilitado.