Below is a method using CROSS JOIN in batches of 10M. This loaded 2 billion rows in about 6 minutes on my desktop machine.
--create and load 2B row table using cross join CTE source
CREATE TABLE dbo.TableHere(
ID int NOT NULL
);
DECLARE
@TargetRowCount int = 2000000000
, @RowsInserted int = 0;
WHILE @RowsInserted < @TargetRowCount
BEGIN
WITH
t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
,t1k AS (SELECT 0 AS n FROM t10 AS a CROSS JOIN t10 AS b CROSS JOIN t10 AS c)
,t10m AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS num FROM t1k AS a
CROSS JOIN t1k AS b CROSS JOIN t10 AS c)
INSERT INTO dbo.TableHere WITH(TABLOCKX)
SELECT num + @RowsInserted
FROM t10m;
SET @RowsInserted += @@ROWCOUNT;
DECLARE @CurrentTimestampString varchar(1000) = FORMAT(SYSDATETIME(),'yyyy-MM-dd HH:mm:ss');
RAISERROR('%s: %d of %d rows inserted', 0, 0, @CurrentTimestampString, @RowsInserted, @TargetRowCount) WITH NOWAIT;
END;
GO
Consider creating a permanent tally table with incremental values. Not only will this improve performance of test data generation, a materialized tally table can be reused to facilitate a variety of other common tasks, such as generating incremental value ranges, datetime sequences, etc.
Leveraging a tally table improved performance by 30% (4.5 minutes) on my machine by avoiding repeating the cross join every iteration. EDIT: Down to 2.5 minutes after processor upgrade (Intel i9-12900K processor).
SET NOCOUNT ON;
--create and load 10M row tally table
DROP TABLE IF EXISTS dbo.Tally;
CREATE TABLE dbo.Tally(
Num int NOT NULL CONSTRAINT PK_Tally PRIMARY KEY
);
WITH
t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
,t1k AS (SELECT 0 AS n FROM t10 AS a CROSS JOIN t10 AS b CROSS JOIN t10 AS c)
,t10m AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS num FROM t1k AS a
CROSS JOIN t1k AS b CROSS JOIN t10 AS c)
INSERT INTO dbo.Tally WITH(TABLOCKX) (Num)
SELECT num
FROM t10m;
ALTER INDEX PK_Tally ON dbo.Tally REBUILD WITH (FILLFACTOR=100);
GO
--create and load 2B row table using tally table source
CREATE TABLE dbo.TableHere(
ID int NOT NULL
);
DECLARE
@TargetRowCount int = 2000000000
, @RowsInserted int = 0;
WHILE @RowsInserted < @TargetRowCount
BEGIN
INSERT INTO dbo.TableHere WITH(TABLOCKX) (ID)
SELECT Num + @RowsInserted
FROM dbo.Tally;
SET @RowsInserted += @@ROWCOUNT;
DECLARE @CurrentTimestampString varchar(1000) = FORMAT(SYSDATETIME(),'yyyy-MM-dd HH:mm:ss');
RAISERROR('%s: %d of %d rows inserted', 0, 0, @CurrentTimestampString, @RowsInserted, @TargetRowCount) WITH NOWAIT;
END;
GO