¿Cómo generar un rango de números entre dos números?


141

Tengo dos números como entrada del usuario, como por ejemplo 1000y 1050.

¿Cómo genero los números entre estos dos números, usando una consulta sql, en filas separadas? Quiero esto:

 1000
 1001
 1002
 1003
 .
 .
 1050

Respuestas:


159

Seleccione valores no persistentes con la VALUESpalabra clave. Luego use JOINs para generar muchas combinaciones (puede extenderse para crear cientos de miles de filas y más).

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

Una alternativa más corta, que no es tan fácil de entender:

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
Esta es una solución fantásticamente elegante
Aaron Hudon

9
¿Puedes explicar la sintaxis? ¿Qué es la v (n)?
Rafi

2
@Rafi the v (n) y cientos (n), etc. son nombres / alias de tablas y columnas
Twon-ha

106

Una solución alternativa es el CTE recursivo:

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)

44
No intente utilizar la opción maxrecusion en una definición de vista. En su lugar, debe SELECCIONAR * DESDE LA OPCIÓN CTE_VIEW (MAXRECURSION 10000) - problemático, si su aplicación cliente quiere consumir la vista tal como está.
TvdH

44
Hay una recursión máxima máxima establecida en 32767 (en SQL Server 2012).
BProv

44
Solo para aclarar, si necesita una recursión de más de 32767, entonces se puede establecer en 0, lo que significa nomax,
Jayvee

2
Aquí está la demostración para esta respuesta.
Stomy

77
Comparé esta respuesta con las otras y el plan de Ejecución muestra que esta respuesta ( tiene el menor costo de consulta y ) es la más rápida.
Stomy

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

Demo

Tenga en cuenta que esta tabla tiene un máximo de 2048 porque los números tienen espacios en blanco.

Aquí hay un enfoque ligeramente mejor usando una vista del sistema (desde 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

o use una tabla de números personalizada. Créditos a Aaron Bertrand, sugiero leer el artículo completo: generar un conjunto o secuencia sin bucles


2
@ user3211705: observe mi edición, esta tabla tiene un máximo de 2048. Sugiero leer el artículo completo.
Tim Schmelter

3
Yo creo que se podría añadir WHERE type = 'P'y evitarSELECT DISTINCT
Salman A

1
Su primer enlace "Demo" me sigue diciendoString index out of range: 33
slartidan

1
Tienes razón. Pero parece ser un problema con SqlFiddle. ¿Funciona en tu base de datos?
Tim Schmelter

44
Nota rápida, las consultas entre bases de datos como esta no funcionan con SQL Azure
Kieren Johnstone

33

Recientemente escribí esta función con valores de tabla en línea para resolver este mismo problema. No está limitado en su alcance además de la memoria y el almacenamiento. No tiene acceso a tablas, por lo que no hay necesidad de lecturas o escrituras de disco en general. Agrega valores de combinaciones exponencialmente en cada iteración, por lo que es muy rápido incluso para rangos muy grandes. Crea diez millones de registros en cinco segundos en mi servidor. También funciona con valores negativos.

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);

También es útil para rangos de fecha y hora:

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

Puede usar una combinación cruzada para dividir registros en función de los valores de la tabla. Entonces, por ejemplo, para crear un registro por cada minuto en un rango de tiempo en una tabla, podría hacer algo como:

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
Wow, esa consulta inicial es RÁPIDA. Mucho más rápido que la solución CLR publicada anteriormente. ¡Gracias!
Derreck Dean

1
Agradable: todavía tengo un cliente en SQL Server 2008 y ¡esto era justo lo que necesitaba! ¡Muy inteligente!
STLDev

1
funciona para 1-100 pero luego falla. Incluso su ejemplo de generar 5-500 no funciona para mí, muestra 5, 21, ... 484, 500
Rez.Net

3
Si desea ordenarlo, deberá agregar un pedido por cláusula:SELECT X FROM fn_ConsecutiveNumbers(5, 500) ORDER BY X;
Brian Pressler

29

La mejor opción que he usado es la siguiente:

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

He generado millones de registros usando esto y funciona perfecto.


2
Esta es la solución más elegante aquí, pero creo que es difícil para muchas personas entenderlo (había estado haciendo esto con master.sys.all_columns). @STLDeveloper, sí, funciona con 2008 y posteriores.
Cetin Basoz

13

¡Funciona para mí!

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

2
Una buena línea, pero tenga en cuenta que el número máximo de filas dependerá de sys.all_objects, para rangos pequeños <2000 elementos, esto no es un problema. ¿No está seguro si tendrá problemas de permisos? perfecto para generar rápidamente un lote de datos de prueba.
freedomn-m

@ freedomn-m Una forma de aumentar las filas máximas sería realizar una unión de autocruzamiento. select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount from sys.all_objects a, sys.all_objects b. Donde antes solo podía generar 2384 filas, ahora puedo generar 5683456 filas.
Klicker el

