Come generare un intervallo di numeri tra due numeri?


141

Ho due numeri come input da parte dell'utente, come ad esempio 1000e 1050.

Come faccio a generare i numeri tra questi due numeri, usando una query sql, in righe separate? Voglio questo:

 1000
 1001
 1002
 1003
 .
 .
 1050

Risposte:


159

Seleziona valori non persistenti con la VALUESparola chiave. Quindi usa JOINs per generare molte e molte combinazioni (può essere esteso per creare centinaia di migliaia di righe e oltre).

SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
     (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
     (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
     (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
WHERE ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n BETWEEN @userinput1 AND @userinput2
ORDER BY 1

Demo

Un'alternativa più breve, che non è così facile da capire:

WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM x ones,     x tens,      x hundreds,       x thousands
ORDER BY 1

Demo


13
Questa è una soluzione
straordinariamente

9
Puoi spiegare la sintassi? Cos'è la v (n)?
Rafi,

2
@Rafi the v (n) e centinaia (n) ecc. Sono nomi / alias di tabelle e colonne
Twon-ha

106

una soluzione alternativa è CTE ricorsiva:

DECLARE @startnum INT=1000
DECLARE @endnum INT=1050
;
WITH gen AS (
    SELECT @startnum AS num
    UNION ALL
    SELECT num+1 FROM gen WHERE num+1<=@endnum
)
SELECT * FROM gen
option (maxrecursion 10000)

4
Non tentare di utilizzare l'opzione maxrecusion in una definizione di vista. Invece devi SELEZIONARE * DA OPZIONE CTE_VIEW (MAXRECURSION 10000) - problematico, se l'applicazione client vuole consumare la vista così com'è.
TvdH,

4
Esiste una massima ricorrenza massima impostata su 32767 (in SQL Server 2012).
BProv,

4
Solo per chiarire, se hai bisogno di una ricorsione superiore a 32767, allora può essere impostato su 0 che significa nomax,
Jayvee

2
Ecco la demo per questa risposta.
ventre

7
Ho confrontato questa risposta con le altre e il piano di esecuzione mostra che questa risposta ( ha il minor costo di query e ) è la più veloce.
ventre

39
SELECT DISTINCT n = number 
FROM master..[spt_values] 
WHERE number BETWEEN @start AND @end

Demo

Nota che questa tabella ha un massimo di 2048 perché i numeri hanno degli spazi vuoti.

Ecco un approccio leggermente migliore usando una vista di sistema (dal momento che da SQL-Server 2005):

;WITH Nums AS
(
  SELECT n = ROW_NUMBER() OVER (ORDER BY [object_id]) 
  FROM sys.all_objects 

)
SELECT n FROM Nums 
WHERE n BETWEEN @start AND @end
ORDER BY n;

Demo

oppure usa una tabella numerica personalizzata. Ringraziando Aaron Bertrand, suggerisco di leggere l'intero articolo: Genera un set o una sequenza senza loop


2
@ user3211705: nota la mia modifica, questa tabella ha un massimo di 2048. Suggerisco di leggere l'intero articolo.
Tim Schmelter,

3
Io penso che si potrebbe aggiungere WHERE type = 'P'ed evitareSELECT DISTINCT
Salman A

1
Il tuo primo link "Demo" continua a dirmeloString index out of range: 33
slartidan,

1
Hai ragione. Ma sembra essere un problema con SqlFiddle. Funziona nel tuo DB?
Tim Schmelter,

4
Nota rapida, le query tra database come questa non funzionano con SQL Azure
Kieren Johnstone,

33

Di recente ho scritto questa preziosa funzione di tabella in linea per risolvere questo problema. Non è limitato nell'intervallo diverso da memoria e archiviazione. Non accede a tabelle, quindi non è necessario leggere o scrivere sul disco in generale. Aggiunge valori di join in modo esponenziale su ogni iterazione, quindi è molto veloce anche per intervalli molto ampi. Crea dieci milioni di record in cinque secondi sul mio server. Funziona anche con valori negativi.

CREATE FUNCTION [dbo].[fn_ConsecutiveNumbers]
(   
    @start int,
    @end  int
) RETURNS TABLE 
RETURN 

select
    x268435456.X
    | x16777216.X
    | x1048576.X
    | x65536.X
    | x4096.X
    | x256.X
    | x16.X
    | x1.X
    + @start
     X
from
(VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)) as x1(X)
join
(VALUES (0),(16),(32),(48),(64),(80),(96),(112),(128),(144),(160),(176),(192),(208),(224),(240)) as x16(X)
on x1.X <= @end-@start and x16.X <= @end-@start
join
(VALUES (0),(256),(512),(768),(1024),(1280),(1536),(1792),(2048),(2304),(2560),(2816),(3072),(3328),(3584),(3840)) as x256(X)
on x256.X <= @end-@start
join
(VALUES (0),(4096),(8192),(12288),(16384),(20480),(24576),(28672),(32768),(36864),(40960),(45056),(49152),(53248),(57344),(61440)) as x4096(X)
on x4096.X <= @end-@start
join
(VALUES (0),(65536),(131072),(196608),(262144),(327680),(393216),(458752),(524288),(589824),(655360),(720896),(786432),(851968),(917504),(983040)) as x65536(X)
on x65536.X <= @end-@start
join
(VALUES (0),(1048576),(2097152),(3145728),(4194304),(5242880),(6291456),(7340032),(8388608),(9437184),(10485760),(11534336),(12582912),(13631488),(14680064),(15728640)) as x1048576(X)
on x1048576.X <= @end-@start
join
(VALUES (0),(16777216),(33554432),(50331648),(67108864),(83886080),(100663296),(117440512),(134217728),(150994944),(167772160),(184549376),(201326592),(218103808),(234881024),(251658240)) as x16777216(X)
on x16777216.X <= @end-@start
join
(VALUES (0),(268435456),(536870912),(805306368),(1073741824),(1342177280),(1610612736),(1879048192)) as x268435456(X)
on x268435456.X <= @end-@start
WHERE @end >=
    x268435456.X
    | isnull(x16777216.X, 0)
    | isnull(x1048576.X, 0)
    | isnull(x65536.X, 0)
    | isnull(x4096.X, 0)
    | isnull(x256.X, 0)
    | isnull(x16.X, 0)
    | isnull(x1.X, 0)
    + @start

GO

SELECT X FROM fn_ConsecutiveNumbers(5, 500);

È utile anche per intervalli di data e ora:

SELECT DATEADD(day,X, 0) DayX 
FROM fn_ConsecutiveNumbers(datediff(day,0,'5/8/2015'), datediff(day,0,'5/31/2015'))

SELECT DATEADD(hour,X, 0) HourX 
FROM fn_ConsecutiveNumbers(datediff(hour,0,'5/8/2015'), datediff(hour,0,'5/8/2015 12:00 PM'));

È possibile utilizzare un join con applicazione incrociata su di esso per dividere i record in base ai valori nella tabella. Quindi, ad esempio, per creare un record per ogni minuto in un intervallo di tempo in una tabella potresti fare qualcosa del tipo:

select TimeRanges.StartTime,
    TimeRanges.EndTime,
    DATEADD(minute,X, 0) MinuteX
FROM TimeRanges
cross apply fn_ConsecutiveNumbers(datediff(hour,0,TimeRanges.StartTime), 
        datediff(hour,0,TimeRanges.EndTime)) ConsecutiveNumbers

1
Caspita, quella query iniziale è VELOCE. Molto più veloce della soluzione CLR pubblicata sopra. Grazie!
Derreck Dean,

1
Bello - Ho ancora un client su SQL Server 2008 e questa era proprio la cosa di cui avevo bisogno! Molto intelligente!
STLDev,

1
funziona per 1-100 ma poi fallisce. Anche il tuo esempio di generazione di 5-500 non funziona per me, mostra 5, 21, ... 484, 500
Rez.Net

3
Se lo desideri ordinato dovrai aggiungere un ordine per clausola:SELECT X FROM fn_ConsecutiveNumbers(5, 500) ORDER BY X;
Brian Pressler

29

L'opzione migliore che ho usato è la seguente:

DECLARE @min bigint, @max bigint
SELECT @Min=919859000000 ,@Max=919859999999

SELECT TOP (@Max-@Min+1) @Min-1+row_number() over(order by t1.number) as N
FROM master..spt_values t1 
    CROSS JOIN master..spt_values t2

Ho generato milioni di record usando questo e funziona perfettamente.


2
Questa è la soluzione più elegante qui, ma penso che sia difficile per molte persone capirlo (lo avevo fatto con master.sys.all_columns). @STLDeveloper, sì, funziona con il 2008 e versioni successive.
Cetin Basoz,

13

Funziona per me!

select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount
from sys.all_objects a

2
Nice one-liner - ma attenzione che il numero massimo di righe dipenderà da sys.all_objects- per piccoli intervalli <2000 articoli, questo non è un problema. Non sei sicuro se avrà problemi con le autorizzazioni? perfetto per generare rapidamente una serie di dati di test.
freedomn-m

@ freedomn-m Un modo per aumentare il numero massimo di file sarebbe quello di eseguire un auto-cross join. select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount from sys.all_objects a, sys.all_objects b. Dove prima potevo generare solo 2384 righe, ora posso generare 5683456 righe.
Klicker,

9

Il modo migliore consiste nell'utilizzare i ctes ricorsivi.

declare @initial as int = 1000;
declare @final as int =1050;

with cte_n as (
    select @initial as contador
    union all
    select contador+1 from cte_n 
    where contador <@final
) select * from cte_n option (maxrecursion 0)

saludos.


1
Questo è stato molto utile. Ho modificato il codice in modo da poter inserire 100.000 righe. Con la mia soluzione ci sono voluti 13 minuti; usando il tuo, ci sono voluti cinque secondi. Muchísimas gracias.
Cthulhu,

2
In realtà, i CTE ricorsivi sono uno dei modi peggiori per contare. Possono anche essere battuti da un ciclo While in una transazione e il ciclo While produrrà molte meno letture. Il metodo cCTE (Cascading CTEs, originariamente di Itizik Ben-Gan) è molto più veloce e produce zero letture.
Jeff Moden,

9
declare @start int = 1000
declare @end    int =1050

;with numcte  
AS  
(  
  SELECT @start [SEQUENCE]  
  UNION all  
  SELECT [SEQUENCE] + 1 FROM numcte WHERE [SEQUENCE] < @end 
)      
SELECT * FROM numcte

1
È diverso dalla risposta di @Jayvee?
Noel,

1
Sì, in cui la condizione è menzionata come num + 1 <1050 che stamperà solo fino a 1049.
Sowbarani Karthikeyan,

2
Una modifica (o un commento) alla risposta esistente che è essenziale lo stesso fornirebbe più valore di una risposta completamente nuova.
Noel,

7

Se non si verificano problemi durante l'installazione di un assembly CLR nel server, è consigliabile scrivere una funzione con valori di tabella in .NET. In questo modo è possibile utilizzare una sintassi semplice, semplificando il collegamento con altre query e, come bonus, non si sprecherà la memoria perché il risultato è trasmesso in streaming.

Creare un progetto contenente la seguente classe:

using System;
using System.Collections;
using System.Data;
using System.Data.Sql;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

namespace YourNamespace
{
   public sealed class SequenceGenerator
    {
        [SqlFunction(FillRowMethodName = "FillRow")]
        public static IEnumerable Generate(SqlInt32 start, SqlInt32 end)
        {
            int _start = start.Value;
            int _end = end.Value;
            for (int i = _start; i <= _end; i++)
                yield return i;
        }

        public static void FillRow(Object obj, out int i)
        {
            i = (int)obj;
        }

        private SequenceGenerator() { }
    }
}

Metti l'assembly da qualche parte sul server ed esegui:

USE db;
CREATE ASSEMBLY SqlUtil FROM 'c:\path\to\assembly.dll'
WITH permission_set=Safe;

CREATE FUNCTION [Seq](@start int, @end int) 
RETURNS TABLE(i int)
AS EXTERNAL NAME [SqlUtil].[YourNamespace.SequenceGenerator].[Generate];

Ora puoi eseguire:

select * from dbo.seq(1, 1000000)

1
Ho provato questa soluzione e funziona bene, ma non molto velocemente. Se stai generando solo 1.000 numeri, o forse 10.000, è abbastanza veloce. Se sei come me e devi generare miliardi di numeri, la soluzione di Brian Pressler qui sotto è incredibilmente veloce rispetto a SQL CLR.
Derreck Dean,

2
@DerreckDean Hai ragione. Penso che la sua sia la soluzione migliore perché è facile da creare e utilizzare (e veloce come dici tu). Nel mio caso avevo già un assembly per concatenare le stringhe, quindi l'ho appena aggiunto lì.
AlexDev,

1
Avevo anche un assembly esistente e ho provato entrambi i metodi. Sto generando un numero indeterminato di numeri da aggiungere alle date (in pratica, ho ricreato lo scheduler dell'agente del server SQL per generare le date per la nostra applicazione interna e 100 livelli di ricorsione non lo taglierebbero per generare più anni di datetimes, possibilmente fino al secondo.), quindi sono stato in grado di testare a fondo più soluzioni da questo thread. Apprezzo il tuo contributo!
Derreck Dean,

7

Niente di nuovo, ma ho riscritto la soluzione Brian Pressler per essere più facile alla vista, potrebbe essere utile a qualcuno (anche se è solo per me futuro):

alter function [dbo].[fn_GenerateNumbers]
(   
    @start int,
    @end  int
) returns table
return

with 
b0 as (select n from (values (0),(0x00000001),(0x00000002),(0x00000003),(0x00000004),(0x00000005),(0x00000006),(0x00000007),(0x00000008),(0x00000009),(0x0000000A),(0x0000000B),(0x0000000C),(0x0000000D),(0x0000000E),(0x0000000F)) as b0(n)),
b1 as (select n from (values (0),(0x00000010),(0x00000020),(0x00000030),(0x00000040),(0x00000050),(0x00000060),(0x00000070),(0x00000080),(0x00000090),(0x000000A0),(0x000000B0),(0x000000C0),(0x000000D0),(0x000000E0),(0x000000F0)) as b1(n)),
b2 as (select n from (values (0),(0x00000100),(0x00000200),(0x00000300),(0x00000400),(0x00000500),(0x00000600),(0x00000700),(0x00000800),(0x00000900),(0x00000A00),(0x00000B00),(0x00000C00),(0x00000D00),(0x00000E00),(0x00000F00)) as b2(n)),
b3 as (select n from (values (0),(0x00001000),(0x00002000),(0x00003000),(0x00004000),(0x00005000),(0x00006000),(0x00007000),(0x00008000),(0x00009000),(0x0000A000),(0x0000B000),(0x0000C000),(0x0000D000),(0x0000E000),(0x0000F000)) as b3(n)),
b4 as (select n from (values (0),(0x00010000),(0x00020000),(0x00030000),(0x00040000),(0x00050000),(0x00060000),(0x00070000),(0x00080000),(0x00090000),(0x000A0000),(0x000B0000),(0x000C0000),(0x000D0000),(0x000E0000),(0x000F0000)) as b4(n)),
b5 as (select n from (values (0),(0x00100000),(0x00200000),(0x00300000),(0x00400000),(0x00500000),(0x00600000),(0x00700000),(0x00800000),(0x00900000),(0x00A00000),(0x00B00000),(0x00C00000),(0x00D00000),(0x00E00000),(0x00F00000)) as b5(n)),
b6 as (select n from (values (0),(0x01000000),(0x02000000),(0x03000000),(0x04000000),(0x05000000),(0x06000000),(0x07000000),(0x08000000),(0x09000000),(0x0A000000),(0x0B000000),(0x0C000000),(0x0D000000),(0x0E000000),(0x0F000000)) as b6(n)),
b7 as (select n from (values (0),(0x10000000),(0x20000000),(0x30000000),(0x40000000),(0x50000000),(0x60000000),(0x70000000)) as b7(n))

select s.n
from (
    select
          b7.n
        | b6.n
        | b5.n
        | b4.n
        | b3.n
        | b2.n
        | b1.n
        | b0.n
        + @start
         n
    from b0
    join b1 on b0.n <= @end-@start and b1.n <= @end-@start
    join b2 on b2.n <= @end-@start
    join b3 on b3.n <= @end-@start
    join b4 on b4.n <= @end-@start
    join b5 on b5.n <= @end-@start
    join b6 on b6.n <= @end-@start
    join b7 on b7.n <= @end-@start
) s
where @end >= s.n

GO

1
Credo che tu abbia distillato l'essenza di un bellissimo algoritmo in un codice decisamente carino.
Clay

1
I risultati sono ordinati in un ordine strano ma non caotico. Provalo su un intervallo da 5 a 500. Restituisce 5,21,37, ..., 245,6,22, ... Sai come l'ordinamento influenzerebbe le prestazioni? Le soluzioni basate su ROW_NUMBER()non presentano questo problema.
Przemyslaw Remin,

1
Non sono un esperto, ma intuitivamente immagino che il server SQL dovrà mettere tutti i risultati in memoria e ordinarli prima di restituirli in modo da utilizzare di più la memoria e ritardare la risposta anziché limitarsi a trasmettere i risultati appena arrivano.
Guillaume86,

6

2 anni dopo, ma ho scoperto di avere lo stesso problema. Ecco come l'ho risolto. (modificato per includere i parametri)

DECLARE @Start INT, @End INT
SET @Start = 1000
SET @End = 1050

SELECT  TOP (@End - @Start+1) ROW_NUMBER() OVER (ORDER BY S.[object_id])+(@Start - 1) [Numbers]
FROM    sys.all_objects S WITH (NOLOCK)

5

La risposta di slartidan può essere migliorata, per quanto riguarda le prestazioni, eliminando tutti i riferimenti al prodotto cartesiano e usando ROW_NUMBER()invece ( piano di esecuzione a confronto ):

SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n FROM 
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x1(x),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x2(x),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x3(x),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x4(x),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x5(x)
ORDER BY n

Inseriscilo in un CTE e aggiungi una clausola where per selezionare i numeri desiderati:

DECLARE @n1 AS INT = 100;
DECLARE @n2 AS INT = 40099;
WITH numbers AS (
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n FROM 
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x1(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x2(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x3(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x4(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x5(x)
)
SELECT numbers.n
FROM numbers
WHERE n BETWEEN @n1 and @n2
ORDER BY n

1
ROW_NUMBER inizia solo da 1. Come possiamo iniziare da zero con il tuo metodo?
ventre

2
@stomy SELECT ROW_NUMBER() OVER (...) - 1 AS n. In alcuni casi ciò potrebbe compromettere le prestazioni.
Salman A

4

Ecco alcune soluzioni abbastanza ottimali e compatibili:

USE master;

declare @min as int;    set @min = 1000;
declare @max as int;    set @max = 1050;    --null returns all

--  Up to 256 - 2 048 rows depending on SQL Server version
select  isnull(@min,0)+number.number  as  number
FROM    dbo.spt_values  AS  number
WHERE   number."type"                   =   'P'     --integers
    and (   @max                            is null     --return all
        or  isnull(@min,0)+number.number    <=  @max    --return up to max
    )
order by    number
;

--  Up to 65 536 - 4 194 303 rows depending on SQL Server version
select  isnull(@min,0)+value1.number+(value2.number*numberCount.numbers)  as  number
FROM  dbo.spt_values            AS  value1
  cross join  dbo.spt_values    AS  value2
  cross join (  --get the number of numbers (depends on version)
    select  sum(1)  as  numbers
    from    dbo.spt_values
    where   spt_values."type"   =   'P' --integers
  )                             as  numberCount
WHERE   value1."type" = 'P'   --integers
    and value2."type" = 'P'   --integers
    and (   @max    is null     --return all
        or  isnull(@min,0)+value1.number+(value2.number*numberCount.numbers)    
            <=  @max            --return up to max
    )
order by    number
;

1
Questo metodo è in qualche modo migliore del semplice selecting where spt_values.number between @min and @max?
underscore_d

2
Tipo = filtro 'P' è richiesto per evitare numeri duplicati. Con questo filtro la tabella restituirà i numeri da 0 a 2047. Quindi il filtro "numero compreso tra @min e @max" funzionerà finché le variabili rientrano in tale intervallo. La mia soluzione ti permetterà di ottenere fino a 2048 righe nell'intervallo intero (-2.147.483.648) - (2.147.483.647).
jumxozizi,

1
la logica di cui sopra è utile solo quando la differenza tra il numero massimo e minimo inferiore a 2048 e una volta può un massimo di 2048 record in un momento
Smart003

4

So di essere in ritardo di 4 anni, ma mi sono imbattuto in un'altra soluzione alternativa a questo problema. Il problema della velocità non è solo il pre-filtro, ma impedisce anche l'ordinamento. È possibile forzare l'esecuzione dell'ordine di join in modo tale che il prodotto cartesiano venga effettivamente conteggiato a seguito del join. Utilizzando la risposta di slartidan come punto di partenza:

    WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM x ones,     x tens,      x hundreds,       x thousands
ORDER BY 1

Se conosciamo l'intervallo desiderato, possiamo specificarlo tramite @Upper e @Lower. Combinando il suggerimento di join REMOTE con TOP, possiamo calcolare solo il sottoinsieme di valori che vogliamo senza sprecare nulla.

WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
SELECT TOP (1+@Upper-@Lower) @Lower + ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM x thousands
INNER REMOTE JOIN x hundreds on 1=1
INNER REMOTE JOIN x tens on 1=1
INNER REMOTE JOIN x ones on 1=1

Il suggerimento di join REMOTE forza l'ottimizzatore a confrontare prima il lato destro del join. Specificando ciascun join come REMOTO dal valore più significativo a quello meno significativo, il join stesso verrà conteggiato correttamente da uno verso l'alto. Non è necessario filtrare con un WHERE o ordinare con un ORDER BY.

Se si desidera aumentare l'intervallo, è possibile continuare ad aggiungere ulteriori join con ordini di grandezza progressivamente più alti, purché siano ordinati dal più al meno significativo nella clausola FROM.

Questa è una query specifica per SQL Server 2008 o versione successiva.


1
Davvero molto bello. La stessa tecnica può essere applicata all'eccellente risposta di Brian Pressler e alla deliziosa riscrittura di Guillaume86.
Clay

3

Anche questo farà

DECLARE @startNum INT = 1000;
DECLARE @endNum INT = 1050;
INSERT  INTO dbo.Numbers
        ( Num
        )
        SELECT  CASE WHEN MAX(Num) IS NULL  THEN @startNum
                     ELSE MAX(Num) + 1
                END AS Num
        FROM    dbo.Numbers
GO 51

3

La migliore velocità quando si esegue la query

DECLARE @num INT = 1000
WHILE(@num<1050)
begin
 INSERT  INTO [dbo].[Codes]
    (   Code
    ) 
    VALUES (@num)
    SET @num = @num + 1
end

3

CTE ricorsivo in dimensione esponenziale (anche per impostazione predefinita di 100 ricorsione, questo può costruire fino a 2 ^ 100 numeri):

DECLARE @startnum INT=1000
DECLARE @endnum INT=1050
DECLARE @size INT=@endnum-@startnum+1
;
WITH numrange (num) AS (
    SELECT 1 AS num
    UNION ALL
    SELECT num*2 FROM numrange WHERE num*2<=@size
    UNION ALL
    SELECT num*2+1 FROM numrange WHERE num*2+1<=@size
)
SELECT num+@startnum-1 FROM numrange order by num

Secondo l'OP, penso @startnume endnumdovrebbe essere inserito dall'utente?
JC,

2

Ho dovuto inserire il percorso del file immagine nel database usando un metodo simile. La query seguente ha funzionato bene:

DECLARE @num INT = 8270058
WHILE(@num<8270284)
begin
    INSERT  INTO [dbo].[Galleries]
    (ImagePath) 
    VALUES 
    ('~/Content/Galeria/P'+CONVERT(varchar(10), @num)+'.JPG')

    SET @num = @num + 1
end

Il codice per te sarebbe:

DECLARE @num INT = 1000
WHILE(@num<1051)
begin
    SELECT @num

    SET @num = @num + 1
end

2

Questo è quello che faccio, è piuttosto veloce e flessibile e non molto codice.

DECLARE @count  int =   65536;
DECLARE @start  int =   11;
DECLARE @xml    xml =   REPLICATE(CAST('<x/>' AS nvarchar(max)), @count);

; WITH GenerateNumbers(Num) AS
(
    SELECT  ROW_NUMBER() OVER (ORDER BY @count) + @start - 1
    FROM    @xml.nodes('/x') X(T)
)
SELECT  Num
FROM    GenerateNumbers;

Si noti che (ORDER BY @count) è un manichino. Non fa nulla ma ROW_NUMBER () richiede un ORDER BY.

Modifica : mi sono reso conto che la domanda originale era quella di ottenere un intervallo da x a y. Il mio script può essere modificato in questo modo per ottenere un intervallo:

DECLARE @start  int =   5;
DECLARE @end    int =   21;
DECLARE @xml    xml =   REPLICATE(CAST('<x/>' AS nvarchar(max)), @end - @start + 1);

; WITH GenerateNumbers(Num) AS
(
    SELECT  ROW_NUMBER() OVER (ORDER BY @end) + @start - 1
    FROM    @xml.nodes('/x') X(T)
)
SELECT  Num
FROM    GenerateNumbers;

1
Questo è stato molto veloce e flessibile. Ha funzionato bene per le mie esigenze.
AndrewBanjo1968

1
-- Generate Numeric Range
-- Source: http://www.sqlservercentral.com/scripts/Miscellaneous/30397/

CREATE TABLE #NumRange(
    n int
)

DECLARE @MinNum int
DECLARE @MaxNum int
DECLARE @I int

SET NOCOUNT ON

SET @I = 0
WHILE @I <= 9 BEGIN
    INSERT INTO #NumRange VALUES(@I)
    SET @I = @I + 1
END


SET @MinNum = 1
SET @MaxNum = 1000000

SELECT  num = a.n +
    (b.n * 10) +
    (c.n * 100) +
    (d.n * 1000) +
    (e.n * 10000)
FROM    #NumRange a
CROSS JOIN #NumRange b
CROSS JOIN #NumRange c
CROSS JOIN #NumRange d
CROSS JOIN #NumRange e
WHERE   a.n +
    (b.n * 10) +
    (c.n * 100) +
    (d.n * 1000) +
    (e.n * 10000) BETWEEN @MinNum AND @MaxNum
ORDER BY a.n +
    (b.n * 10) +
    (c.n * 100) +
    (d.n * 1000) +
    (e.n * 10000) 

DROP TABLE #NumRange

1

Funziona solo per sequenze purché alcune tabelle delle applicazioni contengano righe. Supponiamo che io voglia una sequenza da 1..100 e che abbia una tabella di applicazioni dbo.foo con colonna (di tipo numerico o stringa) foo.bar:

select 
top 100
row_number() over (order by dbo.foo.bar) as seq
from dbo.foo

Nonostante sia presente in una clausola order by, dbo.foo.bar non deve avere valori distinti o anche non nulli.

Naturalmente, SQL Server 2012 ha oggetti sequenza, quindi esiste una soluzione naturale in quel prodotto.


1

Ecco cosa mi è venuto in mente:

create or alter function dbo.fn_range(@start int, @end int)  returns table
return
with u2(n) as (
    select n 
    from (VALUES (0),(1),(2),(3)) v(n)
), 
u8(n) as (
    select
        x0.n | x1.n * 4 | x2.n * 16 | x3.n * 64 as n
    from u2 x0, u2 x1, u2 x2, u2 x3
)
select 
    @start + s.n as n
from (
    select
        x0.n | isnull(x1.n, 0) * 256 | isnull(x2.n, 0) * 65536 as n
    from u8 x0 
    left join u8 x1 on @end-@start > 256
    left join u8 x2 on @end-@start > 65536
) s
where s.n < @end - @start

Genera fino a 2 ^ 24 valori. Le condizioni di join lo mantengono veloce per piccoli valori.


1

Questo è stato completato per me in 36 secondi sul nostro server DEV. Come la risposta di Brian, concentrarsi sul filtraggio dell'intervallo è importante all'interno della query; un TRA tenta ancora di generare tutti i record iniziali prima del limite inferiore anche se non ne ha bisogno.

declare @s bigint = 10000000
    ,   @e bigint = 20000000

;WITH 
Z AS (SELECT 0 z FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)) T(n)),
Y AS (SELECT 0 z FROM Z a, Z b, Z c, Z d, Z e, Z f, Z g, Z h, Z i, Z j, Z k, Z l, Z m, Z n, Z o, Z p),
N AS (SELECT ROW_NUMBER() OVER (PARTITION BY 0 ORDER BY z) n FROM Y)

SELECT TOP (1+@e-@s) @s + n - 1 FROM N

Si noti che ROW_NUMBER è un bigint , quindi non possiamo andare oltre 2 ^^ 64 (== 16 ^^ 16) record generati con qualsiasi metodo che lo utilizza. Questa query pertanto rispetta lo stesso limite superiore per i valori generati.


1

Questo utilizza un codice procedurale e una funzione con valori di tabella. Lento, ma facile e prevedibile.

CREATE FUNCTION [dbo].[Sequence] (@start int, @end int)
RETURNS
@Result TABLE(ID int)
AS
begin
declare @i int;
set @i = @start;
while @i <= @end 
    begin
        insert into @result values (@i);
        set @i = @i+1;
    end
return;
end

Uso:

SELECT * FROM dbo.Sequence (3,7);
ID
3
4
5
6
7

È una tabella, quindi puoi usarla nei join con altri dati. Uso più frequentemente questa funzione come lato sinistro di un join rispetto a GROUP BY ora, giorno ecc. Per garantire una sequenza contigua di valori temporali.

SELECT DateAdd(hh,ID,'2018-06-20 00:00:00') as HoursInTheDay FROM dbo.Sequence (0,23) ;

HoursInTheDay
2018-06-20 00:00:00.000
2018-06-20 01:00:00.000
2018-06-20 02:00:00.000
2018-06-20 03:00:00.000
2018-06-20 04:00:00.000
(...)

Le prestazioni non sono entusiasmanti (16 secondi per un milione di righe) ma abbastanza buone per molti scopi.

SELECT count(1) FROM [dbo].[Sequence] (
   1000001
  ,2000000)
GO

1

Oracle 12c; Veloce ma limitato:

select rownum+1000 from all_objects fetch first 50 rows only;

Nota : limitato al conteggio delle righe della vista all_objects;


1

La soluzione che ho sviluppato e usato da un po 'di tempo (cavalcando alcuni sui lavori condivisi di altri) è leggermente simile a almeno uno pubblicato. Non fa riferimento a nessuna tabella e restituisce un intervallo non ordinato fino a 1048576 valori (2 ^ 20) e può includere negativi se lo si desidera. Ovviamente puoi ordinare il risultato se necessario. Funziona abbastanza rapidamente, specialmente su intervalli più piccoli.

Select value from dbo.intRange(-500, 1500) order by value  -- returns 2001 values

create function dbo.intRange 
(   
    @Starting as int,
    @Ending as int
)
returns table
as
return (
    select value
    from (
        select @Starting +
            ( bit00.v | bit01.v | bit02.v | bit03.v
            | bit04.v | bit05.v | bit06.v | bit07.v
            | bit08.v | bit09.v | bit10.v | bit11.v
            | bit12.v | bit13.v | bit14.v | bit15.v
            | bit16.v | bit17.v | bit18.v | bit19.v
            ) as value
        from       (select 0 as v union ALL select 0x00001 as v) as bit00
        cross join (select 0 as v union ALL select 0x00002 as v) as bit01
        cross join (select 0 as v union ALL select 0x00004 as v) as bit02
        cross join (select 0 as v union ALL select 0x00008 as v) as bit03
        cross join (select 0 as v union ALL select 0x00010 as v) as bit04
        cross join (select 0 as v union ALL select 0x00020 as v) as bit05
        cross join (select 0 as v union ALL select 0x00040 as v) as bit06
        cross join (select 0 as v union ALL select 0x00080 as v) as bit07
        cross join (select 0 as v union ALL select 0x00100 as v) as bit08
        cross join (select 0 as v union ALL select 0x00200 as v) as bit09
        cross join (select 0 as v union ALL select 0x00400 as v) as bit10
        cross join (select 0 as v union ALL select 0x00800 as v) as bit11
        cross join (select 0 as v union ALL select 0x01000 as v) as bit12
        cross join (select 0 as v union ALL select 0x02000 as v) as bit13
        cross join (select 0 as v union ALL select 0x04000 as v) as bit14
        cross join (select 0 as v union ALL select 0x08000 as v) as bit15
        cross join (select 0 as v union ALL select 0x10000 as v) as bit16
        cross join (select 0 as v union ALL select 0x20000 as v) as bit17
        cross join (select 0 as v union ALL select 0x40000 as v) as bit18
        cross join (select 0 as v union ALL select 0x80000 as v) as bit19
    ) intList
    where @Ending - @Starting < 0x100000
        and intList.value between @Starting and @Ending
)

1
;WITH u AS (
    SELECT Unit FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(Unit)
),
d AS (
    SELECT 
        (Thousands+Hundreds+Tens+Units) V
    FROM 
           (SELECT Thousands = Unit * 1000 FROM u) Thousands 
           ,(SELECT Hundreds = Unit * 100 FROM u) Hundreds 
           ,(SELECT Tens = Unit * 10 FROM u) Tens 
           ,(SELECT Units = Unit FROM u) Units
    WHERE
           (Thousands+Hundreds+Tens+Units) <= 10000
)

SELECT * FROM d ORDER BY v

1

Ho fatto la seguente funzione dopo aver letto questa discussione. Semplice e veloce:

go
create function numbers(@begin int, @len int)
returns table as return
with d as (
    select 1 v from (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d(v)
)
select top (@len) @begin -1 + row_number() over(order by (select null)) v
from d d0
cross join d d1
cross join d d2
cross join d d3
cross join d d4
cross join d d5
cross join d d6
cross join d d7
go

select * from numbers(987654321,500000)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.