I need your help to solve this problem in PL/SQL.
I'm learning PL/SQL and unfortunately I'm not very good at it and I need your help to solve this using a cursor.
I also created a DbFiddle for Oracle 21c because that's what I use and I apologize for not knowing how to separate text from code or how to edit a question correctly.
I put my attempt in the DbFiddle
https://dbfiddle.uk/uTFrwjsM
I just want to convert the script that goes in Sql Server to Oracle.
It is for educational purpose only.
Maybe you can help me please.
Thanks!
I will also put the data here
create table bus(
bus_id number,
arrival_time number,
capacity number);
insert into bus values(1,2,1);
insert into bus values(2,4,10);
insert into bus values(3,7,2);
commit;
select *
from bus;
create table passenger(
passenger_id number,
arrival_time number);
insert into passenger values(11,1);
insert into passenger values(12,1);
insert into passenger values(13,5);
insert into passenger values(14,6);
insert into passenger values(15,7);
commit;
Result
bus_id capacity b_arrival spot passenger_id p_arrival
1 1 2 1 11 1
2 10 4 1 12 1
2 10 4 2 NULL NULL
2 10 4 3 NULL NULL
2 10 4 4 NULL NULL
2 10 4 5 NULL NULL
2 10 4 6 NULL NULL
2 10 4 7 NULL NULL
2 10 4 8 NULL NULL
2 10 4 9 NULL NULL
2 10 4 10 NULL NULL
3 2 7 1 13 5
3 2 7 2 14 6
Thank you so much
Boneist asked for a solution using a single SELECT statement. Here is one, using not one but two MATCH_RECOGNIZE clauses! The basic idea is, SQL doesn't let us juggle two input streams, so I put the input into one stream and then figure out which rows to scrunch together.
select * from (
select BUS_ID, CAPACITY, SPOT, PASSENGER_ID,
coalesce(b.arrival_time, p.arrival_time) arrival_time
from bus b
cross apply(
select level spot from dual connect by level <= capacity
)
full join passenger p on 1=0
)
match_recognize (
order by arrival_time, PASSENGER_ID, spot
measures classifier() cl, match_number() mn,
case classifier() when 'B' then count(b.*) else count(p.*) end rn
all rows per match
pattern(p+ b+)
define b as bus_id is not null,
p as passenger_id is not null
)
match_recognize(
order by mn, rn, cl
measures first(bus_id) bus_id, first(capacity) capacity,
first(arrival_time) b_arrival, first(spot) spot,
last(passenger_id) passenger_id, last(arrival_time) p_arrival
pattern(a+)
define a as (mn, rn, 'B') = (( first(mn), first(rn), first(cl) ))
)
Based on your SQL Server code of:
DECLARE #id INT
DECLARE #arrival int
DECLARE cur CURSOR FOR
SELECT p.passenger_id, P.arrival_time
FROM dbo.passenger p
ORDER BY p.arrival_time, p.passenger_id
OPEN cur
FETCH NEXT FROM cur INTO #id, #arrival;
WITH cte_bus (bus_id, capacity, b_arrival, spot)
AS (
SELECT bus_id, capacity, arrival_time AS b_arrival, 1 AS spot FROM bus
UNION ALL
SELECT bus_id, capacity, b_arrival, spot+1 FROM cte_bus
WHERE spot < capacity
)
SELECT *, NULL AS passenger_id, NULL AS p_arrival
INTO #tmp
FROM cte_bus ORDER BY bus_id, spot
WHILE ##FETCH_STATUS = 0 BEGIN
UPDATE TOP (1) #tmp SET passenger_id = #id, p_arrival = #arrival WHERE b_arrival>=#arrival AND passenger_id IS NULL
FETCH NEXT FROM cur INTO #id, #arrival
END
CLOSE cur
DEALLOCATE cur
SELECT * FROM #tmp t
order by 1
I think what you need is to create a global temporary table (GTT), which is a permanent table but holds data on a per-session basis (n.b. the default is to delete data when a commit is issued after you have populated the GTT):
create global temporary table tmp (bus_id number,
capacity number,
b_arrival number,
spot number,
passenger_id number,
p_arrival number);
and the following does what you want:
BEGIN
-- initial population of the GTT
insert into tmp (bus_id,
capacity,
b_arrival,
spot)
WITH cte_bus (bus_id, capacity, b_arrival, spot)
AS (SELECT bus_id, capacity, arrival_time AS b_arrival, 1 AS spot FROM bus
UNION ALL
SELECT bus_id, capacity, b_arrival, spot+1 FROM cte_bus
WHERE spot < capacity)
SELECT bus_id,
capacity,
b_arrival,
spot
FROM cte_bus
ORDER BY bus_id,
spot;
-- loop through the passengers and assign them to a bus
for rec in (SELECT p.passenger_id, P.arrival_time
FROM passenger p
ORDER BY p.arrival_time, p.passenger_id)
loop
-- update the tmp GTT
-- find the lowest row that hasn't already got a passenger assigned
-- and update that row to assign the current passenger to it.
merge into tmp tgt
using (select bus_id,
capacity,
b_arrival,
spot,
row_number() over (order by bus_id, spot) rn
from tmp
where b_arrival >= rec.arrival_time
and passenger_id is null) src
on (tgt.bus_id = src.bus_id
and tgt.spot = src.spot
and src.rn = 1)
when matched then
update set tgt.passenger_id = rec.passenger_id,
tgt.p_arrival = rec.arrival_time;
END LOOP;
END;
/
See this dbfiddle for results.
I still think it would be possible to output the results just using a single SELECT statement (probably via use of the MODEL clause), but I lack the wherewithal to do that.
Another PL/SQL solution that works more like an old COBOL program from the '70s / '80s: just "merging" (in a non-SQL sense) two result sets.
create or replace force type t_bus_passenger as object(
bus_id number,
b_arrival number,
capacity number,
spot number,
passenger_id number,
p_arrival number
)
/
create or replace type tt_bus_passenger as table of t_bus_passenger
/
create or replace function bus_passenger_schedule
return tt_bus_passenger pipelined is
cursor cur_bp is
select t_bus_passenger(
BUS_ID, ARRIVAL_TIME, CAPACITY, SPOT, null, null
) obj
from bus, lateral(
select level spot from dual
connect by level <= capacity
)
order by arrival_time, spot;
cursor cur_passenger is
select * from passenger
order by arrival_time, passenger_id;
rec_passenger cur_passenger%rowtype;
begin
open cur_passenger;
fetch cur_passenger into rec_passenger;
for rec_bp in cur_bp loop
if rec_bp.obj.b_arrival >= rec_passenger.arrival_time then
rec_bp.obj.passenger_id := rec_passenger.passenger_id;
rec_bp.obj.p_arrival := rec_passenger.arrival_time;
fetch cur_passenger into rec_passenger;
end if;
pipe row(rec_bp.obj);
end loop;
close cur_passenger;
return;
end;
/
select * from bus_passenger_schedule();
Related
below is my code that will trigger when a new row in Trip is inserted. Then it will update on the column totalTripMade in the Driver table.
CREATE OR REPLACE TRIGGER UPDATE_TOTAL_TRIPS_MADE
AFTER INSERT
ON TRIP
FOR EACH ROW
DECLARE
tripsDone NUMBER(6);
driverL# NUMBER(12);
BEGIN
--Find the L# of the Driver performing the INSERT into the Trip table
SELECT D.L# INTO driverL#
FROM DRIVER D
WHERE D.L# =: NEW.L#;
--Find the number of trips done by the driver (Error occured here)
SELECT COUNT(T#) INTO tripsDone
FROM TRIP T
WHERE NEW.L# =: driverL#;
--Then update the totaltripmade by the driver
UPDATE
DRIVER
SET
totalTripMade = tripsDone
WHERE
L# = driverL#;
END UPDATE_TOTAL_TRIPS_MADE;
/
However, there is compilation error due to not able to query TRIP until the trigger is completed.
So, I tried changing the select count statement to like that:
SELECT COUNT(*) INTO tripsDone
FROM TRIP T
WHERE driverL# =: NEW.L#;
But it did not work too. I am not sure how can I work around to get the total number of rows in the table Trip with the driverL# that triggered the trigger.
For solving this problem, you should probably NOT use a trigger. Just write a query for counting the "trips". Example:
Tables
create table driver (
id number generated always as identity start with 1 primary key
, surname varchar2( 50 )
-- , ttm number -- total trips made: redundant!
) ;
create table trip (
id number generated always as identity start with 1000 primary key
, from_ varchar2( 50 )
, to_ varchar2( 50 )
, driver number references driver( id )
) ;
Test data: DRIVER
insert into driver( surname )
select
'driver_' || to_char( level )
from dual
connect by level <= 10 ;
select * from driver ;
ID SURNAME
1 driver_1
2 driver_2
3 driver_3
4 driver_4
5 driver_5
6 driver_6
7 driver_7
8 driver_8
9 driver_9
10 driver_10
Test data: TRIP
begin
for i in 1 .. 10
loop
insert into trip ( from_, to_, driver ) values (
'departure_' || to_char( i )
, 'arrival_' || to_char( i )
, mod( i, 3 ) + 1
) ;
end loop ;
commit ;
end ;
/
SQL> select * from trip ;
ID FROM_ TO_ DRIVER
1000 departure_1 arrival_1 2
1001 departure_2 arrival_2 3
1002 departure_3 arrival_3 1
1003 departure_4 arrival_4 2
1004 departure_5 arrival_5 3
1005 departure_6 arrival_6 1
1006 departure_7 arrival_7 2
1007 departure_8 arrival_8 3
1008 departure_9 arrival_9 1
1009 departure_10 arrival_10 2
Query: find the "trip count"
select D.id, D.surname, count(*) as trips_made
from driver D
join trip T on D.id = T.driver
group by D.id, D.surname ;
-- result
ID SURNAME TRIPS_MADE
1 driver_1 3
2 driver_2 4
3 driver_3 3
However, if you want to learn about triggers, you will find that a "row level trigger" will give you "mutating table" errors. When using a "table level" trigger, you cannot reference :NEW and :OLD. What you need is a COMPOUND TRIGGER. You can find numerous discussions about this problem. Apart from consulting the Oracle PL/SQL documentation, it may be worth your while looking that the examples written by T Hall eg here, and S Feuerstein eg here.
The dbfiddle here contains the code used for this answer, and a third example table called DRIVERV2 plus examples of the above mentioned trigger types (and typical error messages).
Let me start my question by setting up my scenario.
I have a test2 table which contains only 2 fields: productid and productlife, I would like to explicitly list all the years along with the products
for example,
With product A, I would like to list
Y15|Y16|Y17|Y18|Y19, A
and for product B, using the same rule, I should get
Y18|Y19, B
My sql does not produce the result I am looking for:
SELECT
(
SELECT listagg("Year",'|') within GROUP (
ORDER BY "Year") "Prefix"
FROM
(
SELECT 'Y'
||(TO_CHAR(SYSDATE,'yy')-LEVEL) "Year"
FROM dual
CONNECT BY level<=r.productlife
)
) "Prefix", productid
FROM TEST2 r
How should it be corrected? I would think the field productlife in each record will control the level in the statement but it does not seem to do so..
Please advise.
Below is the script to create my example for your convenience.
CREATE TABLE "TEST2"
( productid VARCHAR2(20 BYTE),
productlife NUMBER
) ;
Insert into TEST2 (productid,productlife) values ('A',5);
Insert into TEST2 (productid,productlife) values ('B',2);
Thanks!
If it doesn't absolutely have to use connect by, another approach might be to write it explicitly as a function:
with
function list_years(n number) return varchar2 as
end_year date := trunc(sysdate,'YYYY');
start_year date := add_months(end_year, (n*-12));
y date;
years_list varchar(200);
begin
for i in reverse 1..n loop
y := add_months(sysdate, -12 * i);
years_list := years_list || to_char(y,'"Y"YY"|"');
end loop;
return rtrim(years_list,'|');
end list_years;
select productid
, productlife
, list_years(productlife) as prefix
from test2
/
DBFiddle
This query will do it, but I feel like there must be a better way:
SELECT t.productid, s.yr
FROM test2 t
INNER JOIN (SELECT ROWNUM RN, TO_CHAR(SYSDATE, 'YY')-ROWNUM+1 YR
FROM dual d
CONNECT BY level <= (SELECT max(productlife)
FROM test2)) s ON t.productlife >= s.rn
ORDER BY t.productid, s.yr;
Here is a SQLFiddle for you: SQLFiddle
I have a table with one column containing different integers.
For each integer in the table I would like to duplicate it as the number of digits -
For example:
12345 (5 digits):
1. 12345
2. 12345
3. 12345
4. 12345
5. 12345
I thought doing it using with recursion t (...) as () but I didn't manage, since I don't really understand how it works and what is happening "behind the scenes.
I don't want to use insert because I want it to be scalable and automatic for as many integers as needed in a table.
Any thoughts and an explanation would be great.
The easiest way is to join to a table with numbers from 1 to n in it.
SELECT n, x
FROM yourtable
JOIN
(
SELECT day_of_calendar AS n
FROM sys_calendar.CALENDAR
WHERE n BETWEEN 1 AND 12 -- maximum number of digits
) AS dt
ON n <= CHAR_LENGTH(TRIM(ABS(x)))
In my example I abused TD's builtin calendar, but that's not a good choice, as the optimizer doesn't know how many rows will be returned and as the plan must be a Product Join it might decide to do something stupid. So better use a number table...
Create a numbers table that will contain the integers from 1 to the maximum number of digits that the numbers in your table will have (I went with 6):
create table numbers(num int)
insert numbers
select 1 union select 2 union select 3 union select 4 union select 5 union select 6
You already have your table (but here's what I was using to test):
create table your_table(num int)
insert your_table
select 12345 union select 678
Here's the query to get your results:
select ROW_NUMBER() over(partition by b.num order by b.num) row_num, b.num, LEN(cast(b.num as char)) num_digits
into #temp
from your_table b
cross join numbers n
select t.num
from #temp t
where t.row_num <= t.num_digits
I found a nice way to perform this action. Here goes:
with recursive t (num,num_as_char,char_n)
as
(
select num
,cast (num as varchar (100)) as num_as_char
,substr (num_as_char,1,1)
from numbers
union all
select num
,substr (t.num_as_char,2) as num_as_char2
,substr (num_as_char2,1,1)
from t
where char_length (num_as_char2) > 0
)
select *
from t
order by num,char_length (num_as_char) desc
Is there any way to select the numbers (integers) that are included between two numbers with SQL in Oracle; I don't want to create PL/SQL procedure or function.
For example I need to get the numbers between 3 and 10. The result will be the values 3,4,5,6,7,8,9,10.
Thx.
This trick with Oracle's DUAL table also works:
SQL> select n from
2 ( select rownum n from dual connect by level <= 10)
3 where n >= 3;
N
----------
3
4
5
6
7
8
9
10
The first thing I do when I create a new database is to create and populate some basic tables.
One is a list of all integers between -N and N, another is a list of dates 5 years in the past through 10 years in the future (a scheduled job can continue creating these as needed, going forward) and the last is a list of all hours throughout the day. For example, the inetgers:
create table numbers (n integer primary key);
insert into numbers values (0);
insert into numbers select n+1 from numbers; commit;
insert into numbers select n+2 from numbers; commit;
insert into numbers select n+4 from numbers; commit;
insert into numbers select n+8 from numbers; commit;
insert into numbers select n+16 from numbers; commit;
insert into numbers select n+32 from numbers; commit;
insert into numbers select n+64 from numbers; commit;
insert into numbers select n+128 from numbers; commit;
insert into numbers select n+256 from numbers; commit;
insert into numbers select n+512 from numbers; commit;
insert into numbers select n+1024 from numbers; commit;
insert into numbers select n+2048 from numbers; commit;
insert into numbers select n+4096 from numbers; commit;
insert into numbers select n+8192 from numbers; commit;
insert into numbers select -n from numbers where n > 0; commit;
This is for DB2/z which has automatic transaction start which is why it seems to have naked commits.
Yes, it takes up a (minimal) space but it makes queries much easier to write, simply by selecting values from those tables. It's also very portable across pretty much any SQL-based DBMS.
Your particular query would then be a simple:
select n from numbers where n >=3 and n <= 10;
The hour figures and date ranges are quite useful for the sort of reporting applications we work on. It allows us to create zero entries for those hours of the day (or dates) which don't have any real data so that, instead of (where there's no data on the second of the month):
Date | Quantity
-----------+---------
2009-01-01 | 7
2009-01-03 | 27
2009-01-04 | 6
we can instead get:
Date | Quantity
-----------+---------
2009-01-01 | 7
2009-01-02 | 0
2009-01-03 | 27
2009-01-04 | 6
SQL> var N_BEGIN number
SQL> var N_END number
SQL> exec :N_BEGIN := 3; :N_END := 10
PL/SQL procedure successfully completed.
SQL> select :N_BEGIN + level - 1 n
2 from dual
3 connect by level <= :N_END - :N_BEGIN + 1
4 /
N
----------
3
4
5
6
7
8
9
10
8 rows selected.
This uses the same trick as Tony's. Note that when you are using SQL*Plus 9, you have to make this query an inline view as Tony showed you. In SQL*Plus 10 or higher, the above is sufficient.
Regards,
Rob.
You can use the MODEL clause for this.
SELECT c1 from dual
MODEL DIMENSION BY (1 as rn) MEASURES (1 as c1)
RULES ITERATE (7)
(c1[ITERATION_NUMBER]=ITERATION_NUMBER+7)
this single line query will help you,
select level lvl from dual where level<:upperbound and
level >:lowerbound connect by level<:limt
For your case:
select level lvl from dual where level<10 and level >3 connect by level<11
let me know if any clarification.
One way to generate numbers from range is to use XMLTABLE('start to end'):
SELECT TO_NUMBER(column_value) integer_value
FROM XMLTABLE('3 to 10');
I added TO_NUMBER because COLUMN_VALUE is a string
DBFiddle Demo
Or you can use Between
Select Column1 from dummy_table where Column2 Between 3 and 10
Gary, to show the result that he explained, the model query will be:
SELECT c1
FROM DUAL
MODEL DIMENSION BY (1 as rn)
MEASURES (1 as c1)
RULES ITERATE (8)
(c1[ITERATION_NUMBER]=ITERATION_NUMBER+3)
ORDER BY rn
;)
I always use:
SELECT (LEVEL - 1) + 3 as result
FROM Dual
CONNECT BY Level <= 8
Where 3 is the start number and 8 is the number of "iterations".
This is a late addition. But the solution seems to be more elegant and easier to use.
It uses a pipelined function that has to be installed once:
CREATE TYPE number_row_type AS OBJECT
(
num NUMBER
);
CREATE TYPE number_set_type AS TABLE OF number_row_type;
CREATE OR REPLACE FUNCTION number_range(p_start IN PLS_INTEGER, p_end IN PLS_INTEGER)
RETURN number_set_type
PIPELINED
IS
out_rec number_row_type := number_row_type(NULL);
BEGIN
FOR i IN p_start .. p_end LOOP
out_rec.num := i;
pipe row(out_rec);
END LOOP;
END number_range;
/
Then you can use it like this:
select * from table(number_range(1, 10));
NUM
---
1
2
3
4
5
6
7
8
9
10
The solution is Oracle specific.
I just did a table valued function to do this in SQL server, if anyone is interested, this works flawlessly.
CREATE FUNCTION [dbo].[NumbersBetween]
(
#StartN int,
#EndN int
)
RETURNS
#NumberList table
(
Number int
)
AS
BEGIN
WHILE #StartN <= #EndN
BEGIN
insert into #NumberList
VALUES (#StartN)
set #StartN = #StartN + 1
END
Return
END
GO
If you run the query: "select * from dbo.NumbersBetween(1,5)" (w/o the quotes of course) the result will be
Number
-------
1
2
3
4
5
I want to share an usefull query that converts a string of comma and '-' separated list of numbers into a the equivalent expanded list of numbers:
An example that converts '1,2,3,50-60' into
1
2
3
50
51
...
60
select distinct * from (SELECT (LEVEL - 1) + mini as result FROM (select REGEXP_SUBSTR (value, '[^-]+', 1, 1)mini ,nvl(REGEXP_SUBSTR (value, '[^-]+', 1, 2),0) maxi from (select REGEXP_SUBSTR (value, '[^,]+', 1, level) as value from (select '1,2,3,50-60' value from dual) connect by level <= length(regexp_replace(value,'[^,]*'))+1)) CONNECT BY Level <= (maxi-mini+1)) order by 1 asc;
You may use it as a view and parametrize the '1,2,3,50-60' string
create table numbers (value number);
declare
x number;
begin
for x in 7 .. 25
loop
insert into numbers values (x);
end loop;
end;
/
In addition to the answers already provided, it is possible to combine the listagg function with connect by to obtain the result in the format mentioned in the question. See a code example below:
SELECT
DBMS_LOB.SUBSTR(LISTAGG(S.INTEGERS,',' ) WITHIN GROUP (ORDER BY S.INTEGERS), 300,1) RESULT
FROM
(SELECT
INTEGERS
FROM
( SELECT ROWNUM INTEGERS FROM DUAL CONNECT BY LEVEL <= 10)
WHERE
INTEGERS >= 3
) S;
OutPut:
SQL>
RESULT
----------------
3,4,5,6,7,8,9,10
Is there any way to select the numbers (integers) that are included between two numbers with SQL in Oracle; I don't want to create PL/SQL procedure or function.
For example I need to get the numbers between 3 and 10. The result will be the values 3,4,5,6,7,8,9,10.
Thx.
This trick with Oracle's DUAL table also works:
SQL> select n from
2 ( select rownum n from dual connect by level <= 10)
3 where n >= 3;
N
----------
3
4
5
6
7
8
9
10
The first thing I do when I create a new database is to create and populate some basic tables.
One is a list of all integers between -N and N, another is a list of dates 5 years in the past through 10 years in the future (a scheduled job can continue creating these as needed, going forward) and the last is a list of all hours throughout the day. For example, the inetgers:
create table numbers (n integer primary key);
insert into numbers values (0);
insert into numbers select n+1 from numbers; commit;
insert into numbers select n+2 from numbers; commit;
insert into numbers select n+4 from numbers; commit;
insert into numbers select n+8 from numbers; commit;
insert into numbers select n+16 from numbers; commit;
insert into numbers select n+32 from numbers; commit;
insert into numbers select n+64 from numbers; commit;
insert into numbers select n+128 from numbers; commit;
insert into numbers select n+256 from numbers; commit;
insert into numbers select n+512 from numbers; commit;
insert into numbers select n+1024 from numbers; commit;
insert into numbers select n+2048 from numbers; commit;
insert into numbers select n+4096 from numbers; commit;
insert into numbers select n+8192 from numbers; commit;
insert into numbers select -n from numbers where n > 0; commit;
This is for DB2/z which has automatic transaction start which is why it seems to have naked commits.
Yes, it takes up a (minimal) space but it makes queries much easier to write, simply by selecting values from those tables. It's also very portable across pretty much any SQL-based DBMS.
Your particular query would then be a simple:
select n from numbers where n >=3 and n <= 10;
The hour figures and date ranges are quite useful for the sort of reporting applications we work on. It allows us to create zero entries for those hours of the day (or dates) which don't have any real data so that, instead of (where there's no data on the second of the month):
Date | Quantity
-----------+---------
2009-01-01 | 7
2009-01-03 | 27
2009-01-04 | 6
we can instead get:
Date | Quantity
-----------+---------
2009-01-01 | 7
2009-01-02 | 0
2009-01-03 | 27
2009-01-04 | 6
SQL> var N_BEGIN number
SQL> var N_END number
SQL> exec :N_BEGIN := 3; :N_END := 10
PL/SQL procedure successfully completed.
SQL> select :N_BEGIN + level - 1 n
2 from dual
3 connect by level <= :N_END - :N_BEGIN + 1
4 /
N
----------
3
4
5
6
7
8
9
10
8 rows selected.
This uses the same trick as Tony's. Note that when you are using SQL*Plus 9, you have to make this query an inline view as Tony showed you. In SQL*Plus 10 or higher, the above is sufficient.
Regards,
Rob.
You can use the MODEL clause for this.
SELECT c1 from dual
MODEL DIMENSION BY (1 as rn) MEASURES (1 as c1)
RULES ITERATE (7)
(c1[ITERATION_NUMBER]=ITERATION_NUMBER+7)
this single line query will help you,
select level lvl from dual where level<:upperbound and
level >:lowerbound connect by level<:limt
For your case:
select level lvl from dual where level<10 and level >3 connect by level<11
let me know if any clarification.
One way to generate numbers from range is to use XMLTABLE('start to end'):
SELECT TO_NUMBER(column_value) integer_value
FROM XMLTABLE('3 to 10');
I added TO_NUMBER because COLUMN_VALUE is a string
DBFiddle Demo
Or you can use Between
Select Column1 from dummy_table where Column2 Between 3 and 10
Gary, to show the result that he explained, the model query will be:
SELECT c1
FROM DUAL
MODEL DIMENSION BY (1 as rn)
MEASURES (1 as c1)
RULES ITERATE (8)
(c1[ITERATION_NUMBER]=ITERATION_NUMBER+3)
ORDER BY rn
;)
I always use:
SELECT (LEVEL - 1) + 3 as result
FROM Dual
CONNECT BY Level <= 8
Where 3 is the start number and 8 is the number of "iterations".
This is a late addition. But the solution seems to be more elegant and easier to use.
It uses a pipelined function that has to be installed once:
CREATE TYPE number_row_type AS OBJECT
(
num NUMBER
);
CREATE TYPE number_set_type AS TABLE OF number_row_type;
CREATE OR REPLACE FUNCTION number_range(p_start IN PLS_INTEGER, p_end IN PLS_INTEGER)
RETURN number_set_type
PIPELINED
IS
out_rec number_row_type := number_row_type(NULL);
BEGIN
FOR i IN p_start .. p_end LOOP
out_rec.num := i;
pipe row(out_rec);
END LOOP;
END number_range;
/
Then you can use it like this:
select * from table(number_range(1, 10));
NUM
---
1
2
3
4
5
6
7
8
9
10
The solution is Oracle specific.
I just did a table valued function to do this in SQL server, if anyone is interested, this works flawlessly.
CREATE FUNCTION [dbo].[NumbersBetween]
(
#StartN int,
#EndN int
)
RETURNS
#NumberList table
(
Number int
)
AS
BEGIN
WHILE #StartN <= #EndN
BEGIN
insert into #NumberList
VALUES (#StartN)
set #StartN = #StartN + 1
END
Return
END
GO
If you run the query: "select * from dbo.NumbersBetween(1,5)" (w/o the quotes of course) the result will be
Number
-------
1
2
3
4
5
I want to share an usefull query that converts a string of comma and '-' separated list of numbers into a the equivalent expanded list of numbers:
An example that converts '1,2,3,50-60' into
1
2
3
50
51
...
60
select distinct * from (SELECT (LEVEL - 1) + mini as result FROM (select REGEXP_SUBSTR (value, '[^-]+', 1, 1)mini ,nvl(REGEXP_SUBSTR (value, '[^-]+', 1, 2),0) maxi from (select REGEXP_SUBSTR (value, '[^,]+', 1, level) as value from (select '1,2,3,50-60' value from dual) connect by level <= length(regexp_replace(value,'[^,]*'))+1)) CONNECT BY Level <= (maxi-mini+1)) order by 1 asc;
You may use it as a view and parametrize the '1,2,3,50-60' string
create table numbers (value number);
declare
x number;
begin
for x in 7 .. 25
loop
insert into numbers values (x);
end loop;
end;
/
In addition to the answers already provided, it is possible to combine the listagg function with connect by to obtain the result in the format mentioned in the question. See a code example below:
SELECT
DBMS_LOB.SUBSTR(LISTAGG(S.INTEGERS,',' ) WITHIN GROUP (ORDER BY S.INTEGERS), 300,1) RESULT
FROM
(SELECT
INTEGERS
FROM
( SELECT ROWNUM INTEGERS FROM DUAL CONNECT BY LEVEL <= 10)
WHERE
INTEGERS >= 3
) S;
OutPut:
SQL>
RESULT
----------------
3,4,5,6,7,8,9,10