Jak wygenerować zakres liczb między dwiema liczbami?

141

Mam dwie liczby jako dane wejściowe od użytkownika, jak na przykład 1000i1050 .

Jak wygenerować liczby między tymi dwiema liczbami, używając zapytania sql, w oddzielnych wierszach? Chcę to:

 1000
 1001
 1002
 1003
 .
 .
 1050
user3211705
źródło

Odpowiedzi:

159

Wybierz nietrwałe wartości za pomocą VALUESsłowa kluczowego. Następnie użyj JOINs, aby wygenerować wiele, wiele kombinacji (można je rozszerzyć, aby utworzyć setki tysięcy wierszy i więcej).

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

Krótsza alternatywa, która nie jest tak łatwa do zrozumienia:

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

slartidan
źródło
13
To fantastycznie eleganckie rozwiązanie
Aaron Hudon
9
Czy możesz wyjaśnić składnię? Co to jest v (n)?
Rafi
2
@Rafi the v (n) i setki (n) etc to nazwy / aliasy tabel i kolumn
Twon-ha
106

alternatywnym rozwiązaniem jest rekurencyjne CTE:

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)
Jayvee
źródło
4
Nie próbuj używać opcji maxrecusion w definicji widoku. Zamiast tego należy wybrać opcję SELECT * FROM CTE_VIEW OPTION (MAXRECURSION 10000) - problematyczne, jeśli aplikacja kliencka chce korzystać z widoku w obecnej postaci.
TvdH
4
Istnieje maksymalna wartość maxrecursion ustawiona na 32767 (w programie SQL Server 2012).
BProv
4
Właśnie w celu wyjaśnienia, czy trzeba rekursji większą niż 32767, to może być ustawiony na 0, co oznacza NOMAX,
Jayvee
2
Oto Demo dla tej odpowiedzi.
stomia
7
Porównałem tę odpowiedź z innymi, a Plan wykonania pokazuje, że ta odpowiedź ( ma najmniejszy koszt zapytania i ) jest najszybsza.
stomia
39
SELECT DISTINCT n = number 
FROM master..[spt_values] 
WHERE number BETWEEN @start AND @end

Demo

Zauważ, że ta tabela ma maksymalnie 2048, ponieważ wtedy liczby mają luki.

