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?

Foi útil?

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),']'));

LiveDemo


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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top