จะสร้างช่วงของตัวเลขระหว่างตัวเลขสองตัวได้อย่างไร?


148

ฉันมีตัวเลขสองตัวเป็นอินพุตจากผู้ใช้เช่น 1000และ1050.

ฉันจะสร้างตัวเลขระหว่างสองตัวเลขนี้โดยใช้แบบสอบถาม sql ในแถวแยกกันได้อย่างไร ฉันต้องการสิ่งนี้:

 1000
 1001
 1002
 1003
 .
 .
 1050

คำตอบ:


167

เลือกค่าที่ไม่คงอยู่ด้วย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


14
นี่คือโซลูชันที่สวยงามน่าอัศจรรย์
Aaron Hudon

9
คุณสามารถอธิบายไวยากรณ์ได้หรือไม่? v (n) คืออะไร?
Rafi

2
@Rafi ตัว v (n) และหลายร้อย (n) ฯลฯ เป็นชื่อตารางและคอลัมน์ / นามแฝง
Twon-ha

111

ทางเลือกอื่นคือ 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 OPTION (MAXRECURSION 10,000) - เป็นปัญหาหากแอปพลิเคชันไคลเอนต์ของคุณต้องการใช้มุมมองตามที่เป็นอยู่
TvdH

4
มีการตั้งค่าmaxrecursionสูงสุดเป็น 32767 (ใน SQL Server 2012)
BProv

4
เพื่อชี้แจงหากคุณต้องการการเรียกซ้ำมากกว่า 32767 คุณสามารถตั้งค่าเป็น 0 ซึ่งหมายถึง nomax
Jayvee

2
นี่คือการสาธิตสำหรับคำตอบนี้
เหยียบ

7
ฉันเปรียบเทียบคำตอบนี้กับข้ออื่น ๆ และแผนการดำเนินการแสดงให้เห็นว่าคำตอบนี้ ( มีค่าใช้จ่ายในการสืบค้นน้อยที่สุดและ ) เร็วที่สุด
stomy

44
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

หรือใช้ตารางตัวเลขที่กำหนดเอง ให้เครดิตกับ Aaron Bertrand ฉันขอแนะนำให้อ่านบทความทั้งหมด: สร้างชุดหรือลำดับโดยไม่ต้องวนซ้ำ


2
@ user3211705: สังเกตการแก้ไขของฉันตารางนี้มีสูงสุด 2048 ฉันแนะนำให้อ่านบทความทั้งหมด
Tim Schmelter

3
ฉันคิดว่าคุณสามารถเพิ่มWHERE type = 'P'และหลีกเลี่ยงได้SELECT DISTINCT
Salman A

1
ลิงก์ "สาธิต" แรกของคุณช่วยบอกฉันString index out of range: 33
slartidan

1
คุณถูก. แต่ดูเหมือนจะเป็นปัญหากับ SqlFiddle ทำงานในฐานข้อมูลของคุณหรือไม่
Tim Schmelter

5
บันทึกย่อการสืบค้นข้ามฐานข้อมูลเช่นนี้ใช้ไม่ได้กับ SQL Azure
Kieren Johnstone

34

ฉันเพิ่งเขียนฟังก์ชันมูลค่าตารางอินไลน์นี้เพื่อแก้ปัญหานี้ ไม่ จำกัด ในช่วงอื่นนอกจากหน่วยความจำและที่เก็บข้อมูล ไม่เข้าถึงตารางดังนั้นจึงไม่จำเป็นต้องอ่านหรือเขียนดิสก์โดยทั่วไป เพิ่มค่าการรวมแบบทวีคูณในการทำซ้ำแต่ละครั้งดังนั้นจึงรวดเร็วมากแม้ในช่วงที่มีขนาดใหญ่มาก มันสร้างสิบล้านบันทึกในห้าวินาทีบนเซิร์ฟเวอร์ของฉัน นอกจากนี้ยังทำงานร่วมกับค่าลบ

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

คุณสามารถใช้ cross apply join เพื่อแยกระเบียนตามค่าในตาราง ตัวอย่างเช่นในการสร้างบันทึกสำหรับทุกนาทีในช่วงเวลาในตารางคุณสามารถทำสิ่งต่างๆเช่น:

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 ที่โพสต์ไว้ข้างต้นมาก ขอบคุณ!
Derreck Dean

