Was ist der beste Weg, um eine Zahlentabelle zu erstellen und zu füllen?

71

Ich habe viele verschiedene Möglichkeiten gesehen, eine Zahlentabelle zu erstellen und zu füllen. Was ist jedoch der beste Weg, um einen zu erstellen und zu füllen? Mit "am besten" definiert von am meisten bis am wenigsten wichtig:

  • Tabelle mit optimaler Indizierung erstellt
  • Zeilen am schnellsten generiert
  • einfacher Code zum Erstellen und Auffüllen

Wenn Sie nicht wissen, was eine Zahlentabelle ist, schauen Sie hier: Warum sollte ich die Verwendung einer Hilfszahlentabelle in Betracht ziehen?

KM.
quelle
2
Es stellte sich heraus, dass dies mehr oder weniger ein Duplikat von stackoverflow.com/questions/10819/… war , soweit ich das beurteilen kann
Tao
Der mit Abstand beste Weg ist die Abstimmung für eine integrierte Implementierung einer virtuellen Tabelle, die nicht physisch zugewiesen werden muss. Dies kann derzeit hier erfolgen: https://feedback.azure.com/forums/908035-sql-server/suggestions/32890519-add-a-built-in-table-of-numbers
Louis Somers
1
@ LouisSomers, ich mag diesen Ansatz. Wenn Ihr Programm jedoch lange vor dem Hinzufügen dieser Funktion funktionieren soll, müssen Sie Ihr eigenes Programm erstellen.
KM.
1
@KM Haha, das stimmt, ich bin im selben Boot, ich versuche nur, mehr Stimmen für ein Feature zu sammeln, das meiner Meinung nach wichtiger ist als ein dunkles Thema im Managementstudio ...
Louis Somers

Antworten:

136

Hier sind einige Codebeispiele aus dem Internet und Antworten auf diese Frage.

Für jede Methode habe ich den Originalcode so geändert, dass jede dieselbe Tabelle und Spalte verwendet: NumbersTest und Number, mit 10.000 Zeilen oder so nah wie möglich daran. Außerdem habe ich Links zum Herkunftsort bereitgestellt.

METHODE 1 ist hier eine sehr langsame Schleifenmethode. Von hier aus wurden
durchschnittlich 13,01 Sekunden
dreimal am höchsten entfernt, hier sind Zeiten in Sekunden: 12,42, 13,60

DROP TABLE NumbersTest
DECLARE @RunDate datetime
SET @RunDate=GETDATE()
CREATE TABLE NumbersTest(Number INT IDENTITY(1,1)) 
SET NOCOUNT ON
WHILE COALESCE(SCOPE_IDENTITY(), 0) < 100000
BEGIN 
    INSERT dbo.NumbersTest DEFAULT VALUES 
END
SET NOCOUNT OFF
-- Add a primary key/clustered index to the numbers table
ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE())/1000.0)+' seconds'
SELECT COUNT(*) FROM NumbersTest

METHODE 2 ist eine viel schnellere Schleife von hier aus.
Durchschnittlich 1,1658 Sekunden wurden
11 Mal am höchsten entfernt. Hier sind die Zeiten in Sekunden: 1,117, 1,140, ​​1,203, 1,170, 1,173, 1,156, 1,203, 1,153, 1,173, 1,170

DROP TABLE NumbersTest
DECLARE @RunDate datetime
SET @RunDate=GETDATE()
CREATE TABLE NumbersTest (Number INT NOT NULL);
DECLARE @i INT;
SELECT @i = 1;
SET NOCOUNT ON
WHILE @i <= 10000
BEGIN
    INSERT INTO dbo.NumbersTest(Number) VALUES (@i);
    SELECT @i = @i + 1;
END;
SET NOCOUNT OFF
ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE())/1000.0)+' seconds'
SELECT COUNT(*) FROM NumbersTest

METHODE 3 Hier ist ein einzelnes INSERT basierend auf dem Code von hier.
Durchschnittlich 488,6 Millisekunden,
die 11 Mal am höchsten entfernt wurden. Hier sind Zeiten in Millisekunden: 686, 673, 623, 686,343,343,376,360,343,453

DROP TABLE NumbersTest
DECLARE @RunDate datetime
SET @RunDate=GETDATE()
CREATE TABLE NumbersTest (Number  int  not null)  
;WITH Nums(Number) AS
(SELECT 1 AS Number
 UNION ALL
 SELECT Number+1 FROM Nums where Number<10000
)
insert into NumbersTest(Number)
    select Number from Nums option(maxrecursion 10000)
ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'
SELECT COUNT(*) FROM NumbersTest

METHODE 4 hier ist eine "Semi-Looping" -Methode von hier aus durchschnittlich 348,3 Millisekunden (es war schwierig, ein gutes Timing zu erhalten, da "GO" in der Mitte des Codes vorhanden ist, Vorschläge wären willkommen),
die hier 11 Mal am höchsten entfernt ausgeführt wurde sind Zeiten in Millisekunden: 356, 360, 283, 346, 360, 376, 326, 373, 330, 373

