Declare @start intSET @start = 12000Declare @End intSET @end = 13000SELECT @start + t.number FROM Master.dbo.spt_Values t WHERE t.Type = 'P' AND t.Number BETWEEN 0 AND (@end - @start)
CREATE FUNCTION dbo.TallyRange/**************************************************************************************** Purpose: Given a starting integer and an ending integer, generate the inclusive range of integers between those two values in the same "direction" as those two values. Note to DBA's: There is no recursion or other non set-based forms of RBAR in this code. The function itself is a high speed "inline table valued function" recognized for it's very high performance and will generally only be used once in any given query in the FROM clause as if it were an actual table. It causes no reads, no writes, no log growth, and no performance problems. Performance: On most machines, this method takes < 1 second of CPU time to generate a million rows of joinable incremental integers and close to 0 milliseconds to generate 8,000 rows for use in such areas as splitting VARCHAR(8000). Usage: SELECT N FROM dbo.TallyRange(@Start, @End) --where @Start and @End are INT values. Programmer's Notes. 1. Will always count in direction of start value to end value. 2. In any case, you should use an ORDER BY in the outer query to quarantee the order you want. 3. 99.999% of the time, I'll use a permanent Tally table of 11,000 rows because it meets/exceeds most of my needs. This function was developed because of a need to generate some very large numbers (1 Billion was the max) and this method does NOT cause the log file to grow whereas more than one x-join of tables does. The initial run of multiple x-joins to get to a Billion rows caused the log file of the working DB to grow well over 40GB. This function does not. Credits: Original concept by Itzik Ben-Gan and company with the alternate ideas made by many including the following (in alphabetical order): Jeff Moden (myself) Lynn Pettis (see article at http://www.sqlservercentral.com/articles/T-SQL/67899/) Matt Miller Michael Valentine Jones Peter Larrson R. Barry Young Revision History: Rev 00 - 20 Dec 2008 - Jeff Moden - Initial creation. Rev 01 - 21 Dec 2008 - Jeff Moden - Made inputs "reversible" just to be "forgiving". Rev 02 - 08 May 2009 - Jeff Moden - Changed from Base 2 to Base 10 notation for slight performance - gain, made changes ("E" notation for CTE names) for the sake of - explainability, and added "credits" for those involved in a race - on the forum using different methods that exposed the slight - performance gain. Rev 03 - 20 Jun 2009 - Jeff Moden - Remove a particular ANSI join type. Rev 04 - 23 Sep 2009 - Jeff Moden - Added Lynn Pettis' link to his article to credits. Rev 05 - 26 Sep 2009 - Jeff Moden - Moved column aliases out of the SELECTs to the CTE name - declarations and removed "AS" from the alias names for the - joined CTE references to further condense the code.****************************************************************************************/--===== Declare I/O parameters ( @Start INT, @End INT )RETURNS TABLE AS RETURN ( --======= Use multiple "cascaded" joined CTE's to generate numbers up to 10E16. -- Note that the SIGN function is used to allow the inputs "reversible" -- and ABS is used to always have a positive number for the TOP number -- of rows to generate. The +1 is to makeup for the subtraction loss... -- for example, counting from 0 to 10 is actually 11 counts, not 10 as -- some would expect. WITH E01(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1), -- 10 or 10E01 rows E02(N) AS (SELECT 1 FROM E01 a, E01 b), -- 100 or 10E02 rows E04(N) AS (SELECT 1 FROM E02 a, E02 b), -- 10,000 or 10E04 rows E08(N) AS (SELECT 1 FROM E04 a, E04 b), --100,000,000 or 10E08 rows E16(N) AS (SELECT 1 FROM E08 a, E08 b), --10E16 or more rows than you'll EVER need cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) * SIGN(@End - @Start) FROM E16) --===== Final SELECT uses a TOP "reflection" back on the CTE's to limit rows -- calculated by the CTE's to only those needed. SELECT TOP (ABS(@End - @Start) + 1) N + @Start + SIGN(@Start - @End) AS N FROM cteTally )
DECLARE @Start INT, @End INT; SELECT @Start = 12000, @End = 13000; SELECT N FROM dbo.TallyRange(@Start,@End)
WITH E01(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1), -- 10 or 10E01 rows E02(N) AS (SELECT 1 FROM E01 a INNER JOIN E01 b ON a.N = b.N), -- 100 or 10E02 rows E04(N) AS (SELECT 1 FROM E02 a INNER JOIN E02 b ON a.N = b.N), -- 10,000 or 10E04 rows E08(N) AS (SELECT 1 FROM E04 a INNER JOIN E04 b ON a.N = b.N), --100,000,000 or 10E08 rows E16(N) AS (SELECT 1 FROM E08 a INNER JOIN E08 b ON a.N = b.N), --10E16 or more rows than you'll EVER need cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) * SIGN(@End - @Start) FROM E16) --===== Final SELECT uses a TOP "reflection" back on the CTE's to limit rows -- calculated by the CTE's to only those needed. SELECT TOP (ABS(@End - @Start) + 1) N + @Start + SIGN(@Start - @End) AS N FROM cteTally