Wie generiere ich einen Zahlenbereich zwischen zwei Zahlen?


141

Ich habe zwei Zahlen als Eingabe vom Benutzer, wie zum Beispiel 1000und 1050.

Wie generiere ich die Zahlen zwischen diesen beiden Zahlen mithilfe einer SQL-Abfrage in separaten Zeilen? Ich will das:

 1000
 1001
 1002
 1003
 .
 .
 1050

Antworten:


158

Wählen Sie nicht persistierte Werte mit dem VALUESSchlüsselwort aus. Verwenden Sie dann JOINs, um viele, viele Kombinationen zu generieren (kann erweitert werden, um Hunderttausende von Zeilen und mehr zu erstellen).

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

Eine kürzere Alternative, die nicht so einfach zu verstehen ist:

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
Dies ist eine fantastisch elegante Lösung
Aaron Hudon

9
Können Sie die Syntax erklären? Was ist das v (n)?
Rafi

2
@ Rafi das v (n) und Hunderte (n) usw. sind Tabellen- und Spaltennamen / Aliase
Twon-ha

106

Eine alternative Lösung ist der rekursive 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
Versuchen Sie nicht, die Option maxrecusion in einer Ansichtsdefinition zu verwenden. Stattdessen müssen Sie SELECT * FROM CTE_VIEW OPTION (MAXRECURSION 10000) auswählen - problematisch, wenn Ihre Clientanwendung die Ansicht so verwenden möchte, wie sie ist.
TvdH

4
Es ist eine maximale maximale Rekursion auf 32767 festgelegt (in SQL Server 2012).
BProv

4
Nur um zu verdeutlichen, wenn Sie eine Rekursion von mehr als 32767 benötigen, dann kann sie auf 0 gesetzt werden, was nomax bedeutet,
Jayvee

2
Hier ist eine Demo für diese Antwort.
Stomy

7
Ich habe diese Antwort mit den anderen verglichen und der Ausführungsplan zeigt, dass diese Antwort ( mit den geringsten Abfragekosten und ) die schnellste ist.
Stomy

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

Demo

Beachten Sie, dass diese Tabelle maximal 2048 enthält, da die Zahlen dann Lücken aufweisen.