DROP TABLE NumbersTest
DROP TABLE #RunDate
CREATE TABLE #RunDate (RunDate datetime)
INSERT INTO #RunDate VALUES(GETDATE())
CREATE TABLE NumbersTest (Number int NOT NULL);
INSERT NumbersTest values (1);
GO --required
INSERT NumbersTest SELECT Number + (SELECT COUNT(*) FROM NumbersTest) FROM NumbersTest
GO 14 --will create 16384 total rows
ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
SELECT CONVERT(varchar(20),datediff(ms,RunDate,GETDATE()))+' milliseconds' FROM #RunDate
SELECT COUNT(*) FROM NumbersTest

METHODE 5 hier ist ein einzelnes INSERT aus Philip Kelleys Antwort.
Durchschnittlich 92,7 Millisekunden
liefen 11-mal entfernt am höchsten, hier sind Zeiten in Millisekunden: 80, 96, 96, 93, 110, 110, 80, 76, 93, 93

DROP TABLE NumbersTest
DECLARE @RunDate datetime
SET @RunDate=GETDATE()
CREATE TABLE NumbersTest (Number  int  not null)  
;WITH
  Pass0 as (select 1 as C union all select 1), --2 rows
  Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
  Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
  Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
  Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
  --I removed Pass5, since I'm only populating the Numbers table to 10,000
  Tally as (select row_number() over(order by C) as Number from Pass4)
INSERT NumbersTest
        (Number)
    SELECT Number
        FROM Tally
        WHERE Number <= 10000
ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'
SELECT COUNT(*) FROM NumbersTest

METHODE 6 hier ist ein einzelnes INSERT von Mladen Prajdic Antwort
durchschnittlich 82,3 Millisekunden
liefen 11-mal entfernt am höchsten, hier sind Zeiten in Millisekunden: 80, 80, 93, 76, 93, 63, 93, 76, 93, 76

DROP TABLE NumbersTest
DECLARE @RunDate datetime
SET @RunDate=GETDATE()
CREATE TABLE NumbersTest (Number  int  not null)  
INSERT INTO NumbersTest(Number)
SELECT TOP 10000 row_number() over(order by t1.number) as N
FROM master..spt_values t1 
    CROSS JOIN master..spt_values t2
ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number);
PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'
SELECT COUNT(*) FROM NumbersTest

METHODE 7 ist hier ein einzelnes INSERT basierend auf dem Code von hier.
Durchschnittlich 56,3 Millisekunden wurden
11 Mal am höchsten entfernt, hier sind Zeiten in Millisekunden: 63, 50, 63, 46, 60, 63, 63, 46, 63, 46

DROP TABLE NumbersTest
DECLARE @RunDate datetime
SET @RunDate=GETDATE()
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
    INTO NumbersTest
    FROM sys.objects s1       --use sys.columns if you don't get enough rows returned to generate all the numbers you need
    CROSS JOIN sys.objects s2 --use sys.columns if you don't get enough rows returned to generate all the numbers you need
ALTER TABLE NumbersTest ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'
SELECT COUNT(*) FROM NumbersTest

Nachdem ich mir all diese Methoden angesehen habe, mag ich Methode 7, die am schnellsten war und deren Code auch ziemlich einfach ist.

KM.
quelle
Hab diesen Beitrag Jahre später gesehen. Ich würde mich für das Timing von 1 Million Zeilen oder mehr interessieren. Ich kann das eines Tages versuchen, aber 10000 sind wahrscheinlich so viele, wie jemals vernünftigerweise benötigt würden.
Philip Kelley
14
Das Timing ist zwar interessant, scheint mir aber nicht so wichtig zu sein. Insbesondere, wenn ich jemals eine Zahlentabelle benötige, werde ich sie einmal erstellen und immer wieder verwenden.
Ben Thul
Danke vielmals! Ich weiß, dass dies alt ist, aber für diejenigen, die hier landen, würde ich vorschlagen, eine Zahlentabelle mit 100.000 zu erstellen, damit Sie sie in Kombination mit Datumsangaben verwenden können.
BJury
1
Methode 7 erstellte eine Tabelle mit 9604 Zeilen.
Zerem
1
@ Dave, HA, ein Kommentar sagte, dass sie 9604 Zeilen vier Jahre nach der Beantwortung! Sechs Jahre nach der Beantwortung sagen Sie, dass es zufällige Ergebnisse gibt. Zufällige Ergebnisse bedeuten, dass Sie zufällige Werte erhalten. Sie erhalten immer fortlaufende Ganzzahlwerte ab 1, möglicherweise weniger als 10.000, wenn Sie nur wenige Zeilen in sys.objects haben. Ich habe Methode 7 in einer neuen Datenbank (76 Zeilen in sys.objects) ausprobiert und sie kann 5.776 Zeilen (76 * 76) erstellen. Wenn Sie die CROSS JOIN sys.objects s3im vorherigen Kommentar vorgeschlagenen hinzufügen , erhalten Sie 438.976 Zeilen (76 * 76 * 76).
KM.
60

