I am referring to this stackoverflow answer:
How can I select from list of values in SQL Server
How could something similar be done in Oracle?
I've seen the other answers on this page that use UNION and although this method technically works, it's not what I would like to use in my case.
So I would like to stay with syntax that more or less looks like a comma-separated list of values.
UPDATE regarding the create type table answer:
I have a table:
CREATE TABLE BOOK
( "BOOK_ID" NUMBER(38,0)
)
I use this script but it does not insert any rows to the BOOK table:
create type number_tab is table of number;
INSERT INTO BOOK (
BOOK_ID
)
SELECT A.NOTEBOOK_ID FROM
(select column_value AS NOTEBOOK_ID from table (number_tab(1,2,3,4,5,6))) A
;
Script output:
TYPE number_tab compiled
Warning: execution completed with warning
But if I use this script it does insert new rows to the BOOK table:
INSERT INTO BOOK (
BOOK_ID
)
SELECT A.NOTEBOOK_ID FROM
(SELECT (LEVEL-1)+1 AS NOTEBOOK_ID FROM DUAL CONNECT BY LEVEL<=6) A
;
You don't need to create any stored types, you can evaluate Oracle's built-in collection types.
select distinct column_value from table(sys.odcinumberlist(1,1,2,3,3,4,4,5))
If you are seeking to convert a comma delimited list of values:
select column_value
from table(sys.dbms_debug_vc2coll('One', 'Two', 'Three', 'Four'));
-- Or
select column_value
from table(sys.dbms_debug_vc2coll(1,2,3,4));
If you wish to convert a string of comma delimited values then I would recommend Justin Cave's regular expression SQL solution.
Starting from Oracle 12.2, you don't need the TABLE function, you can directly select from the built-in collection.
SQL> select * FROM sys.odcinumberlist(5,2,6,3,78);
COLUMN_VALUE
------------
5
2
6
3
78
SQL> select * FROM sys.odcivarchar2list('A','B','C','D');
COLUMN_VALUE
------------
A
B
C
D
There are various ways to take a comma-separated list and parse it into multiple rows of data. In SQL
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select '1,2,3,a,b,c,d' str from dual
3 )
4 select regexp_substr(str,'[^,]+',1,level) element
5 from x
6* connect by level <= length(regexp_replace(str,'[^,]+')) + 1
SQL> /
ELEMENT
----------------------------------------------------
1
2
3
a
b
c
d
7 rows selected.
Or in PL/SQL
SQL> create type str_tbl is table of varchar2(100);
2 /
Type created.
SQL> create or replace function parse_list( p_list in varchar2 )
2 return str_tbl
3 pipelined
4 is
5 begin
6 for x in (select regexp_substr( p_list, '[^,]', 1, level ) element
7 from dual
8 connect by level <= length( regexp_replace( p_list, '[^,]+')) + 1)
9 loop
10 pipe row( x.element );
11 end loop
12 return;
13 end;
14
15 /
Function created.
SQL> select *
2 from table( parse_list( 'a,b,c,1,2,3,d,e,foo' ));
COLUMN_VALUE
--------------------------------------------------------------------------------
a
b
c
1
2
3
d
e
f
9 rows selected.
You can do this:
create type number_tab is table of number;
select * from table (number_tab(1,2,3,4,5,6));
The column is given the name COLUMN_VALUE by Oracle, so this works too:
select column_value from table (number_tab(1,2,3,4,5,6));
Hi it is also possible for Strings with XML-Table
SELECT trim(COLUMN_VALUE) str FROM xmltable(('"'||REPLACE('a1, b2, a2, c1', ',', '","')||'"'));
[Deprecated] - Just to add for the op,
The issue with your second code it seems to be that you haven't defined there your "number_tab" type.
AS :
CREATE type number_tab is table of number;
SELECT a.notebook_id FROM (
SELECT column_value AS notebook_id FROM table (number_tab(1,2,3,4,5,6) ) ) a;
INSERT INTO BOOK ( BOOK_ID )
SELECT a.notebook_id FROM (
SELECT column_value AS notebook_id FROM table (number_tab(1,2,3,4,5,6) ) ) a;
DROP type number_tab ;
Sorry, I couldn't reproduce your error, could you send us the version of oracle used and the same code used for the procedure in the first instance?, that could help. All the best.
Related
I am new to Oracle and not sure if there are any inbuilt functions to do this task.
I have a column that contains Product_ID's separated by comma.
Product_ID
123,234,546,789,487
I am passing a list of Product_ID's separated by a comma as varchar2.
so, I am passing "234,789" as varchar2.
I want to find if 234 and 789 exist in that column and if exists get that row.
How can I do this?
If you want to check that all the values in your input list are included in the column then you can use:
SELECT *
FROM table_name t
WHERE EXISTS (
WITH input ( value ) AS (
SELECT '123,789' FROM DUAL -- Your input value
)
SELECT 1
FROM input
WHERE ','||t.product_id||',' LIKE '%,' || REGEXP_SUBSTR( value, '[^,]+', 1, LEVEL ) || ',%'
CONNECT BY LEVEL <= REGEXP_COUNT( value, '[^,]+' )
HAVING COUNT(*) = REGEXP_COUNT( value, '[^,]+' )
)
Which, for the sample data:
CREATE TABLE table_name ( Product_ID ) AS
SELECT '123,234,546,789,487' FROM DUAL
Outputs:
| PRODUCT_ID |
| :------------------ |
| 123,234,546,789,487 |
If you want to check that at least one value in your input list is in the column then you can use the same query without the line containing the HAVING clause.
db<>fiddle here
Here's one way - making the comma-separated number lists into JSON arrays so that we can split them using json_table, then re-aggregating as nested tables so that we can compare with the submultiset operator:
create type table_of_pid as table of number;
/
with
sample_data (product_id) as (
select '123,234,546,789,487' from dual union all
select '333,444,555,666,888' from dual
)
, user_input (product_list) as (
select '234,789' from dual
)
select *
from sample_data
where ( select cast(collect(pid) as table_of_pid) as input_pid
from user_input cross apply
json_table('[' || product_list || ']', '$[*]'
columns pid number path '$')
)
submultiset
( select cast(collect(pid) as table_of_pid) as input_pid
from json_table('[' || product_id || ']', '$[*]'
columns pid number path '$')
)
;
PRODUCT_ID
-------------------
123,234,546,789,487
Your inputs violate First Normal Form, the most basic sanity requirement in a relational database. If the data was in normal form, you wouldn't need any of the JSON trickery. Still, the aggregation into collection and the submultiset comparison would be the correct approach even if the data was already in normal form.
It is a bad idea to store comma-separated values into a single column.
One option to do what you asked for is in the following example; read comments within code. Note that - for large tables - performance WILL suffer.
SQL> set ver off
SQL>
SQL> with
2 test (product_id) as
3 -- your sample table
4 (select '123,234,546,789,487' from dual union all
5 select '111,222,333' from dual
6 ),
7 test_split as
8 -- you have to split it into rows
9 (select product_id,
10 regexp_substr(product_id, '[^,]+', 1, column_value) val
11 from test
12 cross join table(cast(multiset(select level from dual
13 connect by level <= regexp_count(product_id, ',') + 1
14 ) as sys.odcinumberlist))
15 ),
16 parameter_split as
17 -- split input parameter into rows as well
18 (select regexp_substr('&&par_id', '[^,]+', 1, level) val
19 from dual
20 connect by level <= regexp_count('&&par_id', ',') + 1
21 )
22 -- join split values, return the result
23 select distinct t.product_id
24 from test_split t join parameter_split p on p.val = t.val;
Enter value for par_id: 123,546
PRODUCT_ID
-------------------
123,234,546,789,487
SQL> undefine par_id
SQL> /
Enter value for par_id: 333
PRODUCT_ID
-------------------
111,222,333
SQL>
I want to write a PL/SQL function which can be used in a variety of queries, particular the subqueries of a WITH clause. The tricky part is that I want the function to both receive and return a one-column TABLE (or CURSOR) of information.
Details: imagine that this function just sorts a list of employee IDs according to some very complicated criteria. We'd have this:
My function is called SORT_EMPLOYEES
My function takes a 1-column table of employee IDs (emp_id) as input.
This input type is probably a TABLE type EMP_T_TYPE.
This return type is probably also a TABLE type EMP_T_TYPE.
So far so good, I hope.
Now, I think that I can use the output of the function with the TABLE operator pretty much anywhere; e.g.:
WITH
wanted_emps AS (...), -- find employees we want, as table of emp_id
ranked_emps AS (
SELECT rownum() as rank, emp_id
FROM TABLE(SORT_EMPLOYEES(...???...))
),
...
The problem is: how can I get the list of employees from 'wanted_emps' and make it the input of SORT_EMPLOYEES? What goes in the "...???..." above? Is this even possible?
Please note that I want this to be used from plain SQL, especially from subqueries of WITH clauses as shown above -- not from PL/SQL. Thanks!
Here is an example of how to COLLECT values into a collection which can then be passed to a FUNCTION that returns another collection:
Oracle Setup:
CREATE TYPE numbers_table AS TABLE OF NUMBER;
CREATE TABLE test_data ( grp, value ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 1, 2 FROM DUAL UNION ALL
SELECT 1, 3 FROM DUAL UNION ALL
SELECT 2, 4 FROM DUAL UNION ALL
SELECT 2, 5 FROM DUAL UNION ALL
SELECT 3, 6 FROM DUAL;
Query:
WITH FUNCTION square( i_numbers IN numbers_table ) RETURN numbers_table
IS
p_numbers numbers_table := numbers_table();
p_count PLS_INTEGER;
BEGIN
IF i_numbers IS NULL THEN
p_count := 0;
ELSE
p_count := i_numbers.COUNT;
END IF;
p_numbers.EXTEND( p_count );
FOR i IN 1 .. p_count LOOP
p_numbers(i) := i_numbers(i) * i_numbers(i);
END LOOP;
RETURN p_numbers;
END;
collected_rows ( grp, grouped_values ) AS (
SELECT grp,
CAST(
COLLECT( value ORDER BY value )
AS numbers_table
)
FROM test_data
GROUP BY grp
)
SELECT c.grp,
t.COLUMN_VALUE AS squared_value
FROM collected_rows c
CROSS JOIN
TABLE( square( c.grouped_values ) ) t;
Output:
GRP | SQUARED_VALUE
--: | ------------:
1 | 1
1 | 4
1 | 9
2 | 16
2 | 25
3 | 36
db<>fiddle here
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
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Comma Separated values in Oracle
Folks, I know its an exteremly bad idea, and this table needs to be normalized. But unfortunately I cannot change the schema.
We have a table in Oracle DB , as
id|value | other_columns
----------------------------
1|a,b,c |some values
Can we create a view with something like
id|value
-----------
1|a
1|b
1|c
Thanks in advance for help.
I don't think this is an exact duplicate of the question referenced in the close votes. Similar yes, but not the same.
Not exactly beautiful, but:
CREATE OR REPLACE VIEW your_view AS
SELECT tt.ID, SUBSTR(value, sp, ep-sp) split, other_col1, other_col2...
FROM (SELECT id, value
, INSTR(','||value, ',', 1, L) sp -- 1st posn of substr at this level
, INSTR(value||',', ',', 1, L) ep -- posn of delimiter at this level
FROM tt JOIN (SELECT LEVEL L FROM dual CONNECT BY LEVEL < 20) q -- 20 is max #substrings
ON LENGTH(value)-LENGTH(REPLACE(value,','))+1 >= L
) qq JOIN tt on qq.id = tt.id;
where tt is your table.
Works for csv values longer than 1 or null. The CONNECT BY LEVEL < 20 is arbitrary, adjust for your situation.
To illustrate:
SQL> CREATE TABLE tt (ID INTEGER, c VARCHAR2(20), othercol VARCHAR2(20));
Table created
SQL> INSERT INTO tt VALUES (1, 'a,b,c', 'val1');
1 row inserted
SQL> INSERT INTO tt VALUES (2, 'd,e,f,g', 'val2');
1 row inserted
SQL> INSERT INTO tt VALUES (3, 'a,f', 'val3');
1 row inserted
SQL> INSERT INTO tt VALUES (4,'aa,bbb,cccc', 'val4');
1 row inserted
SQL> CREATE OR REPLACE VIEW myview AS
2 SELECT tt.ID, SUBSTR(c, sp, ep-sp+1) splitval, othercol
3 FROM (SELECT ID
4 , INSTR(','||c,',',1,L) sp, INSTR(c||',',',',1,L)-1 ep
5 FROM tt JOIN (SELECT LEVEL L FROM dual CONNECT BY LEVEL < 20) q
6 ON LENGTH(c)-LENGTH(REPLACE(c,','))+1 >= L
7 ) q JOIN tt ON q.id =tt.id;
View created
SQL> select * from myview order by 1,2;
ID SPLITVAL OTHERCOL
--------------------------------------- -------------------- --------------------
1 a val1
1 b val1
1 c val1
2 d val2
2 e val2
2 f val2
2 g val2
3 a val3
3 f val3
4 aa val4
4 bbb val4
4 cccc val4
12 rows selected
SQL>
I did something similiar to this in the past. You need to create a function that accepts an input string and a separator and returns a dataset. If separator is ommited, then comma is assumed.
First create a new type that represents a table of strings:
create or replace type varcharTableType as table of varchar2(255);
/
Then create this function:
create or replace function splitString(
allValues in varchar2,
delim in varchar2 default ','
)
return varcharTableType
as
str varchar2(255) := allValues || delim;
pos number;
dataset varcharTableType := varcharTableType();
begin
loop
pos := instr(str, delim);
exit when (nvl(pos, 0) = 0);
dataset.extend;
dataset(dataset.count) := ltrim(rtrim(substr(str, 1, pos - 1)));
str := substr(str, pos + length(delim));
end loop;
return dataset;
end;
/
Finally, call as:
select *
from table(cast(splitString('a,b,c') as varcharTableType));
COLUMN_VALUE
---------------
a
b
c
3 rows selected
To answer your specific case, you simply need to create a view that joins your table with this function table, as:
create or replace view splitView as
select yourTable.id, s.column_value as value
from yourTable,
table(cast(splitString(yourTable.value) as varcharTableType)) s;
select * from splitView;
id value
---- ---------------
1 a
1 b
1 c
3 rows selected
I am not sure if this last query will work, as I don't have an Oracle machine right now, but hopefully should help you.
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