Як генерувати діапазон чисел між двома числами?


141

У мене є два числа як вхід від користувача, як, наприклад, 1000і 1050.

Як я можу генерувати числа між цими двома номерами, використовуючи запит sql, в окремі рядки? Я хочу це:

 1000
 1001
 1002
 1003
 .
 .
 1050

Відповіді:


159

Виберіть необоротні значення за допомогою VALUESключового слова. Потім використовуйте JOINs для створення безлічі комбінацій (можна розширити, щоб створити сотні тисяч рядків і далі).

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

Коротша альтернатива, яку не так просто зрозуміти:

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
Це фантастично елегантне рішення
Аарон Хадон

9
Чи можете ви пояснити синтаксис? Що таке v (n)?
Рафі

2
@Rafi, v (n) і сотні (n) тощо - це назви таблиць та стовпців / псевдоніми
Twon-ha

106

альтернативне рішення - рекурсивний 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)

4
Не намагайтеся використовувати параметр maxrecusion у визначенні подання. Натомість вам потрібно ВИБІРАТИ * ВІД ОПЦІЇ CTE_VIEW (MAXRECURSION 10000) - проблематично, якщо клієнтська програма хоче споживати вигляд таким, яким він є.
TvdH

4
Максимальна максимальна рекурсія встановлена ​​на 32767 (у SQL Server 2012).
БПров

4
Просто для уточнення, якщо вам потрібна рекурсія більше 32767, то її можна встановити на 0, що означає номакс,
Jayvee

2
Ось демонстрація для цієї відповіді.
стом

7
Я порівняв цю відповідь з іншими, і План виконання показує, що ця відповідь ( має найменшу вартість запиту і ) є найшвидшою.
стом

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

Demo

Зауважте, що ця таблиця має максимум 2048, оскільки тоді цифри мають прогалини.

Ось дещо кращий підхід із використанням системного перегляду (починаючи з 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

або використовувати спеціальну таблицю номерів. Подяки Аарону Бертранду, я пропоную прочитати всю статтю: Створіть набір або послідовність без циклів


2
@ user3211705: зауважте, що я редагую, ця таблиця має максимум 2048. Я пропоную прочитати всю статтю.
Тім Шмелтер

3
Я думаю, ви могли б додати WHERE type = 'P'та уникнутиSELECT DISTINCT
Salman A

1
Ваше перше посилання "Демо" продовжує говорити меніString index out of range: 33
slartidan

1
Ти маєш рацію. Але, здається, це проблема із SqlFiddle. Це працює у вашій БД?
Тім Шмелтер

4
Швидка примітка, запити між базами даних, подібні цьому, не працюють із SQL Azure
Kieren Johnstone,

33

Нещодавно я написав цю функціональну функцію в таблиці, щоб вирішити цю проблему. Він не обмежений діапазоном, крім пам’яті та пам’яті. Він не має доступу до таблиць, тому немає потреби в читанні чи записі диска взагалі. Він додає значення об'єднання експоненціально для кожної ітерації, тому це дуже швидко навіть для дуже великих діапазонів. Він створює десять мільйонів записів за п'ять секунд на моєму сервері. Він також працює з негативними значеннями.

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

Це також зручно для дат та часових діапазонів:

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

Ви можете використовувати перехресне застосування приєднатись до нього, щоб розділити записи на основі значень у таблиці. Так, наприклад, для створення запису на кожну хвилину за часовим діапазоном у таблиці ви можете зробити щось на кшталт:

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
Нічого собі, що початковий запит - Швидкий. Набагато швидше, ніж CLR-рішення, розміщене вище. Дякую!
Дірк Деррек

1
Приємно - я ще клієнт на SQL Server 2008, і це було просто те, що мені потрібно! Дуже розумний!
СТЛДєв

1
він працює на 1-100, але потім не працює. Навіть ваш приклад отримання 5-500 не працює для мене, він показує 5, 21, ... 484, 500
Rez.Net

3
Якщо ви хочете, щоб це було відсортовано, вам доведеться додати замовлення за пунктом:SELECT X FROM fn_ConsecutiveNumbers(5, 500) ORDER BY X;
Брайан Пресслер

29

Кращий варіант, який я використав, такий:

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

Я створив мільйони записів за допомогою цього, і це працює ідеально.


2
Це найелегантніше рішення тут, але я думаю, що багатьом людям важко зрозуміти це (я робив це за допомогою master.sys.all_column). @STLDeveloper, так, це працює з 2008 і пізнішими версіями.
Cetin Basoz

13

Це працює для мене!

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

2
Хороший однолінійний, але майте на увазі, що максимальна кількість рядків буде залежати sys.all_objects- для невеликих діапазонів <2000 позицій це не проблема. Не впевнені, чи виникнуть проблеми з дозволами? ідеально підходить для швидкого генерування партії тестових даних.
Freedomn-m

@ Freedomn-m Одним із способів збільшення максимальних рядків було б виконати з'єднання самоперехрестя. select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount from sys.all_objects a, sys.all_objects b. Де раніше я міг генерувати лише 2384 рядки, тепер я можу генерувати 5683456 рядків.
Кликер

9

Найкращий спосіб - використання рекурсивних 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)