Hier ist ein etwas besserer Ansatz mit einer Systemansicht (seit 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

oder verwenden Sie eine benutzerdefinierte Zahlentabelle. Dank an Aaron Bertrand empfehle ich, den gesamten Artikel zu lesen: Generieren Sie einen Satz oder eine Sequenz ohne Schleifen


2
@ user3211705: Beachten Sie meine Bearbeitung, diese Tabelle hat ein Maximum von 2048. Ich empfehle, den gesamten Artikel zu lesen.
Tim Schmelter

3
Ich denke, Sie könnten hinzufügen WHERE type = 'P'und vermeidenSELECT DISTINCT
Salman A

1
Ihr erster "Demo" String index out of range: 33
-Link

1
Du hast recht. Aber es scheint ein Problem mit SqlFiddle zu sein. Funktioniert es in Ihrer DB?
Tim Schmelter

4
Kurzer Hinweis, datenbankübergreifende Abfragen wie diese funktionieren nicht mit SQL Azure
Kieren Johnstone

33

Ich habe kürzlich diese Inline-Tabellenwertfunktion geschrieben, um genau dieses Problem zu lösen. Die Reichweite ist nicht auf Speicher und Speicher beschränkt. Es greift auf keine Tabellen zu, sodass im Allgemeinen keine Lese- oder Schreibvorgänge auf der Festplatte erforderlich sind. Es fügt bei jeder Iteration exponentiell Verknüpfungswerte hinzu, sodass es auch für sehr große Bereiche sehr schnell ist. Auf meinem Server werden in fünf Sekunden zehn Millionen Datensätze erstellt. Es funktioniert auch mit negativen Werten.

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

Es ist auch praktisch für Datums- und Zeitbereiche:

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

Sie können einen Cross-Apply-Join verwenden, um Datensätze basierend auf den Werten in der Tabelle aufzuteilen. Um beispielsweise einen Datensatz für jede Minute in einem Zeitbereich in einer Tabelle zu erstellen, können Sie Folgendes tun:

select TimeRanges.StartTime,
    TimeRanges.EndTime,
    DATEADD(minute,X, 0) MinuteX
FROM TimeRanges
cross apply fn_ConsecutiveNumbers(datediff(hour,0,TimeRanges.StartTime), 
        datediff(hour,0,TimeRanges.EndTime)) ConsecutiveNumbers

1
Wow, diese erste Abfrage ist SCHNELL. Viel schneller als die oben beschriebene CLR-Lösung. Vielen Dank!
Derreck Dean

1
Schön - ich habe noch einen Client auf SQL Server 2008 und das war genau das, was ich brauchte! Sehr schlau!
STLDev

1
es funktioniert für 1-100, schlägt dann aber fehl. Selbst Ihr Beispiel für die Generierung von 5-500 funktioniert bei mir nicht. Es zeigt 5, 21, ... 484, 500
Rez.Net

3
Wenn Sie möchten, dass es sortiert wird, müssen Sie eine Bestellung nach Klausel hinzufügen:SELECT X FROM fn_ConsecutiveNumbers(5, 500) ORDER BY X;
Brian Pressler

29

Die beste Option, die ich verwendet habe, ist wie folgt:

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

Ich habe damit Millionen von Datensätzen generiert und es funktioniert perfekt.


2
Dies ist die eleganteste Lösung hier, aber ich denke, es ist für viele Menschen schwierig, sie zu verstehen (ich habe dies mit master.sys.all_columns gemacht). @ STLDeveloper, ja, es funktioniert mit 2008 und höher.
Cetin Basoz

13

Es funktioniert für mich!

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

2
Netter Einzeiler - aber seien Sie gewarnt, dass die maximale Anzahl von Zeilen davon abhängt sys.all_objects- für kleine Bereiche <2000 Elemente ist dies kein Problem. Sie sind sich nicht sicher, ob es Berechtigungsprobleme geben wird? Perfekt, um schnell eine Reihe von Testdaten zu generieren.
Freiheit-m

@ Freedomn-m Eine Möglichkeit, die maximale Anzahl von Zeilen zu erhöhen, besteht darin, einen Self-Cross-Join durchzuführen. select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount from sys.all_objects a, sys.all_objects b. Wo ich vorher nur 2384 Zeilen generieren konnte, kann ich jetzt 5683456 Zeilen generieren.
Klicker

9

Der beste Weg ist die Verwendung von rekursiven 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
Das war sehr nützlich. Ich habe den Code so geändert, dass ich 100.000 Zeilen einfügen kann. Mit meiner Lösung dauerte es ungefähr 13 Minuten; Bei Ihnen dauerte es fünf Sekunden. Muchísimas gracias.
Cthulhu

2
Tatsächlich sind rekursive CTEs eine der schlechtesten Methoden zum Zählen. Sie können sogar von einer While-Schleife in einer Transaktion geschlagen werden, und die While-Schleife erzeugt weit weniger Lesevorgänge. Die cCTE-Methode (Cascading CTEs, ursprünglich von Itizik Ben-Gan) ist viel schneller und erzeugt keine Lesevorgänge.
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
Ist das anders als die Antwort von @Jayvee?
Noel

1
Ja, in welchem ​​Zustand wird es als num + 1 <1050 erwähnt, was nur bis zu 1049 druckt.
Sowbarani Karthikeyan

2
Eine Bearbeitung (oder ein Kommentar) der vorhandenen Antwort, die unbedingt erforderlich ist, würde mehr Wert liefern als eine völlig neue Antwort.
Noel

7

Wenn Sie keine Probleme beim Installieren einer CLR-Assembly auf Ihrem Server haben, empfiehlt es sich, eine Tabellenwertfunktion in .NET zu schreiben. Auf diese Weise können Sie eine einfache Syntax verwenden, die das Verknüpfen mit anderen Abfragen erleichtert und als Bonus keinen Speicher verschwendet, da das Ergebnis gestreamt wird.

Erstellen Sie ein Projekt mit der folgenden Klasse:

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

Legen Sie die Assembly irgendwo auf dem Server ab und führen Sie Folgendes aus:

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

Jetzt können Sie ausführen:

select * from dbo.seq(1, 1000000)

1
Ich habe diese Lösung ausprobiert und sie funktioniert gut, nur nicht superschnell. Wenn Sie nur 1.000 oder vielleicht 10.000 Zahlen generieren, geht das ziemlich schnell. Wenn Sie wie ich sind und Milliarden von Zahlen generieren müssen, ist die unten stehende Lösung von Brian Pressler im Vergleich zu SQL CLR unglaublich schnell.
Derreck Dean

2
@DerreckDean Du hast recht. Ich denke, dies ist die beste Lösung, da es einfach zu erstellen und zu verwenden ist (und schnell, wie Sie sagen). In meinem Fall hatte ich bereits eine Assembly zum Verketten von Zeichenfolgen, also habe ich sie einfach dort hinzugefügt.
AlexDev

1
Ich hatte auch eine vorhandene Baugruppe und habe beide Methoden ausprobiert. Ich generiere eine unbestimmte Anzahl von Zahlen, die zu Datumsangaben hinzugefügt werden sollen (im Grunde habe ich den SQL Server-Agenten-Scheduler zum Generieren von Datumsangaben für unsere interne Anwendung neu erstellt, und 100 Rekursionsstufen haben es nicht gekürzt, um mehrere Jahre zu generieren datetimes, möglicherweise auf die Sekunde genau.), so dass ich mehrere Lösungen aus diesem Thread gründlich testen konnte. Ich freue mich über Ihren Beitrag!
Derreck Dean

7

Nichts Neues, aber ich habe die Brian Pressler-Lösung umgeschrieben, um das Auge zu schonen. Sie könnte für jemanden nützlich sein (auch wenn es nur meine Zukunft ist):

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
Ich glaube, Sie haben die Essenz eines schönen Algorithmus in einen geradezu hübschen Code zerlegt.
Clay

1
Die Ergebnisse sind in einer seltsamen, aber nicht chaotischen Reihenfolge angeordnet. Testen Sie es im Bereich von 5 bis 500. Es gibt 5,21,37, ..., 245,6,22, ... zurück. Wissen Sie, wie die Bestellung die Leistung beeinflussen würde? Lösungen, die auf basieren ROW_NUMBER(), haben dieses Problem nicht.
Przemyslaw Remin

1
Ich bin kein Experte, aber intuitiv würde ich vermuten, dass der SQL Server alle Ergebnisse im Speicher ablegen und ordnen muss, bevor er sie zurückgibt, damit mehr Speicher belegt und die Antwort verzögert wird, anstatt nur die Ergebnisse so zu streamen, wie sie kommen.
Guillaume86

6

2 Jahre später, aber ich stellte fest, dass ich das gleiche Problem hatte. Hier ist, wie ich es gelöst habe. (bearbeitet, um Parameter einzuschließen)

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

Die Antwort von slartidan kann in Bezug auf die Leistung verbessert werden, indem alle Verweise auf das kartesische Produkt entfernt und ROW_NUMBER()stattdessen verwendet werden ( Ausführungsplan verglichen ):

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

Wickeln Sie es in einen CTE ein und fügen Sie eine where-Klausel hinzu, um die gewünschten Zahlen auszuwählen:

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 beginnt erst bei 1. Wie können wir mit Ihrer Methode bei Null beginnen?
Stomy

2
@ Stomy SELECT ROW_NUMBER() OVER (...) - 1 AS n. In einigen Fällen kann dies die Leistung beeinträchtigen.
Salman A

4

Hier sind einige recht optimale und kompatible Lösungen:

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
Ist diese Methode irgendwie besser als einfach selecting where spt_values.number between @min and @max?
underscore_d

2
Der Filter Typ = 'P' ist erforderlich, um doppelte Zahlen zu vermeiden. Mit diesem Filter gibt die Tabelle die Zahlen 0 - 2047 zurück. Der Filter "Zahl zwischen @min und @max" funktioniert also, solange sich die Variablen innerhalb dieses Bereichs befinden. Mit meiner Lösung können Sie bis zu 2048 Zeilen im ganzzahligen Bereich (-2.147.483.648) - (2.147.483.647) abrufen.
Jumxozizi

1
Die obige Logik ist nur dann nützlich, wenn die Differenz zwischen der maximalen und minimalen Anzahl kleiner als 2048 und einmal maximal 2048 zu einem bestimmten Zeitpunkt
aufgezeichnet werden kann

4

Ich weiß, dass ich 4 Jahre zu spät bin, aber ich bin auf eine weitere alternative Antwort auf dieses Problem gestoßen. Das Problem der Geschwindigkeit ist nicht nur das Vorfiltern, sondern auch das Verhindern des Sortierens. Es ist möglich, die Verknüpfungsreihenfolge so zu erzwingen, dass das kartesische Produkt als Ergebnis der Verknüpfung tatsächlich zählt. Slartidans Antwort als Ausgangspunkt verwenden:

    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

Wenn wir den gewünschten Bereich kennen, können wir ihn über @Upper und @Lower angeben. Durch Kombinieren des Join-Hinweises REMOTE mit TOP können wir nur die Teilmenge der gewünschten Werte berechnen, ohne dass etwas verschwendet wird.

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

Der Join-Hinweis REMOTE zwingt den Optimierer, zuerst auf der rechten Seite des Joins zu vergleichen. Wenn Sie jeden Join als FERN vom höchsten bis zum niedrigstwertigen Wert angeben, zählt der Join selbst korrekt um eins nach oben. Sie müssen nicht mit einem WHERE filtern oder mit einem ORDER BY sortieren.

Wenn Sie den Bereich vergrößern möchten, können Sie weitere Verknüpfungen mit zunehmend höheren Größenordnungen hinzufügen, sofern diese in der FROM-Klausel von der höchsten zur niedrigsten Bedeutung geordnet sind.

Beachten Sie, dass dies eine Abfrage ist, die für SQL Server 2008 oder höher spezifisch ist.


1
Wirklich sehr nett. Die gleiche Technik kann auf Brian Presslers hervorragende Antwort und Guillaume86s reizendes Umschreiben angewendet werden.
Clay

3

Dies wird auch reichen

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

Die beste Geschwindigkeit beim Ausführen von Abfragen

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

3

rekursiver CTE in exponentieller Größe (selbst bei einer Standardrekursion von 100 können bis zu 2 ^ 100 Zahlen erstellt werden):

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

Laut OP denke ich @startnumund endnumsollte vom Benutzer eingegeben werden?
JC

2

Ich musste den Bilddateipfad mit einer ähnlichen Methode in die Datenbank einfügen. Die folgende Abfrage hat gut funktioniert:

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

Der Code für Sie wäre:

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

    SET @num = @num + 1
end

2

Das mache ich, es ist ziemlich schnell und flexibel und nicht viel Code.

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;

Beachten Sie, dass (ORDER BY @count) ein Dummy ist. Es macht nichts anderes als ROW_NUMBER () erfordert ein ORDER BY.

Bearbeiten : Ich erkannte, dass die ursprüngliche Frage war, einen Bereich von x bis y zu erhalten. Mein Skript kann folgendermaßen geändert werden, um einen Bereich zu erhalten:

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
Das war sehr schnell - und flexibel. Arbeitete gut für meine Bedürfnisse.
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

Dies funktioniert nur für Sequenzen, solange eine Anwendungstabelle Zeilen enthält. Angenommen, ich möchte eine Sequenz von 1..100 und habe die Anwendungstabelle dbo.foo mit der Spalte (vom numerischen oder Zeichenfolgentyp) foo.bar:

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

Trotz des Vorhandenseins in einer order by-Klausel muss dbo.foo.bar keine eindeutigen oder gar nicht null Werte haben.

Natürlich verfügt SQL Server 2012 über Sequenzobjekte, daher gibt es in diesem Produkt eine natürliche Lösung.


1

Folgendes habe ich mir ausgedacht:

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

Erzeugt bis zu 2 ^ 24 Werte. Join-Bedingungen halten es für kleine Werte schnell.


1

Dies war für mich in 36 Sekunden auf unserem DEV-Server erledigt. Wie bei Brians Antwort ist es wichtig, sich innerhalb der Abfrage auf das Filtern nach dem Bereich zu konzentrieren. a ZWISCHEN versucht immer noch, alle anfänglichen Datensätze vor der Untergrenze zu generieren, obwohl sie nicht benötigt werden.

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

Beachten Sie, dass ROW_NUMBER ein Bigint ist , sodass wir mit keiner Methode, die es verwendet, über 2 ^^ 64 (== 16 ^^ 16) generierte Datensätze gehen können. Diese Abfrage berücksichtigt daher die gleiche Obergrenze für generierte Werte.


1

Dies verwendet prozeduralen Code und eine Tabellenwertfunktion. Langsam, aber einfach und vorhersehbar.

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

Verwendung:

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

Es ist eine Tabelle, sodass Sie sie in Verknüpfungen mit anderen Daten verwenden können. Ich verwende diese Funktion am häufigsten als linke Seite eines Joins gegen eine GROUP BY-Stunde, einen Tag usw., um eine zusammenhängende Folge von Zeitwerten sicherzustellen.

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

Die Leistung ist wenig inspirierend (16 Sekunden für eine Million Zeilen), aber für viele Zwecke gut genug.

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

1

Oracle 12c; Schnell aber begrenzt:

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

Hinweis : Beschränkt auf die Zeilenanzahl der Ansicht all_objects.


1

Die Lösung, die ich seit einiger Zeit entwickelt und verwendet habe (einige davon basieren auf den gemeinsamen Werken anderer), ähnelt mindestens einer veröffentlichten. Es verweist nicht auf Tabellen und gibt einen unsortierten Bereich von bis zu 1048576 Werten (2 ^ 20) zurück und kann bei Bedarf auch Negative enthalten. Sie können das Ergebnis natürlich bei Bedarf sortieren. Es läuft ziemlich schnell, besonders auf kleineren Reichweiten.

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

Ich habe die folgende Funktion ausgeführt, nachdem ich diesen Thread gelesen habe. Einfach und schnell:

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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.