9

La mejor manera es usar ctes recursivos.

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
Esto fue muy útil. Modifiqué el código para poder insertar 100.000 filas. Con mi solución me tomó como 13 minutos; usando el tuyo, tardó cinco segundos. Muchísimas gracias.
Cthulhu

2
En realidad, los CTE recursivos son una de las peores formas de contar. Incluso pueden ser superados por un bucle While en una transacción y el bucle While producirá muchas menos lecturas. El método cCTE (CTE en cascada, originalmente por Itizik Ben-Gan) es mucho más rápido y produce cero lecturas.
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
¿Es esto diferente a la respuesta de @Jayvee?
Noel

1
Sí, en la condición donde se menciona como num + 1 <1050, que imprimirá hasta 1049 solamente.
Sowbarani Karthikeyan

2
Una edición (o comentario) de la respuesta existente que es esencial, proporcionaría más valor que una respuesta completamente nueva.
Noel

7

Si no tiene problemas para instalar un ensamblado CLR en su servidor, una buena opción es escribir una función con valores de tabla en .NET. De esa manera, puede usar una sintaxis simple, lo que facilita unirse con otras consultas y, como bonificación, no desperdiciará memoria porque el resultado se transmite.

Cree un proyecto que contenga la siguiente clase:

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() { }
    }
}

Coloque el ensamblaje en algún lugar del servidor y ejecute:

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];

Ahora puedes ejecutar:

select * from dbo.seq(1, 1000000)

1
Probé esta solución y funciona bien, pero no muy rápido. Si está generando solo 1,000 números, o quizás 10,000, es bastante rápido. Si eres como yo y tienes que generar miles de millones de números, la solución de Brian Pressler a continuación es increíblemente rápida en comparación con SQL CLR.
Derreck Dean

2
@DerreckDean Tienes razón. Creo que es la mejor solución porque es fácil de crear y usar (y rápido como dices). En mi caso, ya tenía un ensamblaje para concatenar cadenas, así que lo agregué allí.
AlexDev

1
También tenía un ensamblaje existente y probé ambos métodos. Estoy generando un número indeterminado de números para agregar a las fechas (básicamente, recreé el programador del agente del servidor SQL para generar fechas para nuestra aplicación interna, y 100 niveles de recursión no iban a reducirlo por generar varios años de fechas, posiblemente hasta el segundo.), así que pude probar a fondo múltiples soluciones de este hilo. Agradezco tu aporte!
Derreck Dean

7

Nada nuevo, pero reescribí la solución Brian Pressler para que sea más fácil de ver, podría ser útil para alguien (incluso si es solo para mí):

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
Creo que ha destilado la esencia de un algoritmo hermoso en un código francamente bonito.
Clay

1
Los resultados están ordenados en un orden extraño pero no caótico. Pruébelo en un rango de 5 a 500. Devuelve 5,21,37, ..., 245,6,22, ... ¿Sabe cómo influiría el pedido en el rendimiento? Las soluciones basadas en ROW_NUMBER()no tienen ese problema.
Przemyslaw Remin

1
No soy un experto, pero intuitivamente supongo que el servidor SQL necesitará poner todos los resultados en la memoria y ordenarlos antes de devolverlos, por lo que se usará más memoria y se retrasará la respuesta en lugar de simplemente transmitir los resultados a medida que se presentan.
Guillaume86

6

2 años después, pero descubrí que tenía el mismo problema. Así es como lo resolví. (editado para incluir parámetros)

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 respuesta de slartidan se puede mejorar, en cuanto al rendimiento, eliminando todas las referencias al producto cartesiano y utilizando en su ROW_NUMBER()lugar ( plan de ejecución comparado ):

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

Envuélvalo dentro de un CTE y agregue una cláusula where para seleccionar los números deseados:

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 solo comienza en 1. ¿Cómo podemos comenzar desde cero con su método?
stomy

2
@stomy SELECT ROW_NUMBER() OVER (...) - 1 AS n. En algunos casos, esto podría matar el rendimiento.
Salman A

4

Aquí hay un par de soluciones óptimas y compatibles:

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
¿Es este método de alguna manera mejor que simplemente selecting where spt_values.number between @min and @max?
underscore_d

2
El filtro Type = 'P' es necesario para evitar números duplicados. Con este filtro, la tabla devolverá los números del 0 al 2047. Por lo tanto, el filtro "número entre @min y @max" funcionará siempre que las variables estén dentro de ese rango. Mi solución le permitirá obtener hasta 2048 filas dentro del rango entero (-2,147,483,648) - (2,147,483,647).
jumxozizi

