Technical Article

User-Created GENERATE_SERIES() Function for Older SQL Server Versions

,

SQL Server, has evolved significantly over time, incorporating a myriad of features with each subsequent version. One such feature, introduced in SQL Server 2022, is the GENERATE_SERIES() function. Mirroring PostgreSQL's function of the same name, SQL Server's GENERATE_SERIES() creates a series of numbers within a defined range, incrementing by a specific step value. It's an efficient way to generate a sequence of numbers or dates, aiding in various scenarios such as test data creation, time series analysis, and more.

For those who operate on versions of SQL Server prior to 2022. This is where the user-created GENERATE_SERIES() function comes into play. It offers similar functionality to its newer counterpart, making it accessible to those still using older SQL Server versions.

The user-written GENERATE_SERIES function is an adaptationupdate of Jeff Moden's well-regarded "dbo.fnTally" function, providing equivalent functionality in environments where the SQL Server 2022's GENERATE_SERIES() isn't available.

This GENERATE_SERIES function takes three parameters: @Start, @Stop, and @Step. The function then generates a series of numbers beginning from @Start, ending at @Stop, and increasing by @Step for each subsequent value. The @Step parameter defaults to 1 if not explicitly provided.

The inner workings of this function truly demonstrate its ingenuity. The function constructs a tally table, a table containing a sequence of integers, using a series of cross joins on a static 16-row table H2 with itself, resulting in a massive tally table denoted as H8. This table can hold a sequence of more than 4 billion integers.

An ordered sequence of numbers is then generated using the ROW_NUMBER() function on this tally table. This ordered sequence (N) is utilized to create a new series of numbers by adding @Start and the product of @Step and (N-1). This new sequence, referred to as value, effectively embodies the series of numbers the function aims to generate.

The function concludes with a SELECT TOP clause, limiting the number of returned rows to the absolute difference between @Stop and @Start, divided by the absolute value of @Step, plus one. This ensures the generated series starts at @Start, stops at @Stop, and increments by @Step.

In essence, this user-written GENERATE_SERIES() function is a practical and efficient means of generating a series of numbers in older SQL Server versions. It showcases the platform's extensibility and the SQL Server community's dedication to continually enhancing and adapting the platform's functionalities. It's a shining example of user ingenuity addressing a significant functionality gap in earlier SQL Server versions, bringing valuable tools to a wider audience of developers.

One of the distinct advantages of the user-written GENERATE_SERIES function, designed for older versions of SQL Server, is its ease of transition when migrating to SQL Server 2022 or higher versions that support the built-in GENERATE_SERIES() function.

The structure and syntax of both functions are intentionally made identical, providing a seamless transition when upgrading to newer versions of SQL Server. This was a conscious design decision made to future-proof the function, enabling developers to take advantage of newer SQL Server features when ready without worrying about major code modifications.

So, how does this transition work? It's remarkably simple. To convert the user-written GENERATE_SERIES function to use SQL Server's built-in GENERATE_SERIES() function, all that's required is to remove the dbo. prefix from the function call in the code. Both functions take the same parameters and produce the same output, making the transition a one-step process, as shown below:

From:

SELECT * FROM dbo.GENERATE_SERIES(@Start, @Stop, @Step);

To:

SELECT * FROM GENERATE_SERIES(@Start, @Stop, @Step);

This simplicity drastically reduces the time and resources needed to refactor code during database migration, eliminating potential errors and the need for extensive testing that typically comes with larger codebase modifications.

Moreover, by using the user-written GENERATE_SERIES function in older versions of SQL Server, developers can enjoy the benefits of a powerful sequence-generating tool while also preparing for future migrations. This smart design choice helps bridge the gap between different SQL Server versions, allowing developers to take advantage of new features effortlessly and ensuring that databases can remain modern, efficient, and robust.

In conclusion, the user-written GENERATE_SERIES function for SQL Server exemplifies a strategic approach to database function design, balancing current functionality needs with a vision for easy future upgrades. It demonstrates the ingenuity of the SQL Server community and their forward-thinking approach to continually enhancing the platform's capabilities.

References:  Create a Tally Function (fnTally)

GENERATE_SERIES (Transact-SQL)

CREATE FUNCTION [dbo].[GENERATE_SERIES]
(
    @Start bigint, 
    @Stop  bigint, 
    @Step  bigint = 1
)
RETURNS TABLE WITH SCHEMABINDING AS 
 RETURN WITH
  H2(N) AS ( SELECT 1 
               FROM (VALUES
                     (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    )V(N))            --16^2 or 256 rows
, H4(N) AS (SELECT 1 FROM H2 a, H2 b) --16^4 or 65,536 rows
, H8(N) AS (SELECT 1 FROM H4 a, H4 b) --16^8 or 4,294,967,296 rows
, Tally AS (
            SELECT N = ROW_NUMBER() OVER (ORDER BY N)
              FROM H8
           )
, StepTally AS (
            SELECT value = @Start + (@Step * (N - 1))
              FROM Tally
           )
SELECT TOP(ABS(@Stop - @Start) / ABS(@Step) + 1) value
  FROM StepTally
  WHERE @Step <> 0 
;

Rate

You rated this post out of 5. Change rating

Share

Share

Rate

You rated this post out of 5. Change rating