Use an array in a SELECT statement? - sql

I want to use this array in 'select from..where..in(YYY)' statement.
I don't want to iterate through array values, I want to use it whole in my select statement.
Unfortunately, I found only how to iterate it:
1 declare
2 type array is table of varchar2(30) index by binary_integer;
3 a array;
4 procedure p( array_in array )
5 is
6 begin
7 for i in 1..array_in.count loop
8 dbms_output.put_line( array_in(i) );
9 end loop;
10 end;
11 begin
12 a(1) := 'Apple';
13 a(2) := 'Banana';
14 a(3) := 'Pear';
15 p( a );
16 end;
17 /

You can do this by creating a function returning your array. Then you can use it into a select:
Create external types and function
create or replace type t_array is table of varchar2(30);
create or replace function list_of_fruits
return t_array
is
l_ t_array:=t_array();
begin
l_.extend(); l_(l_.COUNT) := 'Apple';
l_.extend(); l_(l_.COUNT) := 'Banana';
l_.extend(); l_(l_.COUNT) := 'Pear';
return l_;
end list_of_fruits;
/
And here is how to use it:
select * from (
select 'Peter' this_and_that from dual
union all select 'Joy' from dual
union all select 'God' from dual
union all select 'Pear' from dual
union all select 'Man' from dual
)
where this_and_that in (
select column_value from (table( list_of_fruits() ))
);
The trick here is to use the table() function to make a SQL usable list for your select; also difficult for me was to discover the name of that column_value... which is some built-in constant from Oracle: how do you guess that?

You can use oracle defined collection to achieve this as well. Please see below and example.
declare
a sys.odcivarchar2list;
begin
a := sys.odcivarchar2list('Apple','Banana','Pear');
for r in ( SELECT m.column_value m_value
FROM table(a) m )
loop
dbms_output.put_line (r.m_value);
end loop;
end;

Related

pl/sql function to order string from varchar

function that take two parameters, the first to be a string and the second is the order (Asc or Desc) and the returned output to be ordering the first string as per the second parameter.
IN : dgtak
OUT: adgkt
Tried this but doesn't seem to work
CREATE OR REPLACE FUNCTION order_string(my_string IN VARCHAR2)
RETURN VARCHAR2 IS
ret_string VARCHAR2(4000);
BEGIN
SELECT LISTAGG(regexp_substr(my_string, '\w', 1, level), '') WITHIN
GROUP(
ORDER BY 1)
INTO ret_string
FROM dual
CONNECT BY regexp_substr(my_string, '\w', 1, level) IS NOT NULL;
RETURN ret_string;
END;
select order_string('dgtak') as RESULT from dual;
Here's one option:
SQL> create or replace function order_string (par_string in varchar2, par_order in varchar2)
2 return varchar2
3 is
4 retval varchar2(100);
5 begin
6 with temp (val) as
7 -- split PAR_STRING to rows
8 (select substr(par_string, level, 1)
9 from dual
10 connect by level <= length(par_string)
11 )
12 -- aggregate characters back in ascending or descending order
13 select case when par_order = 'Asc' then listagg(val, '') within group (order by val asc)
14 when par_order = 'Desc' then listagg(val, '') within group (order by val desc)
15 else null
16 end
17 into retval
18 from temp;
19
20 return retval;
21 end;
22 /
Function created.
Testing:
SQL> select order_string('dfag', 'Asc') result_asc,
2 order_string('dfag', 'Desc') result_desc
3 from dual;
RESULT_ASC RESULT_DESC
-------------------- --------------------
adfg gfda
SQL>
Just for fun, here's a procedural version. It has more lines of code than the SQL version but in my tests it's slightly faster.
create or replace function order_string
( p_string varchar2
, p_reverse varchar2 default 'N' )
return varchar2
as
pragma udf;
type letter_tt is table of number index by varchar2(1);
letters letter_tt := letter_tt();
letter varchar2(1);
sorted_string long;
string_length integer := length(p_string);
begin
-- Store all characters of p_string as indices of array:
for i in 1..string_length loop
letter := substr(p_string,i,1);
if letters.exists(letter) then
letters(letter) := letters(letter) +1;
else
letters(letter) := 1;
end if;
end loop;
-- Loop through array appending each array index to sorted_string
for i in indices of letters loop
for r in 1..letters(i) loop
sorted_string := sorted_string || i;
end loop;
end loop;
if p_reverse = 'Y' then
select reverse(sorted_string) into sorted_string from dual;
end if;
return sorted_string;
end order_string;
I've used the 21c indices of loop iterator, but you can write a conventional loop in earlier versions. You might also use two alternative loops for ascending and descending order in place of my hack.