Oto nieco lepsze podejście przy użyciu widoku systemu (od wersji 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

lub użyj niestandardowej tabeli liczbowej. Podziękowania dla Aarona Bertranda, proponuję przeczytać cały artykuł: Wygeneruj zestaw lub sekwencję bez pętli

Tim Schmelter
źródło
2
@ user3211705: zwróć uwagę na moją zmianę, ta tabela ma maksymalnie 2048. Proponuję przeczytać cały artykuł.
Tim Schmelter
3
Myślę, że możesz dodać WHERE type = 'P'i uniknąćSELECT DISTINCT
Salman A
1
Twój pierwszy link do "Demo" ciągle mi mówiString index out of range: 33
slartidan
1
Masz rację. Ale wydaje się, że jest to problem z SqlFiddle. Czy to działa w twojej bazie danych?
Tim Schmelter
4
Krótka uwaga: takie zapytania między bazami danych nie działają z SQL Azure
Kieren Johnstone
33

Niedawno napisałem tę funkcję wartościowaną w tabeli inline, aby rozwiązać ten właśnie problem. Nie jest ograniczony zasięgiem innym niż pamięć i przechowywanie. Nie uzyskuje dostępu do tabel, więc nie ma potrzeby wykonywania odczytów ani zapisów na dysku. Dodaje wartości złączeń wykładniczo przy każdej iteracji, dzięki czemu jest bardzo szybki nawet dla bardzo dużych zakresów. Tworzy dziesięć milionów rekordów w pięć sekund na moim serwerze. Działa również z wartościami ujemnymi.

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

Jest to przydatne również w przypadku zakresów dat i godzin:

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

Możesz użyć na nim sprzężenia krzyżowego, aby podzielić rekordy na podstawie wartości w tabeli. Na przykład, aby utworzyć rekord dla każdej minuty w zakresie czasu w tabeli, możesz zrobić coś takiego:

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
Brian Pressler
źródło
1
Wow, to wstępne zapytanie jest SZYBKIE. Znacznie szybsze niż opisane powyżej rozwiązanie CLR. Dzięki!
Derreck Dean
1
Świetnie - nadal mam klienta na SQL Server 2008 i właśnie tego potrzebowałem! Bardzo mądry!
STLDev
1
działa dla 1-100, ale potem zawodzi. Nawet twój przykład generowania 5-500 nie działa dla mnie, pokazuje 5, 21, ... 484, 500
Rez.Net
3
Jeśli chcesz, aby wszystko zostało uporządkowane, musisz dodać kolejność według klauzuli:SELECT X FROM fn_ConsecutiveNumbers(5, 500) ORDER BY X;
Brian Pressler
29

Najlepsza opcja, z której korzystałem, jest następująca:

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

Wygenerowałem miliony rekordów za pomocą tego i działa idealnie.

Habeeb
źródło
2
Jest to najbardziej eleganckie rozwiązanie tutaj, ale myślę, że wielu osobom trudno to zrozumieć (robiłem to z master.sys.all_columns). @STLDeveloper, tak, działa z 2008 i późniejszymi.
Cetin Basoz
13

To działa dla mnie!

select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount
from sys.all_objects a
Nguyen Son Tung
źródło
2
Ładny, jednoliniowy - ale ostrzegamy, że maksymalna liczba rzędów będzie zależała od sys.all_objects- w przypadku małych zakresów <2000 elementów nie stanowi to problemu. Nie jesteś pewien, czy będzie miał problemy z uprawnieniami? idealne do szybkiego generowania partii danych testowych.
wolność, n-m,
@ wolnośćn-m Jednym ze sposobów zwiększenia maksymalnej liczby wierszy byłoby wykonanie sprzężenia krzyżowego. select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount from sys.all_objects a, sys.all_objects b. Tam, gdzie wcześniej mogłem wygenerować tylko 2384 wiersze, teraz mogę wygenerować wiersze 5683456.
Klicker
9

Najlepszym sposobem jest użycie rekurencyjnych ctes.

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.

cesargroening
źródło
1
To było bardzo przydatne. Zmodyfikowałem kod, więc mogę wstawić 100 000 wierszy. Z moim rozwiązaniem zajęło to około 13 minut; używając twojego, zajęło to pięć sekund. Muchísimas gracias.
Cthulhu
2
W rzeczywistości rekurencyjne CTE to jeden z najgorszych sposobów liczenia. Mogą nawet zostać pokonane przez pętlę While w transakcji, a pętla While będzie generować znacznie mniej odczytów. Metoda cCTE (Cascading CTEs, pierwotnie autorstwa Itizika Ben-Gana) jest znacznie szybsza i daje zerowe odczyty.
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
Sowbarani Karthikeyan
źródło
1
Czy różni się to od odpowiedzi @Jayvee?
Noel
1
Tak, w przypadku gdy warunek jest wymieniony jako num + 1 <1050, co spowoduje wydrukowanie tylko do 1049.
Sowbarani Karthikeyan
2
Edycja (lub komentarz) do istniejącej odpowiedzi, która jest istotna, to samo, zapewniłaby większą wartość niż zupełnie nowa odpowiedź.
Noel
7

Jeśli nie masz problemu z instalacją zestawu CLR na serwerze, dobrym rozwiązaniem jest napisanie funkcji wartościowanej tabelowo w .NET. W ten sposób możesz użyć prostej składni, dzięki czemu łatwo będzie łączyć się z innymi zapytaniami, a jako bonus nie marnujesz pamięci, ponieważ wynik jest przesyłany strumieniowo.

Utwórz projekt zawierający następującą klasę:

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

Umieść zestaw gdzieś na serwerze i uruchom:

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

Teraz możesz biegać:

select * from dbo.seq(1, 1000000)
AlexDev
źródło
1
Wypróbowałem to rozwiązanie i działa dobrze, tylko nie super szybko. Jeśli generujesz tylko 1000, a może 10000 liczb, jest to dość szybkie. Jeśli jesteś podobny do mnie i musisz generować miliardy liczb, poniższe rozwiązanie Briana Presslera jest niewiarygodnie szybkie w porównaniu z SQL CLR.
Derreck Dean
2
@DerreckDean Masz rację. Myślę, że jego rozwiązanie jest najlepszym rozwiązaniem, ponieważ jest łatwe w tworzeniu i obsłudze (i szybko, jak mówisz). W moim przypadku miałem już zestaw do łączenia łańcuchów, więc po prostu go tam dodałem.
AlexDev
1
Miałem również istniejący zespół i wypróbowałem obie metody. Generuję nieokreśloną liczbę liczb do dodania do dat (w zasadzie odtworzyłem program planujący agenta serwera SQL do generowania dat dla naszej wewnętrznej aplikacji, a 100 poziomów rekursji nie ograniczyło go do generowania wielu lat datimes, prawdopodobnie do sekundy.), więc mogłem dokładnie przetestować wiele rozwiązań z tego wątku. Doceniam Twój wkład!
Derreck Dean
7

Nic nowego, ale przepisałem rozwiązanie Briana Presslera, aby było przyjemniejsze dla oka, może się komuś przydać (nawet jeśli to tylko przyszły mnie):

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
Guillaume86
źródło
1
Wydaje mi się, że wydestylowałeś istotę pięknego algorytmu w całkiem ładny kod.
Clay
1
Wyniki są uporządkowane w dziwnej, ale nie chaotycznej kolejności. Przetestuj w zakresie od 5 do 500. Zwraca 5,21,37, ..., 245,6,22, ... Czy wiesz, jak porządkowanie wpłynie na wydajność? Rozwiązania oparte na oprogramowaniu ROW_NUMBER()nie mają tego problemu.
Przemysław Remin
1
Nie jestem ekspertem, ale intuicyjnie sądzę, że serwer SQL będzie musiał zapisać wszystkie wyniki w pamięci i uporządkować je przed ich zwróceniem, aby uzyskać większe zużycie pamięci i opóźnioną odpowiedź, w przeciwieństwie do przesyłania wyników w miarę ich pojawiania się.
Guillaume86
6

2 lata później, ale okazało się, że mam ten sam problem. Oto jak to rozwiązałem. (edytowane w celu uwzględnienia parametrów)

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)
chrząszcz
źródło
5