1
ดี - ฉันยังมีไคลเอ็นต์อยู่ใน SQL Server 2008 และนี่เป็นเพียงสิ่งที่ฉันต้องการ ฉลาดมาก!
STLDev

1
ใช้งานได้สำหรับ 1-100 แต่ล้มเหลว แม้แต่ตัวอย่างของคุณในการสร้าง 5-500 ก็ไม่ได้ผลสำหรับฉันมันแสดงให้เห็นว่า 5, 21, ... 484, 500
Rez.Net

3
หากคุณต้องการจัดเรียงคุณจะต้องเพิ่มคำสั่งตามข้อ:SELECT X FROM fn_ConsecutiveNumbers(5, 500) ORDER BY X;
Brian Pressler

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_columns) @STLDeveloper ใช่มันใช้ได้กับปี 2008 และใหม่กว่า
Cetin Basoz

14

ได้ผลสำหรับฉัน!

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 แถวได้แล้ว
Klicker

10
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 หรือไม่?
Noel

1
ใช่ในเงื่อนไขที่กล่าวถึงเป็น num + 1 <1050 ซึ่งจะพิมพ์ได้ไม่เกิน 1049 เท่านั้น
Sowbarani Karthikeyan

2
การแก้ไข (หรือแสดงความคิดเห็น) สำหรับคำตอบที่มีอยู่ซึ่งมีความสำคัญเช่นเดียวกันจะให้คุณค่ามากกว่าคำตอบใหม่ทั้งหมด
Noel

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)

Saludos.


1
สิ่งนี้มีประโยชน์มาก ฉันแก้ไขโค้ดเพื่อให้สามารถแทรกแถวได้ 100.000 แถว ด้วยวิธีการแก้ปัญหาของฉันใช้เวลา 13 นาที โดยใช้ของคุณใช้เวลาห้าวินาที Muchísimas gracias
Cthulhu

3
จริงๆแล้ว CTE แบบเรียกซ้ำเป็นวิธีที่แย่ที่สุดวิธีหนึ่งในการนับ พวกเขายังสามารถถูกโจมตีโดย While Loop ในการทำธุรกรรมและ While Loop จะให้การอ่านน้อยลงมาก วิธี cCTE (Cascading CTEs เดิมโดย Itizik Ben-Gan) เร็วกว่ามากและให้การอ่านเป็นศูนย์
Jeff Moden

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
ฉันลองใช้วิธีแก้ปัญหานี้แล้วและใช้ได้ดีเพียง แต่ไม่เร็วมาก หากคุณสร้างตัวเลขเพียง 1,000 หมายเลขหรืออาจจะ 10,000 หมายเลขก็ค่อนข้างเร็ว หากคุณเป็นเหมือนฉันและต้องสร้างตัวเลขหลายพันล้านโซลูชันของ Brian Pressler ด้านล่างนี้เร็วอย่างไม่น่าเชื่อเมื่อเทียบกับ SQL CLR
Derreck Dean

2
@DerreckDean คุณพูดถูก ฉันคิดว่าเขาเป็นทางออกที่ดีที่สุดเพราะสร้างและใช้งานได้ง่าย (และเร็วอย่างที่คุณพูด) ในกรณีของฉันฉันมีแอสเซมบลีสำหรับการต่อสตริงอยู่แล้วดังนั้นฉันจึงเพิ่มเข้าไปที่นั่น
AlexDev

1
ฉันมีชุดประกอบที่มีอยู่แล้วและลองทั้งสองวิธี ฉันกำลังสร้างจำนวนตัวเลขที่ไม่แน่นอนเพื่อเพิ่มวันที่ (โดยทั่วไปฉันสร้างตัวกำหนดตารางเวลาเอเจนต์เซิร์ฟเวอร์ SQL ขึ้นใหม่เพื่อสร้างวันที่สำหรับแอปพลิเคชันภายในของเราและการเรียกซ้ำ 100 ระดับจะไม่ถูกตัดออกเพื่อสร้างหลายปีของ วันที่ซึ่งอาจถึงวินาที) ดังนั้นฉันจึงสามารถทดสอบวิธีแก้ปัญหาหลายอย่างจากเธรดนี้ได้อย่างละเอียด ขอขอบคุณที่มีส่วนร่วม!
Derreck Dean