Ich benutze das, was höllisch schnell ist:

insert into Numbers(N)
select top 1000000 row_number() over(order by t1.number) as N
from   master..spt_values t1 
       cross join master..spt_values t2
Mladen Prajdic
quelle
Nur ein Hinweis, dass dies in der Azure SQL-Datenbank nicht unterstützt wird.
Robyaw
20

Wenn Sie dies nur in SQL Server Management Studio oder tun sqlcmd.exe, können Sie die Tatsache verwenden, dass Sie mit dem Stapeltrennzeichen den Stapel wiederholen können:

CREATE TABLE Number (N INT IDENTITY(1,1) PRIMARY KEY NOT NULL);
GO

INSERT INTO Number DEFAULT VALUES;
GO 100000

Dadurch werden 100000 Datensätze Numbersmit dem Standardwert der nächsten Identität in die Tabelle eingefügt.

Es ist langsam. Es ist vergleichbar mit METHODE 1 in der Antwort von @ KM., die das langsamste der Beispiele ist. Es ist jedoch ungefähr so ​​leicht wie es nur geht. Sie können dies etwas beschleunigen, indem Sie die Primärschlüsseleinschränkung nach dem Einfügestapel hinzufügen.

Speckwürfel
quelle
@ Bacon Bits, kann ich nur (a) bestimmte Spalten einfügen?
Azimuth
@Azimuth Sie können diese Methode verwenden, solange Sie eine einzelne INSERT-Anweisung schreiben können, die bei wiederholter Ausführung die Daten für jede Zeile erstellt. Der Batch-Repeater weist den Client (entweder SSMS oder sqlcmd.exe) lediglich an, genau dieselbe Abfrage N-mal zu wiederholen. Sie können T-SQL auf verschiedene Arten nutzen, aber ich vermute, dass es schnell alles andere als Code-Licht werden würde.
Bacon Bits
14

Ich beginne mit der folgenden Vorlage, die aus zahlreichen Drucken der Routine von Itzik Ben-Gan abgeleitet ist:

;WITH
  Pass0 as (select 1 as C union all select 1), --2 rows
  Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
  Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
  Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
  Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
  Pass5 as (select 1 as C from Pass4 as A, Pass4 as B),--4,294,967,296 rows
  Tally as (select row_number() over(order by C) as Number from Pass5)
 select Number from Tally where Number <= 1000000

Die Klausel "WHERE N <= 1000000" begrenzt die Ausgabe auf 1 bis 1 Million und kann einfach auf den gewünschten Bereich eingestellt werden.

Da dies eine WITH-Klausel ist, kann sie wie folgt in ein INSERT ... SELECT ... eingearbeitet werden:

--  Sample use: create one million rows
CREATE TABLE dbo.Example (ExampleId  int  not null)  

DECLARE @RowsToCreate int
SET @RowsToCreate = 1000000

--  "Table of numbers" data generator, as per Itzik Ben-Gan (from multiple sources)
;WITH
  Pass0 as (select 1 as C union all select 1), --2 rows
  Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
  Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
  Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
  Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
  Pass5 as (select 1 as C from Pass4 as A, Pass4 as B),--4,294,967,296 rows
  Tally as (select row_number() over(order by C) as Number from Pass5)
INSERT Example (ExampleId)
 select Number
  from Tally
  where Number <= @RowsToCreate

Das Indizieren der Tabelle nach ihrer Erstellung ist der schnellste Weg, sie zu indizieren.

Oh, und ich würde es als "Tally" -Tabelle bezeichnen. Ich denke, dies ist ein gebräuchlicher Begriff, und Sie können eine Menge Tricks und Beispiele finden, indem Sie ihn googeln.

Philip Kelley
quelle
4

Für alle, die nach einer Azure-Lösung suchen

SET NOCOUNT ON    
CREATE TABLE Numbers (n bigint PRIMARY KEY)    
GO    
DECLARE @numbers table(number int);  
WITH numbers(number) as  (   
SELECT 1 AS number   
UNION all   
SELECT number+1 FROM numbers WHERE number<10000  
)  
INSERT INTO @numbers(number)  
SELECT number FROM numbers OPTION(maxrecursion 10000)
INSERT INTO Numbers(n)  SELECT number FROM @numbers

Quelle: SQL-Azure-Team-Blog http://azure.microsoft.com/blog/2010/09/16/create-a-numbers-table-in-sql-azure/

Denis Pitcher
quelle
4

Hier ist eine kurze und schnelle In-Memory-Lösung, die ich mit den in SQL Server 2008 eingeführten Konstruktoren mit Tabellenwerten entwickelt habe :

Es werden 1.000.000 Zeilen zurückgegeben. Sie können jedoch entweder CROSS JOINs hinzufügen / entfernen oder die TOP-Klausel verwenden, um dies zu ändern.

;WITH v AS (SELECT * FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) v(z))