How to tokenize semicolon separated column value to pass to IF statement in a function in Oracle DB

I have a table called 'config' and when I query it in following manner:
SELECT value FROM config WHERE property = 'SPECIAL_STORE_ID'
its response will be: 59216;131205;76707;167206 //... (1)
I want to tokenize the above values using semicolon as the delimiter and then use them in a user-defined Function's IF statement to compare, something like this:
IF in_store_id exists in (<delimited response from (1) above>)//...(2)
THEN do some stuff
where in_store_id is the parameter passed-in to the function
Is this possible to do as one-liner in (2) above ?
I'm on Oracle 12c
One-liner? I don't think so, but - if you're satisfied with something like this, fine.
SQL> select * From config;
VALUE PROPERTY
-------------- ----------------
7369;7499;7521 SPECIAL_STORE_ID
SQL> declare
2 in_store_id varchar2(20) := 7369;
3 l_exists number;
4 begin
5 select instr(value, ';' || in_store_id || ';')
6 into l_exists
7 from config
8 where property = 'SPECIAL_STORE_ID';
9
10 if l_exists > 0 then
11 dbms_output.put_line('that STORE_ID exists in the value');
12 else
13 dbms_output.put_line('that STORE_ID does not exist in the value');
14 end if;
15 end;
16 /
that STORE_ID exists in the value
PL/SQL procedure successfully completed.
SQL>
If the delimited response is a collection then you can use member of to check if the collection contains the ID or not like
create or replace procedure test_procedure2(p_property in varchar2, p_id in varchar2) is
type test_t is table of varchar2(20);
l_ids test_t;
begin
select regexp_substr(value, '[^;]+', 1, level) bulk collect into l_ids
from (select value from config where property = p_property)
connect by level <= regexp_count(value, ';')+1;
if(p_id member of (l_ids)) then
dbms_output.put_line('Do stuff for '||p_property||' '||p_id);
end if;
end;
/
or do it without the collection with intermediate select like
create or replace procedure test_procedure1(p_property in varchar2, p_id in varchar2) is
l_flag number(3);
begin
select count(1) into l_flag from dual where p_id in (
select regexp_substr(value, '[^;]+', 1, level)
from (select value from config where property = p_property)
connect by level <= regexp_count(value, ';')+1
);
if(l_flag > 0) then
dbms_output.put_line('Do stuff for '||p_property||' '||p_id);
end if;
end;
/
See fiddle

Return Multiple Values from Oracle Function

