SpatialDB Advisor
There is a very useful function in PostgreSQL called generate_series that can be used to generate a series of integer numbers from som start value to and end value with an optional step value.
Here is the function and its description from the PostgreSQL help.
| Function | Argument Type | Return Type | Description |
|---|---|---|---|
| generate_series(start, stop) | int or bigint | setof int or setof bigint (same as argument type) | Generate a series of values, from start to stop with a step size of one |
| generate_series(start, stop, step) | int or bigint | setof int or setof bigint (same as argument type) | Generate a series of values, from start to stop with a step size of step |
TYPE t_numbers IS TABLE OF number;
The most efficient way to do this is via a PIPELINED function, so that is what I will code.
create or replace
function generate_series(p_start in pls_integer,
p_end in pls_integer,
p_step in pls_integer := 1 )
Return CODESYS.centroid.t_numbers Pipelined
As
v_i PLS_INTEGER := CASE WHEN p_start IS NULL THEN 1 ELSE p_start END;
v_step PLS_INTEGER := CASE WHEN p_step IS NULL OR p_step = 0 THEN 1 ELSE p_step END;
v_terminating_value PLS_INTEGER := p_start + TRUNC(ABS(p_start-p_end) / abs(v_step) ) * v_step;
Begin
-- Check for impossible combinations
If ( p_start > p_end AND SIGN(p_step) = 1 )
Or
( p_start < p_end AND SIGN(p_step) = -1 ) Then
Return;
End If;
-- Generate integers
LOOP
PIPE ROW ( v_i );
EXIT WHEN ( v_i = v_terminating_value );
v_i := v_i + v_step;
End Loop;
Return;
End generate_series;
/
show errors
Now, to run the tests on the PostgreSQL help page:
Let’s start with a simple, additional, example not on the page.
SQL> select g.column_value as generate_series
2* from table(generate_series(1,5)) g;
Now, let’s execute the ones on the help page.
SQL> select g.column_value as generate_series
2* from table(generate_series(2,4)) g
SQL> select g.column_value as generate_series
2* from table(generate_series(5,1,-2)) g
SQL> select g.column_value as generate_series
2* from table(generate_series(4,3)) g
With one additional:
SQL> select g.column_value as generate_series
2* from table(generate_series(-4,-1,1)) g
And finally.
SQL> select to_char(current_date + sa.column_value,'YYYY-MM-DD') as da
2* from table(generate_series(0,14,7)) sa
Alternative Table Function
Now all this is very good, but there is some debate as to implementing a series of integers in this way.
Vadim Tropashko, in his excellent book, “SQL Design Patterns, The Expert Guide to SQL Programming”, Rampart Press has a whole chapter (2) devoted to “Integer Generators in SQL”. In this chapter, Vadmin presents an coding of a simple Integer generating table function called “Integers”. His coding is as follows.
create or replace function Integers
return t_integers pipelined
As
Begin
Loop
PIPE ROW ( 0 );
End Loop;
Return;
end Integers;
We will now use this function to implement the PostgreSQL help examples above.
Firstly, generating numbers between 1 and 5.
SQL> select rownum as rin
2 from table(Integers)
3* where rownum <= 5
All numbers between 2 and 4.
SQL> select rin
2 from (select rownum as rin
3 from table(Integers)
4 where rownum <= 5)
5* where rin between 2 and 4
Series 5,3,1 using a step of -2.
SQL> select rin
2 from (select 5 + ((rownum-1) * -1) as rin
3 from table(Integers)
4 where rownum < 10)
5 where rin between 1 and 5
6* and mod(rin,ABS(-2)) = 1;
I won’t code the invalid series. So let’s move on to the negative series.
SQL> select rin
2 from (select -1 + (rownum - 1 ) * -1 as rin
3 from table(Integers)
4 where rownum < 10
5 order by 1)
6* where rin between -4 and -1;
And finally.
SQL> select to_char(current_date + sa.rin,'YYYY-MM-DD') as dates
2 from (select (rownum - 1) as rin
3 from table(Integers)
4 where rownum <= 15) sa
5* where mod(rin,7) = 0;
Hierarchical Queries
Vadim goes on in his book to describe the use of hierarchical queries for generating integer series. Here is an example of how to generate all even numbers between 5 and 8.
SQL> select level
2 from dual
3 where level between 5 and 8
4 and MOD(level,2) = 0
5* connect by level <= 10
And the negative series -4,-3,-2,-1 above.
SQL> select level * -1
2 from dual
3 where level between 1 and 4
4 connect by level <= 5
5* order by level desc;
I am a big fan of hierachical queries and have used them a lot in my work but mainly in the area of generating sample data (see other articles in this blog for examples). My personal view is that coding integer ranges with the “connect by level” hierarchical query is simpler to use and code than the Integers() function. However, the “connect by level” usage above does not work in versions of Oracle before 10g. So all you 9i users will have to resort to coding a suitable table function.
Conclusion
Tropashko’s preference (as also Mikito Harakiri) in coding an integer series generation table function is to code one without parameters, such as Integers(), and not like generate_series. The main reason is that he prefers the use of predicates in SQL SELECT statements (such as “rownum < = 1000”) than parameters passed to a function. This is because they are at a “higher abstraction level”, claiming that “programs with predicate expressions are shorter and cleaner”. Because I am a “SQL man”, prefering a single SQL statement to complex PL/SQL, and because I like things to be done “orthogonally”, “theoretically” and via patterns/templates I am tempted to agree with Tropashko. However, when I look at the SQL above, I find the generate_series implementation much, much cleaner and less complicated.
The problem with the Integers() function is that it forces me to “reinvent the wheel” each time I need a series of integers. With simple series the SQL Is not too bad. But more complicated series create much more complicated SQL. Now, many of my uses of SQL result in large SQL statements with can result in many uses of generate_series. To integrate multiple calls to the same series generation can be neatly encapsulated using the WITH construct, but even so it does add, to my way of thinking, unnecessary complicated. And complication usually means lots of errors and longer debugging.
In the end a generate_series function like above allows us to design and algorithm, code and test it and then use it many times without having to remember how to code an integer series every time we need it. This is part of good software engineering: the algorithm is “encapsulated” in a program unit that offers a stable interface. And interfaces are everthing.


















