This can be done using SQL using analytic functions.
* Update 1: * Answer updated using the parser function, skipped in the previous version.
* Update 2: * Added final string composition
with p as ( -- Parameter string select replace('2000,2001,2002,2005,2006,2007 and 2010',' and ',',') s from dual ), ex as ( -- Parse string to sequence select to_number( substr( s, decode( level, 1, 1, instr(s,',',1,level-1)+1 ), decode( instr(s,',',1,level), 0, length(s)+1, instr(s,',',1,level) ) - decode( level, 1, 1, instr(s,',',1,level-1)+1 ) ) ) as y from p connect by instr(s,',',1,level-1) > 0 ), period_set as ( select -- Make final string for each interval start y, lag(y) over (order by y) prior_y, max(y) over (partition by 1) max_y, y || (case when is_end > 1 then null else '-' ||end_y end) as interval_string from ( -- For each start find interval end select y, is_start, is_end, lead(y) over (order by y) end_y from ( -- Find if previous/next value differs more then by one. -- If so, mark as start/end select y, nvl(y - prev_y, 100) is_start, nvl(next_y - y, 100) is_end from ( -- Find previous/next value in sequence select y, lag(y) over (order by y) prev_y, lead(y) over (order by y) next_y from ex ) ) where is_start > 1 or is_end > 1 ) where is_start > 1 ) select replace( substr( sys_connect_by_path( decode(y,max_y,'m', null) || interval_string, ',' ),2 ), ',m', ' and ' ) result_str from period_set where connect_by_isleaf = 1 start with prior_y is null connect by prior y = prior_y
SQL Fiddle can be found here .