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?
Antworten:
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.
quelle
CROSS JOIN sys.objects s3
im vorherigen Kommentar vorgeschlagenen hinzufügen , erhalten Sie 438.976 Zeilen (76 * 76 * 76).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
quelle
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
Numbers
mit 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.
quelle
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.
quelle
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/
quelle
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
INTO
Klausel nach demSELECT N
Segment hinzu) mit einem Primärschlüssel auf demN
Feld gespeichert werden kann, um die Effizienz zu verbessern.quelle
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.
quelle
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.
quelle
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.
quelle
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.
quelle
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
quelle