SELECT N FROM (SELECT ROW_NUMBER() OVER (ORDER BY v1.z)-1 N FROM v v1 
    CROSS JOIN v v2 CROSS JOIN v v3 CROSS JOIN v v4 CROSS JOIN v v5 CROSS JOIN v v6) Nums

Beachten Sie, dass dies schnell im laufenden Betrieb berechnet oder (noch besser) in einer permanenten Tabelle (fügen Sie einfach eine INTOKlausel nach dem SELECT NSegment hinzu) mit einem Primärschlüssel auf dem NFeld gespeichert werden kann, um die Effizienz zu verbessern.

iliketocode
quelle
Ich mag diese Idee, wenn Sie eine On-the-Fly-Zahlentabelle möchten. Es ist langsamer als andere, wenn Sie es zum Generieren einer tatsächlichen Tabelle verwenden.
KM.
1
@KM. Ich habe es gerade in meinem Setup getestet, es dauerte weniger als eine Sekunde. Aber lassen Sie uns hypothetisch sagen, dass es 10 Sekunden gedauert hat, gegenüber einem anderen, der nur 1 Sekunde gedauert hat (um den permanenten Tisch einzurichten). IMO, das ist immer noch winzig, wenn man bedenkt, dass Sie den permanenten Tisch immer nur einmal einrichten müssen. Andere Faktoren, wie die Ausführlichkeit des Codes, wären für mich wichtiger. 1 Minute gegen 1 Sekunde? das wäre etwas anders, aber meine Anfrage ist nicht so langsam.
Iliketocode
3

Ich weiß, dass dieser Thread alt und beantwortet ist, aber es gibt eine Möglichkeit, eine zusätzliche Leistung aus Methode 7 herauszuholen:

Stattdessen (im Wesentlichen Methode 7, aber mit etwas benutzerfreundlicher Politur):

DECLARE @BIT AS BIT = 0
IF OBJECT_ID('tempdb..#TALLY') IS NOT NULL
  DROP TABLE #TALLY
DECLARE @RunDate datetime
SET @RunDate=GETDATE()
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
    INTO #TALLY
    FROM sys.objects s1       --use sys.columns if you don't get enough rows returned to generate all the numbers you need
    CROSS JOIN sys.objects s2 --use sys.co
ALTER TABLE #TALLY ADD PRIMARY KEY(Number)
PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'

Versuche dies:

DECLARE @BIT AS BIT = 0
IF OBJECT_ID('tempdb..#TALLY') IS NOT NULL
  DROP TABLE #TALLY
DECLARE @RunDate datetime
SET @RunDate=GETDATE()
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
    INTO #TALLY
    FROM        (SELECT @BIT [X] UNION ALL SELECT @BIT) [T2]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T4]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T8]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T16]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T32]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T64]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T128]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T256]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T512]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T1024]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T2048]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T4096]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T8192]
    CROSS JOIN  (SELECT @BIT [X] UNION ALL SELECT @BIT) [T16384]
ALTER TABLE #TALLY ADD PRIMARY KEY(Number)
PRINT CONVERT(varchar(20),datediff(ms,@RunDate,GETDATE()))+' milliseconds'

Auf meinem Server dauert dies ~ 10 ms im Gegensatz zu ~ 16-20 ms bei der Auswahl aus sys.objects. Es hat auch den zusätzlichen Vorteil, dass es nicht davon abhängt, wie viele Objekte sich in sys.objects befinden. Es ist zwar ziemlich sicher, aber technisch gesehen eine Abhängigkeit, und die andere geht sowieso schneller. Ich denke, der Geschwindigkeitsschub ist auf die Verwendung von BITs zurückzuführen, wenn Sie Folgendes ändern:

DECLARE @BIT AS BIT = 0

zu:

DECLARE @BIT AS BIGINT = 0

Es addiert ~ 8-10 ms zur Gesamtzeit auf meinem Server. Das heißt, wenn Sie auf 1.000.000 Datensätze skalieren, wirkt sich BIT vs BIGINT nicht mehr nennenswert auf meine Abfrage aus, aber es läuft immer noch ungefähr 680 ms vs 730 ms von sys.objects.

Luke Kubat
quelle
2

Ich verwende Zahlentabellen, um hauptsächlich Berichte in BIRT zu duplizieren, ohne mit der dynamischen Erstellung von Datensatzgruppen herumspielen zu müssen.

Ich mache dasselbe mit Daten, die eine Tabelle haben, die von 10 Jahren in der Vergangenheit bis zu 10 Jahren in der Zukunft reicht (und Stunden des Tages für detailliertere Berichte). Es ist ein guter Trick, Werte für alle Daten abzurufen, auch wenn Ihre "echten" Datentabellen keine Daten für sie enthalten.

Ich habe ein Skript, mit dem ich diese erstelle, so etwas wie (dies ist aus dem Speicher):

