İki sayı arasında bir sayı aralığı nasıl oluşturulur?


141

Kullanıcıdan girdi olarak iki numara var, örneğin 1000ve gibi 1050.

Bir sql sorgusu kullanarak bu iki sayı arasındaki sayıları ayrı satırlarda nasıl oluştururum? Bunu istiyorum:

 1000
 1001
 1002
 1003
 .
 .
 1050

Yanıtlar:


159

VALUESAnahtar kelimeyle kalıcı olmayan değerleri seçin . Daha sonra JOINçok ve çok sayıda kombinasyon oluşturmak için s kullanın (yüz binlerce satır ve ötesi oluşturmak için genişletilebilir).

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

Daha kısa bir alternatif, bu anlaşılması kolay değil:

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
Bu fevkalade zarif bir çözüm
Aaron Hudon

9
Sözdizimini açıklayabilir misiniz? V (n) nedir?
Rafi

2
@Rafi the v (n) ve yüzlerce (n) vb. Tablo ve sütun adları / takma adlarıdır
Twon-ha

106

alternatif bir çözüm özyinelemeli CTE'dir:

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
Bir görünüm tanımında maxrecusion seçeneğini kullanmaya çalışmayın. Bunun yerine, istemci uygulamanız görünümü olduğu gibi kullanmak istiyorsa, sorunlu, CTE_VIEW OPTION (MAXRECURSION 10000) FROM SELECT * seçmelisiniz.
TvdH