I want to create a function that returns multiple rows into a table that is of object type.
I have created an object and a nested table object and now when I run the function there is an error which says
PL/SQL: SQL Statement ignored
PL/SQL: ORA-00947: not enough values
-- Object type creation
create or replace type test_object_sn as object
(
column_1 varchar2(30),
column_2 varchar2(30),
column_3 number
);
-- Table of object
create or replace type test_otable_sn as table of test_object_sn;
-- function (where I get an error)
create or replace function load_test_object_sn
return test_otable_sn
as
details test_otable_sn;
begin
with ad as (select 'a', 'b', 4 from dual
union all
select 'r', '5', 3 from dual
union all
select 'g', 's', 3 from dual)
select * into details from ad;
return details;
end;
I want to have the test_otable_sn table object loaded with the data and then query it using the table() function via my load_test_object_sn function
e.g. select * from table(load_test_object_sn);
Update:
do you know how to modify this for scenario whereby I have an sql
statement contained in a string variable to execute?
Yes, we can use a cursor reference (SYS_REFCURSOR) and OPEN/FETCH/CLOSE instead of a CURSOR and CURSOR FOR LOOP.
The syntax is OPEN <cursor-reference> FOR <string-containing-sql-statement> . See below.
CREATE OR REPLACE FUNCTION load_test_object_sn
RETURN test_otable_sn
AS
details test_otable_sn := test_otable_sn();
-- Variable stores SQL statement for cursor
l_sql CLOB :=
q'[with ad as (
select 'a' column_1, 'b' column_2, 4 column_3 from dual union all
select 'r', '5', 3 from dual union all
select 'g', 's', 3 from dual
)
select *
from ad]';
-- Cursor reference allows us to open cursor for SQL statement above
rc SYS_REFCURSOR;
-- Define object instance to store each row fetched from the cursor
l_obj test_object_sn := test_object_sn(NULL, NULL, NULL);
i PLS_INTEGER := 1;
BEGIN
-- Explicitly open, fetch from, and close the cursor
OPEN rc FOR l_sql;
LOOP
FETCH rc INTO l_obj.column_1, l_obj.column_2, l_obj.column_3;
EXIT WHEN rc%NOTFOUND;
details.extend();
details(i) := test_object_sn(l_obj.column_1, l_obj.column_2, l_obj.column_3);
i := i + 1;
END LOOP;
CLOSE rc;
RETURN details;
END;
Original answer:
Unfortunately, one can't use SELECT * INTO with a collection in this manner, so here's an alternative way to populate the table:
create or replace function load_test_object_sn
return test_otable_sn
as
details test_otable_sn := test_otable_sn();
cursor c_ad is
with ad as (select 'a' column_1, 'b' column_2, 4 column_3 from dual
union all
select 'r', '5', 3 from dual
union all
select 'g', 's', 3 from dual)
select * from ad;
i pls_integer := 1;
begin
for ad_rec in c_ad loop
details.extend();
details(i) := test_object_sn(ad_rec.column_1, ad_rec.column_2, ad_rec.column_3);
i := i + 1;
end loop;
return details;
end;
/
Output:
SQL> SELECT * FROM TABLE(load_test_object_sn);
COLUMN_1 COLUMN_2 COLUMN_3
---------- ---------- ----------
a b 4
r 5 3
g s 3

Converting String concatenated with ',' to be used in subquery at oracle SQL