Odpowiedź slartidana można poprawić, pod względem wydajności, poprzez wyeliminowanie wszelkich odniesień do produktu kartezjańskiego i użycie ROW_NUMBER()zamiast tego ( porównanie planu wykonania ):

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

Zawiń go wewnątrz CTE i dodaj klauzulę where, aby wybrać żądane liczby:

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
Salman A
źródło
1
ROW_NUMBER zaczyna się dopiero od 1. W jaki sposób możemy zacząć od zera za pomocą Twojej metody?
stomia
2
@stomy SELECT ROW_NUMBER() OVER (...) - 1 AS n. W niektórych przypadkach może to obniżyć wydajność.
Salman A,
4

Oto kilka całkiem optymalnych i kompatybilnych rozwiązań:

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
;
jumxozizi
źródło
1
Czy ta metoda jest w jakiś sposób lepsza niż po prostu selecting where spt_values.number between @min and @max?
underscore_d
2
Filtr Type = „P” jest wymagany, aby zapobiec zduplikowaniu liczb. Z tym filtrem tabela zwróci liczby od 0 do 2047. Zatem filtr „liczba między @min i @max” będzie działał tak długo, jak długo zmienne będą znajdować się w tym zakresie. Moje rozwiązanie pozwoli Ci uzyskać do 2048 wierszy w zakresie liczb całkowitych (-2 147 483 648) - (2 147 483 647).
jumxozizi
1
powyższa logika jest przydatna tylko wtedy, gdy różnica między maksymalną a minimalną liczbą jest mniejsza niż 2048 i jednorazowo może maksymalnie 2048 rekordów w danym momencie
Smart003
4

Wiem, że spóźniłem się o 4 lata, ale natknąłem się na inną alternatywną odpowiedź na ten problem. Problem z szybkością to nie tylko wstępne filtrowanie, ale także zapobieganie sortowaniu. Możliwe jest wymuszenie wykonania kolejności łączenia w taki sposób, że iloczyn kartezjański faktycznie liczy się w wyniku łączenia. Wykorzystując odpowiedź Slartidana jako punkt wyjścia:

    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

Jeśli znamy żądany zakres, możemy go określić za pomocą @Upper i @Lower. Łącząc wskazówkę o sprzężeniu REMOTE wraz z TOP, możemy obliczyć tylko podzbiór wartości, które chcemy, i nic nie zostanie zmarnowane.

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

Wskazówka dotycząca łączenia REMOTE wymusza na optymalizatorze porównanie najpierw po prawej stronie sprzężenia. Określając każde sprzężenie jako ZDALNE od najbardziej do najmniej znaczącej wartości, samo sprzężenie będzie poprawnie liczyć w górę o jeden. Nie ma potrzeby filtrowania za pomocą GDZIE lub sortowania według ZAMÓWIENIA.

Jeśli chcesz zwiększyć zakres, możesz kontynuować dodawanie dodatkowych sprzężeń o stopniowo wyższych rzędach wielkości, o ile są uporządkowane od najbardziej do najmniej znaczących w klauzuli FROM.

Zauważ, że jest to zapytanie specyficzne dla SQL Server 2008 lub nowszego.

mechoida
źródło
1
Naprawdę bardzo ładnie. Tę samą technikę można zastosować do doskonałej odpowiedzi Briana Presslera i cudownego przepisania Guillaume86.
Clay
3