drop table numbers; commit;
create table numbers (n integer primary key); commit;
insert into numbers values (0); commit;
insert into numbers select n+1 from numbers; commit;
insert into numbers select n+2 from numbers; commit;
insert into numbers select n+4 from numbers; commit;
insert into numbers select n+8 from numbers; commit;
insert into numbers select n+16 from numbers; commit;
insert into numbers select n+32 from numbers; commit;
insert into numbers select n+64 from numbers; commit;

Die Anzahl der Zeilen verdoppelt sich mit jeder Zeile, sodass nicht viel erforderlich ist, um wirklich große Tabellen zu erstellen.

Ich bin mir nicht sicher, ob ich Ihnen zustimme, dass es wichtig ist, schnell erstellt zu werden, da Sie es nur einmal erstellen. Die Kosten hierfür werden über alle Zugriffe darauf abgeschrieben, was diese Zeit ziemlich unbedeutend macht.

paxdiablo
quelle
jeder begeht; führt zu Meldung 3902, Ebene 16, Status 1, Zeile 1 Die Anforderung COMMIT TRANSACTION hat keine entsprechende BEGIN TRANSACTION.
KM.
@KM, der erste Punkt kann leicht durch Starten einer Transaktion behoben werden (DB / 2, mein DBMS der Wahl, ist normalerweise so konfiguriert, dass Transaktionen automatisch gestartet werden). Und wenn Sie mehr Zeilen möchten, müssen Sie nur weitere Einfügungen hinzufügen. Jeder verdoppelt die Reichweite, so dass es sehr einfach ist, große Zahlen zu erreichen, wenn Sie möchten. Ich bevorzuge es auch, wenn möglich generische SQL-Lösungen anzubieten, anstatt Lösungen auf bestimmte Anbieter zu beschränken.
paxdiablo
2

Hier sind einige zusätzliche Methoden:
Methode 1

IF OBJECT_ID('dbo.Numbers', 'U') IS NOT NULL
    DROP TABLE dbo.Numbers
GO

CREATE TABLE Numbers (Number int NOT NULL PRIMARY KEY);
GO

DECLARE @i int = 1;
INSERT INTO dbo.Numbers (Number) 
VALUES (1),(2);

WHILE 2*@i < 1048576
BEGIN
    INSERT INTO dbo.Numbers (Number) 
    SELECT Number + 2*@i
    FROM dbo.Numbers;
    SET @i = @@ROWCOUNT;
END
GO

SELECT COUNT(*) FROM Numbers AS RowCownt --1048576 rows

Methode 2

IF OBJECT_ID('dbo.Numbers', 'U') IS NOT NULL
    DROP TABLE dbo.Numbers
GO

CREATE TABLE dbo.Numbers (Number int NOT NULL PRIMARY KEY);
GO

DECLARE @i INT = 0; 
INSERT INTO dbo.Numbers (Number) 
VALUES (1);

WHILE @i <= 9
BEGIN
    INSERT INTO dbo.Numbers (Number)
    SELECT N.Number + POWER(4, @i) * D.Digit 
    FROM dbo.Numbers AS N
        CROSS JOIN (VALUES(1),(2),(3)) AS D(Digit)
    ORDER BY D.Digit, N.Number
    SET @i = @i + 1;
END
GO

SELECT COUNT(*) FROM dbo.Numbers AS RowCownt --1048576 rows

Methode 3

IF OBJECT_ID('dbo.Numbers', 'U') IS NOT NULL
    DROP TABLE dbo.Numbers
GO

CREATE TABLE Numbers (Number int identity NOT NULL PRIMARY KEY, T bit NULL);

WITH
    T1(T) AS (SELECT T FROM (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) AS T(T)) --10 rows
   ,T2(T) AS (SELECT A.T FROM T1 AS A CROSS JOIN T1 AS B CROSS JOIN T1 AS C) --1,000 rows
   ,T3(T) AS (SELECT A.T FROM T2 AS A CROSS JOIN T2 AS B CROSS JOIN T2 AS C) --1,000,000,000 rows

INSERT INTO dbo.Numbers(T)
SELECT TOP (1048576) NULL
FROM T3;

ALTER TABLE Numbers
    DROP COLUMN T; 
GO

SELECT COUNT(*) FROM dbo.Numbers AS RowCownt --1048576 rows

Methode 4 , entnommen aus dem Buch Defensive Database Programming von Alex Kuznetsov

IF OBJECT_ID('dbo.Numbers', 'U') IS NOT NULL
    DROP TABLE dbo.Numbers
GO

CREATE TABLE Numbers (Number int NOT NULL PRIMARY KEY);
GO

DECLARE @i INT = 1 ; 
INSERT INTO dbo.Numbers (Number) 
VALUES (1);

WHILE @i < 524289 --1048576
BEGIN; 
    INSERT INTO dbo.Numbers (Number) 
    SELECT Number + @i 
    FROM dbo.Numbers; 
    SET @i = @i * 2 ; 
END
GO

SELECT COUNT(*) FROM dbo.Numbers AS RowCownt --1048576 rows

Methode 5 , entnommen aus Arrays und Listen in SQL Server 2005 und darüber hinaus Artikel von Erland Sommarskog