I want to use my result of function e.g. 'S500,S600,S700,S800' in a subquery in another script like:
where dept_no in (my result of function)
So I want to convert my string result to be like this ('S500','S600','S700','S800').
I tried to do this with dynamic SQL but I can't get it to work.
Hope below snipet suffice your requirement.
Approach 1 -> More effective
--Create a table type of VARCHAR
CREATE OR REPLACE type string_table
IS
TABLE OF VARCHAR2(100);
--Function to return tabl type
CREATE OR REPLACE
FUNCTION string_manipulate
RETURN string_table
AS
str_tab string_table;
BEGIN
SELECT 's00'||level bulk collect INTO str_tab FROM dual CONNECT BY level < 10;
RETURN str_tab;
end;
--Use function in the query
SELECT distinct 1
FROM
(SELECT 's001' dn FROM dual
UNION ALL
SELECT 's002' dn FROM dual
UNION ALL
SELECT 's003' dn FROM dual
UNION ALL
SELECT 's004' dn FROM dual
UNION ALL
SELECT 's005' dn FROM dual
UNION ALL
SELECT 's006' dn FROM dual
UNION ALL
SELECT 's007' dn FROM dual
UNION ALL
SELECT 's008' dn FROM dual
UNION ALL
SELECT 's009' dn FROM dual
)a
WHERE a.dn IN
(SELECT * FROM TABLE(string_manipulate)
);
--Approach 2
--Function to get output as mentioned.
CREATE OR REPLACE
FUNCTION string_manipulate
RETURN VARCHAR2
AS
BEGIN
RETURN 'S2009,S2020,S2021';
END;
-- Use function value in a query
SELECT 1
FROM dual
WHERE '''S2009'',''S2020'',''S2021''' = (''''
||REPLACE(string_manipulate,',',''',''')
||'''');
You need an iterator and text splitting by comma sign:
select empno,ename,sal,deptno
from emp
where empno in (
select to_number(
rtrim(
substr(emps,
instr(emps,',',1,iter.pos)+1,
instr(emps,',',1,iter.pos+1) -
instr(emps,',',1,iter.pos)),',')) emps
from (select ','||'7654,7698,7782,7788'||',' emps from t1) csv,
(select rownum pos from emp) iter
where iter.pos <= ((length(csv.emps) -
length(replace(csv.emps,',')))/length(','))-1
)
But better rewrite your function to return cursor.
you can use collection:
SELECT *
FROM YOUR_TABLE
WHERE DEPT_NO IN (SELECT *
FROM TABLE (SPLIT ('S500,S600,S700,S800')))--splits text with comma, for other chars use split(text, split_char)
With usage of MEMBER OF
SELECT *
FROM YOUR_TABLE
WHERE DEPT_NO MEMBER OF SPLIT ('S500,S600,S700,S800')--splits text with comma, for other chars use split(text, split_char)
the split fuction is:
CREATE OR REPLACE TYPE SPLIT_TBL AS TABLE OF VARCHAR2 (32767);
CREATE OR REPLACE FUNCTION SPLIT (P_LIST VARCHAR2, P_DEL VARCHAR2 := ',')
RETURN SPLIT_TBL
PIPELINED
IS
L_IDX PLS_INTEGER;
L_LIST VARCHAR2 (32767) := P_LIST;
BEGIN
LOOP
L_IDX := INSTR (L_LIST, P_DEL);
IF L_IDX > 0
THEN
PIPE ROW (SUBSTR (L_LIST, 1, L_IDX - 1));
L_LIST := SUBSTR (L_LIST, L_IDX + LENGTH (P_DEL));
ELSE
PIPE ROW (L_LIST);
EXIT;
END IF;
END LOOP;
RETURN;
END SPLIT;
FUNCTION GET_TS_EACH_DAY_DEPARTMENT (P_SER_NO VARCHAR2,
P_TS_DATE DATE
)
RETURN STRING_TABLE
IS
V_DEPT_NO VARCHAR2 (4000);
V_DEPT VARCHAR2(4000);
V_TABLE STRING_TABLE:=STRING_TABLE();
J NUMBER:=1;
BEGIN
for i in (select distinct ts_day dayy from WEB_TS_USER_LOCATIONS_V ) loop
V_TABLE.EXTEND;
V_TABLE(J):= WEB_TS_PKG.GET_TS_DAY_DEPARTMENT (P_SER_NO ,P_TS_DATE , i.dayy );
J:=J+1;
end loop;
RETURN V_TABLE;
END GET_TS_EACH_DAY_DEPARTMENT;

I need to use variable with TABLE type in IN operator

I need help with PL/SQL. How can i use variable with TABLE data type (in my case this variable involve 3 VARCHAR2 elements) with IN operator, without use access by index?
Example
select field1
from dual
where field1 in (myTableVariable);
myTableVariable must returning from function.
Not finished function:
declare
v_string varchar2(100);
v_string2 varchar2(100);
TYPE V_ARRAY IS TABLE OF VARCHAR2(10)
INDEX BY BINARY_INTEGER;
result V_ARRAY;
n number;
begin
n := 1;
v_string := '13,15,02';
while v_string != ' ' loop
select regexp_substr(v_string, '^[a-z0-9]*', 1),
regexp_replace(v_string, '^[a-z0-9]*(,|$)', '')
into v_string2, v_string
from dual;
result(n) := v_string2;
n := n + 1;
dbms_output.put_line(v_string2);
end loop;
return result;
end;
First:
Declare your table data type on the schema level (i.e. not in a package).
Then:
select field1
from dual
where feld1 in (select column_value from table(myTableVariable));
Enjoy!