<<Multi-Centroid Shootout >>Fixing failed spatial indexes after import using data pump
Simon,
Neat. I’m tempted to do the same thing for SQL Server since its a pattern I use often especially for things that don’t lend themselves to standard SQL constructs like figuring out length of stays for homeless and hospitalization for claims analysis. I’m just not sure what the performance will be like.
In general I guess the size of the sets I generally use it for shouldn’t impact performance much.
— Regina Nov 12, 01:47 am #
Thanks for commenting: I am a bit fan of your work on "PostGIS":http://postgis.refractions.net.
I have risen to the challenge and ported my version of generate_series to SQL Server 2008 T-SQL. I have tested it and it appears to work correctly.
CREATE FUNCTION [generate_series] ( @p_start INT, @p_end INT, @p_step INT=1 ) RETURNS @Integers TABLE ( [IntValue] INT ) AS BEGIN DECLARE @v_i INT; SET @v_i = CASE WHEN @p_start IS NULL THEN 1 ELSE @p_start END; DECLARE @v_step INT; SET @v_step = CASE WHEN @p_step IS NULL OR @p_step = 0 THEN 1 ELSE @p_step END; DECLARE @v_terminating_value INT; SET @v_terminating_value = @p_start + CONVERT(INT,ABS(@p_start-@p_end) / ABS(@v_step) ) * @v_step; -- Check for impossible combinations IF NOT ( ( @p_start > @p_end AND SIGN(@p_step) = 1 ) OR ( @p_start < @p_end AND SIGN(@p_step) = -1 )) BEGIN WHILE ( 1 = 1 ) BEGIN INSERT INTO @Integers ( [IntValue] ) VALUES ( @v_i ) IF ( @v_i = @v_terminating_value ) BREAK SET @v_i = @v_i + @v_step; END; END; RETURN END GOI hope this is useful. Let me know if you want me to look at a version of the "Integers()" function and how to use it in ordinary SQL.regards
Simon
— Simon Greener Nov 14, 09:59 am #