салюдо.


1
Це було дуже корисно. Я змінив код, щоб я міг вставити 100 000 рядків. З моїм рішенням минуло як 13 хвилин; використовуючи своє, пішло п’ять секунд. Muchísimas gracias.
Cthulhu

2
Власне, рекурсивні КТЕ - це один з найгірших способів підрахунку. Вони навіть можуть бути побиті циклом у той час як транзакція, а цикл "Хоча" видасть набагато менше читань. Метод cCTE (каскадні CTE, спочатку Ітізік Бен-Ган) набагато швидший і дає нульові читання.
Джефф Моден

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
Чи відрізняється це від відповіді @Jayvee?
Ноель

1
Так, в якому стані його згадується як число + 1 <1050, яке буде надруковано до 1049.
Sowbarani Karthikeyan

2
Редагування (або коментар) до існуючої відповіді, яке є суттєвим тим же, надало б більше значення, ніж абсолютно нова відповідь.
Ноель

7

Якщо у вас немає проблем з установкою збірки CLR на ваш сервер, хорошим варіантом є написання функції, що оцінюється таблицею, в .NET. Таким чином, ви можете використовувати простий синтаксис, полегшуючи приєднання до інших запитів і, як бонус, не витрачайте пам’ять, оскільки результат передається потоково.

Створіть проект, що містить такий клас:

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

Покладіть збірку десь на сервер і запустіть:

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

Тепер ви можете запустити:

select * from dbo.seq(1, 1000000)

1
Я спробував це рішення, і воно працює добре, просто не дуже швидко. Якщо ви генеруєте лише 1000 чисел, а може і 10 000, це досить швидко. Якщо ви схожі на мене і вам доводиться генерувати мільярди чисел, рішення Брайана Преслера нижче неймовірно швидке порівняно з SQL CLR.
Деррек Дін

2
@DerreckDean Ви праві. Я думаю, що його найкраще рішення, тому що його легко створювати та використовувати (і швидко, як ви кажете). У моєму випадку я вже мав збірку для об'єднання струн, тому я просто додав її туди.
AlexDev

1
Я також мав існуючу збірку і спробував обидва способи. Я генерую невизначене число чисел, які потрібно додати до дат (в основному, я відтворив планувальник агентів сервера SQL для генерації дат для нашої внутрішньої програми, і 100 рівнів рекурсії не збиралися скорочувати його для генерування кількох років datetimes, можливо, аж до другого.), тому мені вдалося ретельно протестувати декілька рішень з цієї теми. Я ціную ваш внесок!
Деррек Дін

7

Нічого нового, але я переписав рішення Брайана Преслера, щоб було легше на очах, воно може бути корисним комусь (навіть якщо це я просто майбутнє):

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
Я вважаю, що ви перевели суть прекрасного алгоритму в якийсь прямий гарний код.
Глина

1
Результати упорядковані в дивному, але не хаотичному порядку. Перевірте його на діапазоні від 5 до 500. Він повертає 5,21,37, ..., 245,6,22, ... Чи знаєте ви, як замовлення вплине на ефективність? Рішення на основі ROW_NUMBER()не мають цієї проблеми.
Ремін

1
Я не фахівець, але інтуїтивно, я б здогадався, що серверу SQL потрібно буде зберегти всі результати в пам'яті та замовити їх, перш ніж повернути їм так більше використання пам’яті та затримку реакції на відміну від простого потоку результатів у міру їх отримання.
Guillaume86

6

Через 2 роки, але я виявив, що у мене те саме питання. Ось як я це вирішив. (відредаговано, щоб включити параметри)

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 можна покращити, ефективніше, усунувши всі посилання на декартовий продукт і використовуючи ROW_NUMBER()натомість ( план виконання порівняно ):

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

Загорніть його всередині CTE і додайте пункт де для вибору потрібних цифр:

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 починається лише з 1. Як ми можемо починати з нуля за допомогою вашого методу?
стом

2
@stomy SELECT ROW_NUMBER() OVER (...) - 1 AS n. У деяких випадках це може вбити продуктивність.
Салман А

4

Ось кілька цілком оптимальних та сумісних рішень:

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
Цей спосіб якимось кращим, ніж просто selectінґ where spt_values.number between @min and @max?
підкреслити_11

2
Тип = "P" фільтр необхідний для запобігання повторюваних чисел. За допомогою цього фільтра таблиця повертає числа 0 - 2047. Отже, фільтр "число між @min та @max" буде працювати, поки змінні знаходяться в межах цього діапазону. Моє рішення дозволить отримати до 2048 рядків у цілому діапазоні (-2,147,483,648) - (2,147,483,647).
jumxozizi