IF OBJECT_ID('dbo.Numbers', 'U') IS NOT NULL
    DROP TABLE dbo.Numbers
GO

CREATE TABLE Numbers (Number int NOT NULL PRIMARY KEY);
GO

WITH digits (d) AS (
   SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
   SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL
   SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL
   SELECT 0)
INSERT INTO Numbers (Number)
   SELECT Number
   FROM   (SELECT i.d + ii.d * 10 + iii.d * 100 + iv.d * 1000 +
                  v.d * 10000 + vi.d * 100000 AS Number
           FROM   digits i
           CROSS  JOIN digits ii
           CROSS  JOIN digits iii
           CROSS  JOIN digits iv
           CROSS  JOIN digits v
           CROSS  JOIN digits vi) AS Numbers
   WHERE  Number > 0
GO

SELECT COUNT(*) FROM dbo.Numbers AS RowCownt --999999 rows

Zusammenfassung:
Unter diesen 5 Methoden scheint Methode 3 die schnellste zu sein.

Alex
quelle
1

Einige der vorgeschlagenen Methoden basieren auf Systemobjekten (z. B. auf den 'sys.objects'). Sie gehen davon aus, dass diese Systemobjekte genügend Datensätze enthalten, um unsere Zahlen zu generieren.

Ich würde mich nicht auf etwas stützen, das nicht zu meiner Bewerbung gehört und über das ich nicht die volle Kontrolle habe. Beispiel: Der Inhalt dieser Systemtabellen kann sich ändern, die Tabellen sind in einer neuen SQL-Version usw. möglicherweise nicht mehr gültig.

Als Lösung können wir unsere eigene Tabelle mit Datensätzen erstellen. Wir verwenden dann dieses Objekt anstelle dieser systembezogenen Objekte (Tabelle mit allen Zahlen sollte in Ordnung sein, wenn wir den Bereich im Voraus kennen, andernfalls könnten wir uns für dasjenige entscheiden, bei dem die Kreuzverknüpfung durchgeführt wird).

Die CTE-basierte Lösung funktioniert einwandfrei, hat jedoch Einschränkungen in Bezug auf die verschachtelten Schleifen.

Piotr Spikowski
quelle
0

Dies ist ein Umpacken der akzeptierten Antwort - aber auf eine Weise, mit der Sie sie alle miteinander vergleichen können - werden die drei wichtigsten Algorithmen verglichen (und Kommentare erklären, warum andere Methoden ausgeschlossen sind), und Sie können mit Ihrem eigenen Setup arbeiten Sehen Sie, wie sie sich jeweils mit der gewünschten Sequenzgröße verhalten.

SET NOCOUNT ON;

--
-- Set the count of numbers that you want in your sequence ...
--
DECLARE @NumberOfNumbers int = 10000000;
--
--  Some notes on choosing a useful length for your sequence ...
--      For a sequence of  100 numbers -- winner depends on preference of min/max/avg runtime ... (I prefer PhilKelley algo here - edit the algo so RowSet2 is max RowSet CTE)
--      For a sequence of   1k numbers -- winner depends on preference of min/max/avg runtime ... (Sadly PhilKelley algo is generally lowest ranked in this bucket, but could be tweaked to perform better)
--      For a sequence of  10k numbers -- a clear winner emerges for this bucket
--      For a sequence of 100k numbers -- do not test any looping methods at this size or above ...
--                                        the previous winner fails, a different method is need to guarantee the full sequence desired
--      For a sequence of  1MM numbers -- the statistics aren't changing much between the algorithms - choose one based on your own goals or tweaks
--      For a sequence of 10MM numbers -- only one of the methods yields the desired sequence, and the numbers are much closer than for smaller sequences

DECLARE @TestIteration int = 0;
DECLARE @MaxIterations int = 10;
DECLARE @MethodName varchar(128);

-- SQL SERVER 2017 Syntax/Support needed
DROP TABLE IF EXISTS #TimingTest
CREATE TABLE #TimingTest (MethodName varchar(128), TestIteration int, StartDate DateTime2, EndDate DateTime2, ElapsedTime decimal(38,0), ItemCount decimal(38,0), MaxNumber decimal(38,0), MinNumber decimal(38,0))

--
--  Conduct the test ...
--
WHILE @TestIteration < @MaxIterations
BEGIN
    -- Be sure that the test moves forward
    SET @TestIteration += 1;