To też wystarczy

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
BICube
źródło
3

Najlepsza prędkość podczas wykonywania zapytania

DECLARE @num INT = 1000
WHILE(@num<1050)
begin
 INSERT  INTO [dbo].[Codes]
    (   Code
    ) 
    VALUES (@num)
    SET @num = @num + 1
end
Farhad Manafi
źródło
3

rekurencyjne CTE o rozmiarze wykładniczym (nawet przy domyślnej rekursji 100, może to zbudować do 2 ^ 100 liczb):

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
user7555577
źródło
Według PO uważam @startnumi endnumpowinienem być wprowadzany przez użytkownika?
JC
2

Musiałem wstawić ścieżkę pliku obrazu do bazy danych przy użyciu podobnej metody. Poniższe zapytanie zadziałało dobrze:

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

Kod dla ciebie byłby:

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

    SET @num = @num + 1
end
Maludasek
źródło
2

To właśnie robię, jest to dość szybkie i elastyczne i nie zawiera dużo kodu.

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;

Zauważ, że (ORDER BY @count) jest atrapą. To nic nie robi, ale ROW_NUMBER () wymaga ORDER BY.

Edycja : zdałem sobie sprawę, że pierwotnym pytaniem było uzyskanie zakresu od x do y. Mój skrypt można zmodyfikować w ten sposób, aby uzyskać zakres:

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;
JohnnyIV
źródło
1
To było bardzo szybkie - i elastyczne. Działał dobrze na moje potrzeby.
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
Elexsandro Rangel dos Santos
źródło
1

Działa to tylko w przypadku sekwencji, o ile tabela aplikacji zawiera wiersze. Załóżmy, że chcę sekwencję od 1..100 i mam tabelę aplikacji dbo.foo z kolumną (typu numerycznego lub łańcuchowego) foo.bar:

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

Pomimo swojej obecności w klauzuli order by, dbo.foo.bar nie musi mieć odrębnych ani nawet niezerowych wartości.

Oczywiście SQL Server 2012 ma obiekty sekwencyjne, więc ten produkt jest naturalnym rozwiązaniem.

BobHy
źródło
1

Oto, co wymyśliłem:

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

Generuje maksymalnie 2 ^ 24 wartości. Warunki łączenia utrzymują go szybko dla małych wartości.

τεκ
źródło
1

Ukończyło się to dla mnie w 36 sekund na naszym serwerze DEV. Podobnie jak odpowiedź Briana, skupienie się na filtrowaniu do zakresu jest ważne od samego początku zapytania; a BETWEEN nadal próbuje wygenerować wszystkie początkowe rekordy przed dolną granicą, nawet jeśli ich nie potrzebuje.

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

Zauważ, że ROW_NUMBER to bigint , więc nie możemy przejść przez 2 ^^ 64 (== 16 ^^ 16) wygenerowanych rekordów żadną metodą, która go używa. Dlatego to zapytanie przestrzega tego samego górnego limitu generowanych wartości.

J Bryan Price
źródło
1

Używa kodu proceduralnego i funkcji wycenianej w tabeli. Powolny, ale łatwy i przewidywalny.

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

Stosowanie:

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

Jest to tabela, więc możesz jej używać w połączeniach z innymi danymi. Najczęściej używam tej funkcji jako lewej strony sprzężenia z GRUPĄ według godziny, dnia itp., Aby zapewnić ciągłą sekwencję wartości czasu.

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

Wydajność jest mało inspirująca (16 sekund na milion wierszy), ale wystarczająca do wielu celów.

SELECT count(1) FROM [dbo].[Sequence] (
   1000001
  ,2000000)
GO
Robert Calhoun
źródło
1

Oracle 12c; Szybki, ale ograniczony:

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

Uwaga : ograniczona do liczby wierszy widoku all_objects;

Witold Kaczurba
źródło
1

Rozwiązanie, które opracowałem i używam już od jakiegoś czasu (jeżdżę na udostępnionych pracach innych) jest nieco podobne do przynajmniej jednego opublikowanego. Nie odwołuje się do żadnych tabel i zwraca nieposortowany zakres do 1048576 wartości (2 ^ 20) i w razie potrzeby może zawierać negatywy. W razie potrzeby możesz oczywiście posortować wynik. Działa dość szybko, zwłaszcza na mniejszych dystansach.

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
)
rlhane
źródło
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
Chriz
źródło
1

Poniższą funkcję wykonałem po przeczytaniu tego wątku. Prosto i szybko:

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)
Lanjing Jin
źródło