1
la lógica anterior es útil solo cuando la diferencia entre el número máximo y mínimo menor que 2048 y una vez puede un máximo de 2048 registros en un punto de tiempo
Smart003

4

Sé que llego 4 años tarde, pero me encontré con otra respuesta alternativa a este problema. El problema de la velocidad no es solo el prefiltrado, sino también la prevención de la clasificación. Es posible forzar la ejecución del orden de unión de una manera que el producto cartesiano realmente cuente como resultado de la unión. Usando la respuesta de slartidan como punto de partida:

    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

Si conocemos el rango que queremos, podemos especificarlo a través de @Upper y @Lower. Al combinar la sugerencia de combinación REMOTE junto con TOP, podemos calcular solo el subconjunto de valores que queremos sin desperdiciar nada.

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

La sugerencia de unión REMOTO obliga primero al optimizador a comparar en el lado derecho de la unión. Al especificar cada combinación como REMOTO desde el valor más significativo hasta el menos significativo, la combinación en sí contará correctamente hacia arriba. No es necesario filtrar con un DÓNDE ni ordenar con un ORDER BY.

Si desea aumentar el rango, puede continuar agregando combinaciones adicionales con órdenes de magnitud progresivamente más altos, siempre que estén ordenadas de mayor a menor en la cláusula FROM.

Tenga en cuenta que esta es una consulta específica para SQL Server 2008 o superior.


1
Muy bueno de verdad. La misma técnica se puede aplicar a la excelente respuesta de Brian Pressler y la encantadora reescritura de Guillaume86 también.
Clay

3

Esto también servirá

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 mejor velocidad cuando se ejecuta la consulta

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

3

CTE recursivo en tamaño exponencial (incluso para el valor predeterminado de 100 recursiones, esto puede generar hasta 2 ^ 100 números):

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

De acuerdo con el OP, creo @startnumy endnumdebería ser ingresado por el usuario?
JC

2

Tuve que insertar una ruta de archivo de imagen en la base de datos utilizando un método similar. La consulta a continuación funcionó bien:

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

El código para ti sería:

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

    SET @num = @num + 1
end

2

Esto es lo que hago, es bastante rápido y flexible y no tiene mucho código.

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;

Tenga en cuenta que (ORDER BY @count) es un maniquí. No hace nada, pero ROW_NUMBER () requiere un ORDER BY.

Editar : me di cuenta de que la pregunta original era obtener un rango de x a y. Mi script se puede modificar así para obtener un rango:

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
Esto fue muy rápido y flexible. Funcionó bien para mis necesidades.
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

Esto solo funciona para secuencias siempre que alguna tabla de aplicaciones tenga filas. Suponga que quiero la secuencia de 1..100, y tengo la tabla de aplicaciones dbo.foo con columna (de tipo numérico o de cadena) foo.bar:

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

A pesar de su presencia en un orden por cláusula, dbo.foo.bar no tiene que tener valores distintos o incluso no nulos.

Por supuesto, SQL Server 2012 tiene objetos de secuencia, por lo que hay una solución natural en ese producto.


1

Esto es lo que se me ocurrió:

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 hasta 2 ^ 24 valores. Las condiciones de unión lo mantienen rápido para valores pequeños.


1

Esto se completó para mí en 36 segundos en nuestro servidor DEV. Al igual que la respuesta de Brian, centrarse en filtrar al rango es importante desde la consulta; un ENTRE todavía intenta generar todos los registros iniciales antes del límite inferior a pesar de que no los necesita.

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

Tenga en cuenta que ROW_NUMBER es un bigint , por lo que no podemos repasar 2 ^^ 64 (== 16 ^^ 16) registros generados con ningún método que lo use. Por lo tanto, esta consulta respeta el mismo límite superior para los valores generados.


1

Esto usa código de procedimiento y una función con valores de tabla. Lento, pero fácil y predecible.

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

Es una tabla, por lo que puede usarla junto con otros datos. Con mayor frecuencia uso esta función como el lado izquierdo de una unión contra un GROUP BY hora, día, etc. para garantizar una secuencia contigua de valores de tiempo.

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
(...)

El rendimiento es poco inspirador (16 segundos para un millón de filas) pero lo suficientemente bueno para muchos propósitos.

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

1

Oráculo 12c; Rápido pero limitado:

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

Nota : limitado al recuento de filas de la vista all_objects;


1

La solución que he desarrollado y utilizado desde hace bastante tiempo (montar algunos en los trabajos compartidos de otros) es ligeramente similar a al menos uno publicado. No hace referencia a ninguna tabla y devuelve un rango sin clasificar de hasta 1048576 valores (2 ^ 20) y puede incluir negativos si lo desea. Por supuesto, puede ordenar el resultado si es necesario. Funciona bastante rápido, especialmente en rangos más pequeños.

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

Hice la siguiente función después de leer este hilo. Simple y rápido

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)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.