/*  -- This method has been removed, as it is BY FAR, the slowest method
    -- This test shows that, looping should be avoided, likely at all costs, if one places a value / premium on speed of execution ...

    --
    -- METHOD - Fast looping
    --

    -- Prep for the test
    DROP TABLE IF EXISTS [Numbers].[Test];
    CREATE TABLE [Numbers].[Test] (Number INT NOT NULL);

    -- Method information
    SET @MethodName = 'FastLoop';

    -- Record the start of the test
    INSERT INTO #TimingTest(MethodName, TestIteration, StartDate)
    SELECT @MethodName, @TestIteration, GETDATE()

    -- Run the algorithm
    DECLARE @i INT = 1;
    WHILE @i <= @NumberOfNumbers
    BEGIN
        INSERT INTO [Numbers].[Test](Number) VALUES (@i);
        SELECT @i = @i + 1;
    END;

    ALTER TABLE [Numbers].[Test] ADD CONSTRAINT PK_Numbers_Test_Number PRIMARY KEY CLUSTERED (Number)

    -- Record the end of the test
    UPDATE tt
        SET 
            EndDate = GETDATE()
    FROM #TimingTest tt
    WHERE tt.MethodName = @MethodName
    and tt.TestIteration = @TestIteration

    -- And the stats about the numbers in the sequence
    UPDATE tt
        SET 
            ItemCount = results.ItemCount,
            MaxNumber = results.MaxNumber,
            MinNumber = results.MinNumber
    FROM #TimingTest tt
    CROSS JOIN (
        SELECT COUNT(Number) as ItemCount, MAX(Number) as MaxNumber, MIN(Number) as MinNumber FROM [Numbers].[Test]
    ) results
    WHERE tt.MethodName = @MethodName
    and tt.TestIteration = @TestIteration
*/