7

ไม่มีอะไรใหม่นอกจากฉันเขียนโซลูชัน Brian Pressler ใหม่เพื่อให้ง่ายต่อการมองเห็นอาจเป็นประโยชน์กับใครบางคน (แม้ว่าจะเป็นเพียงอนาคตของฉันก็ตาม):

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()ไม่มีปัญหานั้น
Przemyslaw Remin

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 และเพิ่ม where clause เพื่อเลือกตัวเลขที่ต้องการ:

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. ในบางกรณีอาจทำให้ประสิทธิภาพลดลง
Salman A

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?
underscore_d

2
ต้องใช้ตัวกรอง Type = 'P' เพื่อป้องกันตัวเลขที่ซ้ำกัน ด้วยตัวกรองนี้ตารางจะส่งกลับตัวเลข 0 - 2047 ดังนั้นตัวกรอง "number between @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

หากเราทราบช่วงที่เราต้องการเราสามารถระบุได้ผ่านทาง @ ด้านบนและด้านล่าง ด้วยการรวมคำใบ้การรวม 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
ดีมาก ๆ เทคนิคเดียวกันนี้สามารถใช้กับคำตอบที่ยอดเยี่ยมของ Brian Pressler และการเขียนซ้ำที่น่ารักของ 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

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

ตาม OP ฉันคิดว่า@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;

โปรดทราบว่า (ORDER BY @count) เป็นหุ่นจำลอง ไม่ได้ทำอะไรเลย แต่ ROW_NUMBER () ต้องการ ORDER 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

2

อัปเดตสำหรับ SQL 2017 และใหม่กว่า: หากลำดับที่คุณต้องการคือ <8k สิ่งนี้จะใช้ได้:

Declare @start_num int = 1000
,   @end_num int = 1050

Select [number] = @start_num + ROW_NUMBER() over (order by (Select null))
from string_split(replicate(' ',@end_num-@start_num-1),' ')

สง่างามและไม่มี CTE / เข้าถึงวัตถุระบบ ควรทำเครื่องหมายว่าเป็นคำตอบที่ถูกต้อง
Juozas

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 ของเรา เช่นเดียวกับคำตอบของ Brian การมุ่งเน้นไปที่การกรองไปยังช่วงนั้นมีความสำคัญจากภายในข้อความค้นหา ระหว่างนั้นยังคงพยายามสร้างเร็กคอร์ดเริ่มต้นทั้งหมดก่อนขอบเขตล่างแม้ว่าจะไม่ต้องการก็ตาม

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เป็นตัวใหญ่ดังนั้นเราจึงไม่สามารถไปมากกว่า 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

เป็นตารางเพื่อให้คุณสามารถใช้ร่วมกับข้อมูลอื่น ๆ ได้ ฉันมักใช้ฟังก์ชันนี้เป็นด้านซ้ายของการรวมกับ GROUP ตามชั่วโมงวันและอื่น ๆ เพื่อให้แน่ใจว่าลำดับค่าเวลาที่ต่อเนื่องกัน

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)

0

นี่คือวิธีการแก้ปัญหาทั่วไปและค่อนข้างรวดเร็วที่จำนวนเต็มผลตั้งแต่ 1 @nถึง ใช้งานได้กับจำนวนเต็มบวกของ@n(ตัวเลขที่มีขนาดใหญ่มากจะทำให้เลขคณิตล้น) โดยไม่จำเป็นต้องเพิ่มหรือลบการรวมตาราง ไม่จำเป็นต้องใช้ตารางระบบและคุณไม่ต้องเปลี่ยนการเรียกซ้ำสูงสุด

declare @n int = 10000 

;with d as (select * from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x (d)),
n as ( 
    select d x from d where d > 0 and d <= @n
    union all
    select x * 10 + d from n, d where x * 10 + d <= @n
)
select x from n 

คุณสามารถเพิ่มorder byอนุประโยคเพื่อจัดเรียงตัวเลข

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.