April 14, 2006 at 9:59 am
Jeff Moden's post using syscolumns gave me inspiration for a simple solution using new features of SQL Server 2005. It is similarly limited to the number of rows in sys.all_columns squared.
DECLARE @first int, @Last int
SET @first = 10
SET @Last = 500000
SELECT TOP (@last - @first + 1) ROW_NUMBER() OVER(ORDER BY a.object_id) + @first - 1 AS Number
FROM master.sys.all_columns a, master.sys.all_columns b
April 14, 2006 at 6:19 pm
David wrote... "If I was to use the static table approach I would probably stick it in its own database so that it wouldn't artificially inflate the backups."
Yep, I know what you mean but I thought your article was awesome... you had the hair to do a comparison on a difficult subject. Well, done.
Matt, thanks for your post... I didn't realize that 2005 had incorporated an Oracle like Row_Number () Over command... now, that's useful! Because of the order by, I have to ask, how's the performance?
--Jeff Moden
Change is inevitable... Change for the better is not.
April 17, 2006 at 2:03 pm
Hi,
I tested my modification to the function for the aforementioned range (5,567720) and got: f2:9986 and f3:10183 in MS. For my range: (211000,339999,10) I can't reliably measure the difference.
I wanted to add steps to the function:
ALTER FUNCTION dbo.fnCrossJoinRange3 (
@first int, --##PARAM @first The lowest value in the range.
@last int, --##PARAM @Last The highest value in the range.
@step int --##PARAM @step The increment value.
)
RETURNS @values TABLE ( value int primary key ) AS
BEGIN
declare @min-2 int
set @min-2 = @first
set @Last = (@last - @first) / @step
set @first = 0
INSERT INTO @values(value)
SELECT @min +
(@step *
( -- raw range
units.value +
(tens.value) +
(hundreds.value ) +
(Thousands.value ) +
(TenThousands.value ) +
(CThousands.value ) +
(Millions.value )
-- raw range
)
)
FROM dbo.Digits units
CROSS JOIN (SELECT value * 10 as value from dbo.Digits WHERE value * 10 <=@last) tens
CROSS JOIN (SELECT value * 100 as value from dbo.Digits WHERE value * 100 <=@last) hundreds
CROSS JOIN (SELECT value * 1000 as value from dbo.Digits WHERE value * 1000 <=@last) Thousands
CROSS JOIN (SELECT value * 10000 as value from dbo.Digits WHERE value * 10000 <=@last) TenThousands
CROSS JOIN (SELECT value * 100000 as value from dbo.Digits WHERE value * 100000 <=@last) CThousands
CROSS JOIN (SELECT value * 1000000 as value from dbo.Digits WHERE value * 1000000 <=@last) Millions
where units.value +
(tens.value ) +
(hundreds.value) +
(Thousands.value ) +
(TenThousands.value ) +
(CThousands.value ) +
(Millions.value )
BETWEEN @first and @Last
RETURN
END
GO
April 19, 2006 at 1:53 pm
Hi,
Based on all the replies I put together the following (just to compare and it was a slow day at the office):
1. A function that might not be the fastest of all (bit better performance than fn_nums) but the most comprehensive. It includes negative ranges (limited to range of an int), a step value and consists of a combination of using a a form of the original cross join function, identity values and incremental inserts.
2. A modification of the original Cross Join Function fnUnionRange using computed columns which improves performance a bit.
The first function consists of 3 functions
create function dbo.fn_zero_to_giga()
returns @basevalues table
(
unit int not null primary key,
deca as ( unit * 10 ),
hecto as ( unit * 100 ),
kilo as ( unit * 1000 ),
dakilo as ( unit * 10000 ),
hkilo as ( unit * 100000 ),
mega as ( unit * 1000000 ),
damega as ( unit * 10000000 ),
hmega as ( unit * 100000000 ),
giga as ( case when unit < 3 then unit * 1000000000 else 0 end )
)
as
begin
insert @basevalues
select 0 as value
union all
select 1
union all
select 2
union all
select 3
union all
select 4
union all
select 5
union all
select 6
union all
select 7
union all
select 8
union all
select 9
return
end
go
create function dbo.fn_int_range_tab_base
(
@p_startnum int = 1,
@p_endnum int = 1,
@p_increment int = 1
)
returns @rangetab table ( idx int identity(-2147483648,1) primary key, val int )
as
begin
-- Validate the start and end numbers
set @p_startnum = isnull( @p_startnum, 1 )
set @p_endnum = isnull( @p_endnum, 1 )
set @p_increment = isnull( @p_increment, 1 )
declare @order smallint
declare @val int
declare @currcnt int
declare @endidx int
declare @adjendnum int
set @order = 1
-- Swap around the values if required and set the order flag
if @p_startnum > @p_endnum begin
set @order = -1
set @adjendnum = @p_startnum
set @p_startnum = @p_endnum
set @p_endnum = @adjendnum
end --if
set @adjendnum = @p_startnum + ( ( ( ( @p_endnum - @p_startnum ) / @p_increment ) ) * @p_increment )
set @endidx = -2147483648 + ( ( ( @adjendnum - @p_startnum ) / @p_increment ) + 1 )
insert @rangetab
select 1 as value
from ( select unit as value from dbo.fn_zero_to_giga() where -2147483648 + unit <= @endidx ) as u,
( select deca as value from dbo.fn_zero_to_giga() where -2147483648 + deca <= @endidx ) as da,
( select hecto as value from dbo.fn_zero_to_giga() where -2147483648 + hecto <= @endidx ) as h,
( select kilo as value from dbo.fn_zero_to_giga() where -2147483648 + kilo <= @endidx ) as k,
( select dakilo as value from dbo.fn_zero_to_giga() where -2147483648 + dakilo <= @endidx ) as dak,
( select hkilo as value from dbo.fn_zero_to_giga() where -2147483648 + hkilo <= @endidx ) as hk
where -2147483648 + u.value + da.value + h.value + k.value + dak.value + hk.value + 1 <= @endidx
set @currcnt = @@rowcount
while @currcnt < ( ( ( @adjendnum - @p_startnum ) / @p_increment ) + 1 ) begin
if @@identity + @currcnt < @endidx begin
-- Avoid using the where clause on large ranges
insert @rangetab
select val
from @rangetab
end --if
else begin
insert @rangetab
select val
from @rangetab
where idx + @currcnt < @endidx
end --else
set @currcnt = @currcnt + @@rowcount
end --else
if @p_increment > 1 begin
if @order = 1 begin
set @val = @p_startnum - @p_increment
end --if
else begin
set @val = @adjendnum + @p_increment
end --else
set @p_increment = @p_increment * @order
update @rangetab
set @val = val = @val + @p_increment
end --if
return
end
go
create function dbo.fn_int_range_tab
(
@p_startnum int = 1,
@p_endnum int = 1,
@p_increment int = 1
)
returns table
as
return
(
select case isnull( @p_increment, 1 ) when 1 then ( idx - -2147483648 ) + @p_startnum else val end as val
from dbo.fn_int_range_tab_base( @p_startnum, @p_endnum, @p_increment )
)
go
The second function uses the fn_zero_to_giga function above to make it a bit more readable and to improve performance using computed columns. It could be modified to use a 10 row permanent table instead of a table variable to give a bit of a performance increase.
create function dbo.fnUnionRangeNew1
(
@first int ,
@last int
)
returns table
as
return
(
select u.value + da.value + h.value + k.value + dak.value + hk.value + m.value as value
from ( select unit as value from dbo.fn_zero_to_giga() where unit <= @Last ) as u,
( select deca as value from dbo.fn_zero_to_giga() where deca <= @Last ) as da,
( select hecto as value from dbo.fn_zero_to_giga() where hecto <= @Last ) as h,
( select kilo as value from dbo.fn_zero_to_giga() where kilo <= @Last ) as k,
( select dakilo as value from dbo.fn_zero_to_giga() where dakilo <= @Last ) as dak,
( select hkilo as value from dbo.fn_zero_to_giga() where hkilo <= @Last ) as hk,
( select mega as value from dbo.fn_zero_to_giga() where mega <= @Last ) as m
where u.value + da.value + h.value + k.value + dak.value + hk.value + m.value between @first and @Last
)
go
Viewing 4 posts - 16 through 18 (of 18 total)
You must be logged in to reply to this topic. Login to reply