/*  -- This method requires GO statements, which would break the script, also - this answer does not appear to be the fastest *AND* seems to perform "magic"
    --
    -- METHOD - "Semi-Looping"
    --

    -- Prep for the test
    DROP TABLE IF EXISTS [Numbers].[Test];
    CREATE TABLE [Numbers].[Test] (Number INT NOT NULL);

    -- Method information
    SET @MethodName = 'SemiLoop';

    -- Record the start of the test
    INSERT INTO #TimingTest(MethodName, TestIteration, StartDate)
    SELECT @MethodName, @TestIteration, GETDATE()

    -- Run the algorithm 
    INSERT [Numbers].[Test] values (1);
--    GO --required

    INSERT [Numbers].[Test] SELECT Number + (SELECT COUNT(*) FROM [Numbers].[Test]) FROM [Numbers].[Test]
--    GO 14 --will create 16384 total rows

    ALTER TABLE [Numbers].[Test] ADD CONSTRAINT PK_Numbers_Test_Number PRIMARY KEY CLUSTERED (Number)

    -- Record the end of the test
    UPDATE tt
        SET 
            EndDate = GETDATE()
    FROM #TimingTest tt
    WHERE tt.MethodName = @MethodName
    and tt.TestIteration = @TestIteration

    -- And the stats about the numbers in the sequence
    UPDATE tt
        SET 
            ItemCount = results.ItemCount,
            MaxNumber = results.MaxNumber,
            MinNumber = results.MinNumber
    FROM #TimingTest tt
    CROSS JOIN (
        SELECT COUNT(Number) as ItemCount, MAX(Number) as MaxNumber, MIN(Number) as MinNumber FROM [Numbers].[Test]
    ) results
    WHERE tt.MethodName = @MethodName
    and tt.TestIteration = @TestIteration
*/
    --
    -- METHOD - Philip Kelley's algo 
    --          (needs tweaking to match the desired length of sequence in order to optimize its performance, relies more on the coder to properly tweak the algorithm)
    --

    -- Prep for the test
    DROP TABLE IF EXISTS [Numbers].[Test];
    CREATE TABLE [Numbers].[Test] (Number INT NOT NULL);

    -- Method information
    SET @MethodName = 'PhilKelley';

    -- Record the start of the test
    INSERT INTO #TimingTest(MethodName, TestIteration, StartDate)
    SELECT @MethodName, @TestIteration, GETDATE()

    -- Run the algorithm
    ; WITH
    RowSet0 as (select 1 as Item union all select 1),              --          2 rows   -- We only have to name the column in the first select, the second/union select inherits the column name
    RowSet1 as (select 1 as Item from RowSet0 as A, RowSet0 as B), --          4 rows
    RowSet2 as (select 1 as Item from RowSet1 as A, RowSet1 as B), --         16 rows
    RowSet3 as (select 1 as Item from RowSet2 as A, RowSet2 as B), --        256 rows
    RowSet4 as (select 1 as Item from RowSet3 as A, RowSet3 as B), --      65536 rows (65k)
    RowSet5 as (select 1 as Item from RowSet4 as A, RowSet4 as B), -- 4294967296 rows (4BB)
    -- Add more RowSetX to get higher and higher numbers of rows    
    -- Each successive RowSetX results in squaring the previously available number of rows
    Tally   as (select row_number() over (order by Item) as Number from RowSet5) -- This is what gives us the sequence of integers, always select from the terminal CTE expression
    -- Note: testing of this specific use case has shown that making Tally as a sub-query instead of a terminal CTE expression is slower (always) - be sure to follow this pattern closely for max performance
    INSERT INTO [Numbers].[Test] (Number)
    SELECT o.Number
    FROM Tally o
    WHERE o.Number <= @NumberOfNumbers

    ALTER TABLE [Numbers].[Test] ADD CONSTRAINT PK_Numbers_Test_Number PRIMARY KEY CLUSTERED (Number)

    -- Record the end of the test
    UPDATE tt
        SET 
            EndDate = GETDATE()
    FROM #TimingTest tt
    WHERE tt.MethodName = @MethodName
    and tt.TestIteration = @TestIteration

    -- And the stats about the numbers in the sequence
    UPDATE tt
        SET 
            ItemCount = results.ItemCount,
            MaxNumber = results.MaxNumber,
            MinNumber = results.MinNumber
    FROM #TimingTest tt
    CROSS JOIN (
        SELECT COUNT(Number) as ItemCount, MAX(Number) as MaxNumber, MIN(Number) as MinNumber FROM [Numbers].[Test]
    ) results
    WHERE tt.MethodName = @MethodName
    and tt.TestIteration = @TestIteration

    --
    -- METHOD - Mladen Prajdic answer
    --

    -- Prep for the test
    DROP TABLE IF EXISTS [Numbers].[Test];
    CREATE TABLE [Numbers].[Test] (Number INT NOT NULL);

    -- Method information
    SET @MethodName = 'MladenPrajdic';

    -- Record the start of the test
    INSERT INTO #TimingTest(MethodName, TestIteration, StartDate)
    SELECT @MethodName, @TestIteration, GETDATE()

    -- Run the algorithm
    INSERT INTO [Numbers].[Test](Number)
    SELECT TOP (@NumberOfNumbers) row_number() over(order by t1.number) as N
    FROM master..spt_values t1 
    CROSS JOIN master..spt_values t2

    ALTER TABLE [Numbers].[Test] ADD CONSTRAINT PK_Numbers_Test_Number PRIMARY KEY CLUSTERED (Number)

    -- Record the end of the test
    UPDATE tt
        SET 
            EndDate = GETDATE()
    FROM #TimingTest tt
    WHERE tt.MethodName = @MethodName
    and tt.TestIteration = @TestIteration

    -- And the stats about the numbers in the sequence
    UPDATE tt
        SET 
            ItemCount = results.ItemCount,
            MaxNumber = results.MaxNumber,
            MinNumber = results.MinNumber
    FROM #TimingTest tt
    CROSS JOIN (
        SELECT COUNT(Number) as ItemCount, MAX(Number) as MaxNumber, MIN(Number) as MinNumber FROM [Numbers].[Test]
    ) results
    WHERE tt.MethodName = @MethodName
    and tt.TestIteration = @TestIteration

    --
    -- METHOD - Single INSERT
    -- 

    -- Prep for the test
    DROP TABLE IF EXISTS [Numbers].[Test];
    -- The Table creation is part of this algorithm ...

    -- Method information
    SET @MethodName = 'SingleInsert';

    -- Record the start of the test
    INSERT INTO #TimingTest(MethodName, TestIteration, StartDate)
    SELECT @MethodName, @TestIteration, GETDATE()

    -- Run the algorithm
    SELECT TOP (@NumberOfNumbers) IDENTITY(int,1,1) AS Number
    INTO [Numbers].[Test]
    FROM sys.objects s1       -- use sys.columns if you don't get enough rows returned to generate all the numbers you need
    CROSS JOIN sys.objects s2 -- use sys.columns if you don't get enough rows returned to generate all the numbers you need

    ALTER TABLE [Numbers].[Test] ADD CONSTRAINT PK_Numbers_Test_Number PRIMARY KEY CLUSTERED (Number)

    -- Record the end of the test
    UPDATE tt
        SET 
            EndDate = GETDATE()
    FROM #TimingTest tt
    WHERE tt.MethodName = @MethodName
    and tt.TestIteration = @TestIteration

    -- And the stats about the numbers in the sequence
    UPDATE tt
        SET 
            ItemCount = results.ItemCount,
            MaxNumber = results.MaxNumber,
            MinNumber = results.MinNumber
    FROM #TimingTest tt
    CROSS JOIN (
        SELECT COUNT(Number) as ItemCount, MAX(Number) as MaxNumber, MIN(Number) as MinNumber FROM [Numbers].[Test]
    ) results
    WHERE tt.MethodName = @MethodName
    and tt.TestIteration = @TestIteration
END

-- Calculate the timespan for each of the runs
UPDATE tt
    SET
        ElapsedTime = DATEDIFF(MICROSECOND, StartDate, EndDate)
FROM #TimingTest tt

--
-- Report the results ...
--
SELECT 
    MethodName, AVG(ElapsedTime) / AVG(ItemCount) as TimePerRecord, CAST(AVG(ItemCount) as bigint) as SequenceLength,
    MAX(ElapsedTime) as MaxTime, MIN(ElapsedTime) as MinTime,
    MAX(MaxNumber) as MaxNumber, MIN(MinNumber) as MinNumber
FROM #TimingTest tt
GROUP by tt.MethodName
ORDER BY TimePerRecord ASC, MaxTime ASC, MinTime ASC
Mike S.
quelle