¿Cuál es la mejor manera de crear y completar una tabla de números?

Resuelto KM. asked hace 15 años • 12 respuestas

He visto muchas formas diferentes de crear y completar una tabla de números. Sin embargo, ¿cuál es la mejor manera de crear y completar uno? Definiendo "mejor" de mayor a menor importancia:

  • Tabla creada con indexación óptima.
  • filas generadas más rápido
  • código simple utilizado para crear y completar

Si no sabes qué es una tabla de números, mira aquí: ¿ Por qué debería considerar usar una tabla de números auxiliar?

KM. avatar Sep 08 '09 20:09 KM.
Aceptado

Aquí hay algunos ejemplos de código tomados de la web y de las respuestas a esta pregunta.

Para cada método, modifiqué el código original para que cada uno use la misma tabla y columna: NumbersTest y Number, con 10,000 filas o lo más cerca posible. Además, he proporcionado enlaces al lugar de origen.

MÉTODO 1 aquí es un método de bucle muy lento desde aquí
un promedio de 13,01 segundos
ejecutado 3 veces eliminado más alto, aquí están los tiempos en segundos: 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

MÉTODO 2 aquí es un bucle mucho más rápido desde aquí
promedio 1.1658 segundos
ejecutado 11 veces eliminado más alto, aquí están los tiempos en segundos: 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

MÉTODO 3 Aquí hay un único INSERTO basado en el código de aquí
, con un promedio de 488,6 milisegundos
ejecutados 11 veces, eliminado más alto, aquí están los tiempos en milisegundos: 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

El MÉTODO 4 aquí es un método de "semi-bucle" desde aquí con un promedio de 348,3 milisegundos (fue difícil lograr una buena sincronización debido al "GO" en el medio del código, se agradecería cualquier sugerencia) se
ejecutó 11 veces y se eliminó el máximo, aquí son tiempos en milisegundos: 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

MÉTODO 5 aquí hay un único INSERTO de la respuesta de Philip Kelley
con un promedio de 92,7 milisegundos
ejecutados 11 veces eliminado más alto, aquí hay tiempos en milisegundos: 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

MÉTODO 6 aquí hay un único INSERTO de la respuesta de Mladen Prajdic
promedio 82,3 milisegundos
ejecutados 11 veces eliminado más alto, aquí están los tiempos en milisegundos: 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

MÉTODO 7 aquí hay un único INSERTO basado en el código de aquí
promedio 56,3 milisegundos
ejecutados 11 veces eliminado más alto, aquí están los tiempos en milisegundos: 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

Después de ver todos estos métodos, me gusta mucho el Método 7, que fue el más rápido y el código también es bastante simple.

KM. avatar Sep 10 '2009 20:09 KM.

Yo uso esto que es increíblemente rápido:

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 avatar Sep 08 '2009 13:09 Mladen Prajdic

Si solo está haciendo esto en SQL Server Management Studio o en SQL Server Management Studio sqlcmd.exe, puede aprovechar el hecho de que el separador de lotes le permite repetir el lote:

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

INSERT INTO Number DEFAULT VALUES;
GO 100000

Esto insertará 100000 registros en la Numberstabla utilizando el valor predeterminado de la siguiente identidad.

Es lento. Se compara con el MÉTODO 1 en la respuesta de @KM., que es el más lento de los ejemplos. Sin embargo, el código es lo más ligero posible. Podría acelerarlo un poco agregando la restricción de clave principal después del lote de inserción.

Bacon Bits avatar Jan 12 '2015 19:01 Bacon Bits

Empiezo con la siguiente plantilla, que se deriva de numerosas impresiones de la rutina de Itzik Ben-Gan:

;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

La cláusula "WHERE N<= 1000000" limita la salida de 1 a 1 millón y se puede ajustar fácilmente al rango deseado.

Dado que se trata de una cláusula CON, se puede incorporar a un INSERT... SELECT... así:

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

Indexar la tabla una vez creada será la forma más rápida de indexarla.

Ah, y yo me referiría a ella como una tabla "Tally". Creo que este es un término común y puedes encontrar muchos trucos y ejemplos buscándolo en Google.

Philip Kelley avatar Sep 08 '2009 14:09 Philip Kelley

Aquí hay una solución en memoria breve y rápida que se me ocurrió utilizando los constructores con valores de tabla introducidos en SQL Server 2008:

Devolverá 1.000.000 de filas, sin embargo, puede agregar/eliminar CROSS JOIN o usar la cláusula TOP para modificar esto.

;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

Tenga en cuenta que esto podría calcularse rápidamente sobre la marcha o (mejor aún) almacenarse en una tabla permanente (simplemente agregue una INTOcláusula después del SELECT Nsegmento) con una clave principal en el Ncampo para mejorar la eficiencia.

John Smith avatar Oct 09 '2016 01:10 John Smith