4
32767 olarak ayarlanan bir maksimum maksrecursion vardır (SQL Server 2012'de).
BProv

4
Sadece açıklığa kavuşturmak için, 32767'den fazla bir özyineye ihtiyacınız varsa, o zaman 0 olarak ayarlanabilir, bu da nomax anlamına gelir,
Jayvee

2
İşte bu cevap için Demo .
stomy

7
Bu cevabı diğerleriyle karşılaştırdım ve İcra planı bu cevabın ( en düşük sorgu maliyetine sahip ve ) en hızlı olduğunu gösteriyor.
stomy

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

Demo

Bu tablonun maksimum 2048 olduğuna dikkat edin, çünkü sayılarda boşluklar vardır.

İşte sistem görünümünü kullanarak biraz daha iyi bir yaklaşım (SQL-Server 2005'ten beri):

;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

veya özel bir sayı tablosu kullanın. Aaron Bertrand'a verilen krediler, tüm makaleyi okumanızı öneririm: Döngüsüz bir dizi veya dizi oluşturma


2
@ user3211705: düzenlememe dikkat edin, bu tabloda en fazla 2048 tane var. Makalenin tamamını okumanızı öneririm.
Tim Schmelter

3
Ben eklemek ve kaçınmak düşünüyorumWHERE type = 'P'SELECT DISTINCT
Salman A

1
İlk "Demo" bağlantınız sürekli anlatmaya devam ediyorString index out of range: 33
slartidan

1
Haklısın. Ama bu SqlFiddle ile ilgili bir sorun gibi görünüyor. DB'nizde çalışıyor mu?
Tim Schmelter

4
Hızlı not, bunun gibi veritabanları arası sorgular SQL Azure ile çalışmaz
Kieren Johnstone

33

Son zamanlarda bu problemi çözmek için bu satır içi tablo değerli işlevi yazdım. Bellek ve depolama alanı dışında sınırlı değildir. Tablolara erişmez, bu nedenle genel olarak disk okuma veya yazma işlemlerine gerek kalmaz. Her yinelemede birleştirme değerlerini katlanarak ekler, böylece çok geniş aralıklar için bile çok hızlıdır. Sunucumda beş saniyede on milyon kayıt oluşturur. Ayrıca negatif değerlerle çalışır.

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

Tarih ve saat aralıkları için de kullanışlıdır:

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

Tablodaki değerlere göre kayıtları bölmek için üzerinde çapraz uygulama birleştirmesi kullanabilirsiniz. Örneğin, bir tablodaki zaman aralığında her dakika için bir kayıt oluşturmak için şöyle bir şey yapabilirsiniz:

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
Vay be, bu ilk sorgu HIZLI. Yukarıda yayınlanan CLR çözümünden çok daha hızlı. Teşekkürler!
Derreck Dean

1
Güzel - Hala SQL Server 2008'de bir müşterim var ve bu sadece ihtiyacım olan şeydi! Çok zeki!
STLDev

1
1-100 için çalışır ancak başarısız olur. 5-500 üretme örneğiniz bile benim için çalışmıyor, 5, 21, ... 484, 500
Rez.Net

3
Sıralanmasını istiyorsanız, maddeye göre sipariş eklemeniz gerekir:SELECT X FROM fn_ConsecutiveNumbers(5, 500) ORDER BY X;
Brian Pressler

29

Kullandığım en iyi seçenek şöyledir:

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

Bunu kullanarak milyonlarca kayıt oluşturdum ve mükemmel çalışıyor.


2
Buradaki en zarif çözüm, ancak birçok insanın bunu anlaması zor olduğunu düşünüyorum (bunu master.sys.all_columns ile yapıyordum). @STLDeveloper, evet 2008 ve sonrasında çalışır.
Çetin Basoz

13

Benim için çalışıyor!

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

2
Güzel bir astar - ancak maksimum satır sayısının bağlı olacağı konusunda uyarılmalıdır sys.all_objects- 2000'den küçük küçük aralıklar için bu bir sorun değildir. İzin sorunları olup olmayacağından emin değil misiniz? hızlı bir şekilde bir grup test verisi oluşturmak için mükemmeldir.
freedomn-m

@ freedomn-m Maksimum satır sayısını artırmanın bir yolu, kendi kendine çapraz birleştirme yapmaktır. select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount from sys.all_objects a, sys.all_objects b. Daha önce sadece 2384 satır oluşturabildiğim yerde artık 5683456 satır oluşturabilirim.
Klicker

9

En iyi yol, özyinelemeli korsanları kullanmaktır.

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
Bu çok faydalı oldu. 100.000 satır ekleyebilmek için kodu değiştirdim. Çözümümle 13 dakika sürdü; sizinkini kullanarak beş saniye sürdü. Muchísimas gracias.
Cthulhu

2
Aslında, tekrarlayan CTE'ler saymanın en kötü yollarından biridir. Hatta bir işlemde While Döngüsü ile dövülebilirler ve While Döngüsü çok daha az okuma üretir. CCTE yöntemi (başlangıçta Itizik Ben-Gan tarafından basamaklı CTE'ler) çok daha hızlıdır ve sıfır okuma üretir.
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
Bu @Jayvee'nin cevabından farklı mı?
Noel

1
Evet, sadece 1049'a kadar baskı yapacak olan num + 1 <1050 olarak belirtildiği durumda.
Sowbarani Karthikeyan

2
Aynı olan mevcut cevapta yapılan bir düzenleme (veya yorum), tamamen yeni bir yanıttan daha fazla değer sağlayacaktır.
Noel

7

Sunucunuza bir CLR derleme yüklemekte sorun yoksa, iyi bir seçenek .NET tablo değerli bir işlev yazıyor. Bu şekilde basit bir sözdizimi kullanabilirsiniz, böylece diğer sorgularla birleştirmeyi kolaylaştırabilirsiniz ve bonus olarak sonuç akıtıldığından bellek israfına neden olmaz.

Aşağıdaki sınıfı içeren bir proje oluşturun:

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

Montajı sunucuda bir yere koyun ve çalıştırın:

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

Şimdi çalıştırabilirsiniz:

select * from dbo.seq(1, 1000000)

1
Bu çözümü denedim ve süper hızlı değil, iyi çalışıyor. Yalnızca 1.000 sayı veya 10.000 sayı oluşturuyorsanız, oldukça hızlıdır. Eğer benim gibiyseniz ve milyarlarca sayı üretmek zorundaysanız, Brian Pressler'in aşağıdaki çözümü SQL CLR ile karşılaştırıldığında inanılmaz derecede hızlıdır.
Derreck Dean

2
@DerreckDean Haklısın. Bence onun en iyi çözümü olduğunu çünkü yaratması ve kullanması kolay (ve dediğin gibi hızlı). Benim durumumda zaten dizeleri birleştirmek için bir montaj vardı, bu yüzden sadece orada ekledim.
AlexDev

1
Ben de mevcut bir montaj vardı ve her iki yöntemi denedim. Tarihlere eklemek için belirsiz sayıda sayı üretiyorum (temel olarak, şirket içi uygulamamız için tarihler oluşturmak için SQL sunucu aracısı zamanlayıcısını yeniden oluşturdum ve özyineleme seviyeleri 100 yılını oluşturmak için kesmeyecekti datetimes, muhtemelen ikinci aşağı.), bu yüzden iyice bu iplik birden çok çözüm test edebildi. Katkınız için teşekkür ederim!
Derreck Dean

7

Yeni bir şey yok ama Brian Pressler çözümünü göze daha kolay olacak şekilde yeniden yazdım, biri için yararlı olabilir (sadece gelecekteki ben bile):

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
Güzel bir algoritmanın özünü bazı düpedüz güzel kodlara damıttığınıza inanıyorum.
kil

1
Sonuçlar garip fakat kaotik değil sıralanır. 5 ile 500 aralığında test edin. 5,21,37, ..., 245,6,22, ... döndürür. Siparişin performansı nasıl etkileyeceğini biliyor musunuz? Dayalı çözümlerde ROW_NUMBER()bu sorun yoktur.
Przemyslaw Remin

1
Ben bir uzman değilim ama sezgisel olarak SQL sunucusunun tüm sonuçları belleğe koyup onları daha önce kullanmadan önce daha fazla bellek kullanımı ve gecikmeli yanıt döndürmeden önce sipariş vermesi gerektiğini tahmin ediyorum.
Guillaume86

6

2 yıl sonra da aynı sorunu yaşadığımı fark ettim. İşte böyle çözdüm. (parametreleri içerecek şekilde düzenlendi)

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

slartidan cevabı kartezyen ürünle ilgili tüm içeriği ortadan kaldırılması ve kullanarak, akıllı performans, iyileştirilebilir ROW_NUMBER()yerine ( yürütme planını karşılaştırıldığında ):

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

Bir CTE'nin içine sarın ve istediğiniz sayıları seçmek için bir where cümlesi ekleyin:

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 sadece 1'de başlıyor. Yönteminizle sıfırdan nasıl başlayabiliriz?
stomy

2
stomy SELECT ROW_NUMBER() OVER (...) - 1 AS n. Bazı durumlarda bu performansı düşürebilir.
Salman A

4

İşte oldukça uygun ve uyumlu birkaç çözüm:

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
Bu yöntem bir şekilde basitçe selectanlatmaktan daha mı iyi where spt_values.number between @min and @max?
underscore_d

2
Yinelenen sayıları önlemek için Type = 'P' filtresi gereklidir. Bu filtre ile tablo 0 - 2047 arasındaki sayıları döndürür. Bu nedenle "@min ve @max arasındaki sayı" filtresi, değişkenler bu aralık dahilinde olduğu sürece çalışır. Çözümüm, tamsayı aralığında (-2.147.483.648) - (2.147.483.647) 2048 satıra ulaşmanıza izin verecektir.
jumxozizi

1
Yukarıdaki mantık yalnızca
2048'den

4

Biliyorum, 4 yıl çok geç kaldım, ama bu soruna başka bir alternatif cevap daha tökezledim. Hız sorunu sadece ön filtreleme değil aynı zamanda sıralamayı da engeller. Birleştirme sırasını, Kartezyen ürünün birleştirme sonucunda gerçekten saydığı bir şekilde çalışmaya zorlamak mümkündür. Slartidan'ın cevabını bir baraj noktası olarak kullanmak:

    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

İstediğimiz aralığı bilersek, @Upper ve @Lower aracılığıyla belirtebiliriz. REMOTE birleşim ipucunu TOP ile birleştirerek, yalnızca istediğimiz değerlerin alt kümesini hiçbir şey israf etmeden hesaplayabiliriz.

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

Birleştirme ipucu REMOTE, optimize ediciyi önce birleştirme işleminin sağ tarafında karşılaştırmaya zorlar. Her bir birleştirmeyi en çok önemli değere UZAKTAN olarak belirterek birleştirmenin kendisi doğru bir şekilde yukarı doğru sayılır. NEREDE filtrelemenize veya SİPARİŞ TARAFINDAN sıralamaya gerek yoktur.

Aralığı artırmak istiyorsanız, FROM yan tümcesinde en önemliden en az önemli olana doğru sıralandıkları sürece, giderek daha yüksek büyüklük derecelerine sahip ek birleştirmeler eklemeye devam edebilirsiniz.

Bunun SQL Server 2008 veya sonraki sürümlere özgü bir sorgu olduğunu unutmayın.


1
Gerçekten çok iyi. Aynı teknik Brian Pressler'in mükemmel cevabına ve Guillaume86'nın güzel yeniden yazmasına da uygulanabilir.
kil

3

Bu da yapacak

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

Sorgu çalıştırıldığında en iyi hız

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

3

üstel boyutta özyinelemeli CTE (varsayılan 100 özyineleme için bile, bu en fazla 2 ^ 100 sayı oluşturabilir):

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

OP göre, sanırım @startnumve endnumkullanıcı tarafından girilmesi gerekir?
JC

2

Ben benzer bir yöntem kullanarak veritabanına resim dosyayolu eklemek zorunda kaldı. Aşağıdaki sorgu düzgün çalıştı:

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

Sizin için kod:

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

    SET @num = @num + 1
end

2

Yaptığım şey bu, oldukça hızlı ve esnek ve çok fazla kod değil.

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;

(ORDER BY @count) bir sahte olduğunu unutmayın. Hiçbir şey yapmaz, ancak ROW_NUMBER () bir ORDER BY gerektirir.

Düzenleme : Orijinal sorunun x ile y arasında bir aralık elde etmek olduğunu fark ettim. Bir aralık elde etmek için betiğim şu şekilde değiştirilebilir:

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
Bu çok hızlı ve esnektir. Benim ihtiyaçları için iyi çalıştı.
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

Bu, yalnızca bazı uygulama tablosunda satırlar olduğu sürece diziler için geçerlidir. Diyelim ki 1..100 dizisini istiyorum ve do.foo uygulama tablosuna sahip sütun (sayısal veya dize türü) foo.bar var:

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

Bir cümle ile bir sırada bulunmasına rağmen, dbo.foo.bar öğesinin ayrı veya null olmayan değerlere sahip olması gerekmez.

Tabii ki, SQL Server 2012'de dizi nesneleri vardır, bu yüzden bu üründe doğal bir çözüm vardır.


1

İşte ben geldim:

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

En fazla 2 ^ 24 değer üretir. Birleştirme koşulları, küçük değerler için hızlı olmasını sağlar.


1

Bu benim için DEV sunucumuzda 36 saniye içinde tamamlandı. Brian'ın yanıtı gibi, aralığa filtrelemeye odaklanmak da sorgu içinden önemlidir; BETWEEN, ihtiyaç duymasa bile alt sınırdan önce tüm ilk kayıtları oluşturmaya çalışır.

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

ROW_NUMBER bir bigint olduğunu unutmayın , bu yüzden 2 ^ ^ 64 (== 16 ^^ 16) oluşturulan kayıtları, onu kullanan herhangi bir yöntemle geçemeyiz. Dolayısıyla bu sorgu, üretilen değerlerde aynı üst limite saygı duyar.


1

Bu yordamsal kod ve tablo değerli bir işlev kullanır. Yavaş ama kolay ve öngörülebilir.

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

Kullanımı:

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

Bu bir tablo, bu yüzden diğer verilerle birleştirmede kullanabilirsiniz. Bu işlevi sık sık bir dizi zaman değerleri dizisi sağlamak için bir GROUP BY saat, gün vb karşı birleştirme sol tarafı olarak kullanın.

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

Performans sönük (bir milyon satır için 16 saniye), ancak birçok amaç için yeterince iyi.

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

1

Oracle 12c; Hızlı fakat sınırlı:

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

Not : all_objects görünümünün satır sayısı ile sınırlıdır;


1

Şu ana kadar geliştirdiğim ve kullandığım çözüm (diğerlerinin ortak çalışmalarına binmek) yayınlanan en az bir şeye biraz benziyor. Herhangi bir tabloya başvurmaz ve 1048576 değerine (2 ^ 20) kadar sıralanmamış bir aralık döndürür ve istenirse negatifleri içerebilir. Gerekirse elbette sonucu sıralayabilirsiniz. Özellikle daha küçük aralıklarda oldukça hızlı çalışır.

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

Bu konuyu okuduktan sonra aşağıdaki işlevi yaptım. Basit ve hızlı:

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.