Technical Article

Generating sequential numbers - the fast way

,

It is often necessary to generate a table with sequential numbers, up to a specified upper limit N. For small N, a simple INSERT in a WHILE loop will do. But, for large N, that solution becomes too slow. This script presents a different approach. It generates sequential numbers from the binary representation of N, as a polynom. In each iteration it doubles the number of records in the resulting table. Thus, the time required to generate sequential numbers decreases dramatically, compared to the WHILE-loop approach.

The script contains:

- a stored procedure with a fixed structure of the resulting table,

- a UDF (MS SQL 2000 only), which is more suitable for use on the fly, and

- stored procedures for performing comparation tests.


CREATE FUNCTION dbo.udf_Table_Of_Numbers_Seq (@from int, @to int)
  RETURNS @res TABLE (num int)
-- Returns a table of sequential numbers within specified limits
-- The result table is populated sequentially
AS
BEGIN
  DECLARE @i int

  SET @i = @from
  WHILE @i <= @to
  BEGIN
    INSERT INTO @res (num) VALUES (@i)
    SET @i = @i + 1
  END

  RETURN
END
GO

CREATE FUNCTION dbo.dec2bin (@n10 bigint) RETURNS varchar(100)
-- Returns a string with the binary representation of @n10
AS
BEGIN

  DECLARE @res varchar(100)
  DECLARE @tmp bigint

  SET @res = ''
  SET @tmp = @n10;
  WHILE @tmp > 0
  BEGIN
    SET @res = CONVERT(char(1), @tmp % 2) + @res
    SET @tmp = @tmp / 2
  END

  RETURN (@res)
END
GO

CREATE FUNCTION dbo.udf_Table_Of_Numbers_Bin (@from bigint, @to bigint)
  RETURNS @res TABLE (num int)
-- Returns a table of sequential numbers within specified limits
-- The result table is populated using the Horner polynom formula
-- P(x) = A[n] * X^n + A[n-1] * X^(n-1) + ... + A[1] * X + A[0] =
--      = ((...(((A[n] * X + A[n-1]) * X) + A[n-2]) ...) * X + A[1]) * X + A[0]
-- The binary representation (for X = 2) is used.
-- In each iteration the number of records in the table is doubled and
-- one more record is added if A[i] = 1.
AS
BEGIN
  -- Returns a 
  DECLARE @total bigint
  DECLARE @current bigint
  DECLARE @n2 varchar(100)

  SET @total = @to - @from + 1
  SET @n2 = dbo.dec2bin(@total) -- A string of 0's na 1' (the binary representation of @total)
  SET @current = 0

  WHILE @current < @total
  BEGIN
    -- Double the number of records in the result table
    INSERT INTO @res (num)
      SELECT @current + num AS num FROM @res
    SET @current = 2 * @current

    -- If the binary digit is 1, add one record
    IF SUBSTRING(@n2, 1, 1) = '1'
    BEGIN
      INSERT INTO @res (num) VALUES (@from + @current)
      SET @current = @current + 1
    END

    SET @n2 = STUFF(@n2, 1, 1, '')
  END -- WHILE

  RETURN
END
GO

CREATE TABLE Seq_Num (num int NOT NULL PRIMARY KEY)
GO
CREATE PROCEDURE Gen_Num_Bin (@from bigint, @to bigint)
AS
BEGIN
  DECLARE @n2 varchar(100)
  DECLARE @total bigint, @current bigint
  SET NOCOUNT ON
  
  DELETE FROM Seq_Num

  SET @total = @to - @from + 1

  -- Convert @total to binary representation
  SET @n2 = ''
  SET @current = @total;
  WHILE @current > 0
  BEGIN
    SET @n2 = CONVERT(char(1), @current % 2) + @n2
    SET @current = @current / 2
  END

  SET @current = 0

  WHILE @current < @total
  BEGIN
    -- Double the number of records in the result table
    INSERT INTO Seq_Num (num)
      SELECT @current + num AS num FROM Seq_Num
    SET @current = 2 * @current

    -- If the binary digit is 1, add one record
    IF SUBSTRING(@n2, 1, 1) = '1'
    BEGIN
      INSERT INTO Seq_Num (num) VALUES (@from + @current)
      SET @current = @current + 1
    END

    SET @n2 = STUFF(@n2, 1, 1, '')
  END -- WHILE
END
GO

CREATE PROCEDURE TEST_SEQ_NUM (@from bigint, @to bigint)
AS
BEGIN
  DECLARE @start_time datetime
  DECLARE @end_time datetime
  DECLARE @elapsed_seq bigint
  DECLARE @elapsed_bin bigint
  DECLARE @total bigint

  SET NOCOUNT ON

  SET @start_time = GETDATE()
  SELECT @total = count(*) FROM dbo.udf_Table_Of_Numbers_Seq(@from, @to)
  SET @end_time = GETDATE()
  SET @elapsed_seq = DATEDIFF(millisecond, @start_time, @end_time)

  SET @start_time = GETDATE()
  SELECT @total = count(*) FROM dbo.udf_Table_Of_Numbers_Bin(@from, @to)
  SET @end_time = GETDATE()
  SET @elapsed_bin = DATEDIFF(millisecond, @start_time, @end_time)

  PRINT 'Generating sequential numbers from ' + CONVERT(varchar, @from) + ' to ' + CONVERT(varchar, @to)
  PRINT 'Sequential: ' + CONVERT(varchar, @elapsed_seq) + ' ms'
  PRINT 'Binary: ' + CONVERT(varchar, @elapsed_bin) + ' ms'
END
GO

CREATE PROCEDURE RUN_ALL_TESTS (@from bigint, @to bigint, @step bigint)
AS
BEGIN
  DECLARE @current bigint

  SET @current = @from
  WHILE @current <= @to
  BEGIN
    EXECUTE TEST_SEQ_NUM 1, @current
    SET @current = @current + @step
  END
END
GO

-- EXECUTE RUN_ALL_TESTS 5000, 100000, 5000

Rate

4.5 (2)

You rated this post out of 5. Change rating

Share

Share

Rate

4.5 (2)

You rated this post out of 5. Change rating