1
наведена вище логіка корисна лише тоді, коли різниця між максимальним та мінімальним числом менше 2048 та один раз може мати максимум 2048 записів у певний момент часу
Smart003

4

Я знаю, що я запізнився на 4 роки, але натрапив на ще одну альтернативну відповідь на цю проблему. Проблема швидкості - це не лише попередня фільтрація, а й запобігання сортування. Можна примусити замовлення на приєднання виконати таким чином, що декартовий продукт насправді рахується в результаті з'єднання. Використання відповіді slartidan в якості точки переходу:

    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

Якщо ми знаємо потрібний нам діапазон, можемо вказати його через @Upper та @Lower. Комбінуючи підказку REMOTE про з'єднання разом з TOP, ми можемо обчислити лише підмножину значень, які ми хочемо, без нічого витраченого.

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

Підказка REMOTE про приєднання спочатку змушує оптимізатор порівняти з правого боку з'єднання. Вказавши кожне з'єднання як REMOTE від більшості до найменш значущого значення, сам приєднання підрахуватиметься вгору на один правильно. Не потрібно фільтрувати за допомогою WHERE або сортувати за допомогою ORDER BY.

Якщо ви хочете збільшити діапазон, ви можете продовжувати додавати додаткові з'єднання з прогресивно більшими порядками, доки вони впорядковані від більшості до найменш значущих у пункті FROM.

Зауважте, що це запит, характерний для SQL Server 2008 або вище.


1
Справді дуже приємно. Ця ж техніка може бути застосована і до чудової відповіді Брайана Преслера, і до прекрасного перепису Guillaume86.
Глина

3

Це також зробить

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

Найкраща швидкість при запуску запиту

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

3

рекурсивний CTE в експоненціальному розмірі (навіть за умовчанням 100 рекурсії це може створити до 2 ^ 100 чисел):

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

Відповідно до ОП, я думаю, @startnumі endnumповинен бути внесений користувачем?
JC

2

Мені довелося вставляти файловий шлях зображення в базу даних, використовуючи подібний метод. Запит нижче спрацював нормально:

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

Код для вас буде:

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

    SET @num = @num + 1
end

2

Це те, що я роблю, це досить швидко і гнучко, і не багато коду.

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;

Зверніть увагу, що (ЗАМОВИТИ @ @count) - манекен. Він нічого не робить, але ROW_NUMBER () вимагає ЗАМОВЛЕННЯ BY.

Редагувати : Я зрозумів, що оригінальним питанням було отримати діапазон від x до y. Мій сценарій можна змінити так, щоб отримати діапазон:

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
Це було дуже швидко - і гнучко. Добре працював для моїх потреб.
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

Це працює лише для послідовностей, доки в деякій таблиці додатків є рядки. Припустимо, я хочу послідовність від 1..100 і мати таблицю додатків dbo.foo зі стовпцем (числового або рядкового типу) foo.bar:

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

Незважаючи на свою присутність у порядку за умовою, dbo.foo.bar не повинен мати чітких або навіть ненульових значень.

Звичайно, у SQL Server 2012 є об'єкти послідовності, тому в цьому продукті є природне рішення.


1

Ось що я придумав:

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

Створює до 2 ^ 24 значень. Умови приєднання підтримують це швидко для малих значень.


1

Це завершилося для мене за 36 секунд на нашому сервері DEV. Як і відповідь Брайана, фокусування на фільтрації до діапазону важливо з запиту; a BETWEEN все ще намагається генерувати всі початкові записи до нижньої межі, навіть якщо вони не потребують їх.

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 - це bigint , тому ми не можемо перейти на 2 ^^ 64 (== 16 ^^ 16) згенеровані записи будь-яким методом, який його використовує. Таким чином, цей запит поважає ту саму верхню межу для генерованих значень.


1

Для цього використовується процедурний код і функція з табличним значенням. Повільний, але легкий і передбачуваний.

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

Використання:

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

Це таблиця, тому ви можете використовувати її при з'єднанні з іншими даними. Я найчастіше використовую цю функцію як ліву частину з'єднання проти групи за годиною, днем ​​тощо для забезпечення послідовної послідовності значень часу.

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

Продуктивність не надихає (16 секунд за мільйон рядків), але досить хороша для багатьох цілей.

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

1

Oracle 12c; Швидкий, але обмежений:

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

Примітка : обмежено числом рядків для перегляду всіх_об'єктів;


1

Рішення, яке я розробляв і використовував досить давно (їздив на спільних роботах інших), трохи схожий на принаймні один розміщений. Він не посилається на будь-які таблиці і повертає несортований діапазон до 1048576 значень (2 ^ 20) і за бажанням може включати негативи. Ви, звичайно, можете сортувати результат, якщо це необхідно. Він працює досить швидко, особливо на менших дальність.

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

Я зробив функцію нижче, прочитавши цю тему. Просто і швидко:

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.