Calling Stored procedure with 'IS IN' statement in Oracle - sql

I have following issue. I need to filter fetching data in stored procedure:
SELECT * FROM tab WHERE post_code IS IN ('pc1', 'pc2', 'pc3');
My question is: how to pass arguments pc1, pc2, pc3... into stored procedure?
As an array or as string?
When I try pass as string I have problem with apostrophes.
Passing array in my opinion is not good because of performance...I will need to create for loop and create string which will be passed like this:
SELECT * FROM tab WHERE post_code IS IN (post_codes);
How to do it right?

Not sure why you think passing an array would lead to a performance issue.
This will work:
declare
post_codes sys.dbms_debug_vc2coll := new sys.dbms_debug_vc2coll ('pc1', 'pc2', 'pc3');
lrec tab%rowtype;
begin
select * into lrec
where post_code in ( select * from table(post_codes));
end;
/
This is proof of concept only. It will hurl TOO_MANY_ROWS exception if the query returns more than one row. As you haven't provided any context for what you are trying to achieve I haven't bothered to invent anything exra.

The easiest way, in my opinion is to use SQL to split up your string:
with id_generator
as
(
SELECT regexp_substr(:txt, '[^,]+', 1, LEVEL) token
FROM dual
CONNECT BY LEVEL <= length(:txt) - length(REPLACE(:txt, ',', '')) + 1
)
select u.id, u.username
from users u, id_generator g
where u.id = g.token;
This query looks a little scary, but you can take the top part and run it in isolation to see what it does:
SELECT regexp_substr(:txt, '[^,]+', 1, LEVEL) token
FROM dual
CONNECT BY LEVEL <= length(:txt) - length(REPLACE(:txt, ',', '')) + 1
This generates a row per item in your comma separated list, which you can then join to the other table to get your results.
More discussion on this problem on my blog - http://betteratoracle.com/posts/20-how-do-i-bind-a-variable-in-list

Related

convert varchar value to array to varchar sql oracle [duplicate]

I am having trouble getting a block of pl/sql code to work. In the top of my procedure I get some data from my oracle apex application on what checkboxes are checked. Because the report that contains the checkboxes is generated dynamically I have to loop through the
APEX_APPLICATION.G_F01
list and generate a comma separated string which looks like this
v_list VARCHAR2(255) := (1,3,5,9,10);
I want to then query on that list later and place the v_list on an IN clause like so
SELECT * FROM users
WHERE user_id IN (v_list);
This of course throws an error. My question is what can I convert the v_list to in order to be able to insert it into a IN clause in a query within a pl/sql procedure?
If users is small and user_id doesn't contain commas, you could use:
SELECT * FROM users WHERE ',' || v_list || ',' LIKE '%,'||user_id||',%'
This query is not optimal though because it can't use indexes on user_id.
I advise you to use a pipelined function that returns a table of NUMBER that you can query directly. For example:
CREATE TYPE tab_number IS TABLE OF NUMBER;
/
CREATE OR REPLACE FUNCTION string_to_table_num(p VARCHAR2)
RETURN tab_number
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(cc.res);
END LOOP;
END;
/
You would then be able to build queries such as:
SELECT *
FROM users
WHERE user_id IN (SELECT *
FROM TABLE(string_to_table_num('1,2,3,4,5'));
You can use XMLTABLE as follows
SELECT * FROM users
WHERE user_id IN (SELECT to_number(column_value) FROM XMLTABLE(v_list));
I have tried to find a solution for that too but never succeeded. You can build the query as a string and then run EXECUTE IMMEDIATE, see http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/dynamic.htm#i14500.
That said, it just occurred to me that the argument of an IN clause can be a sub-select:
SELECT * FROM users
WHERE user_id IN (SELECT something FROM somewhere)
so, is it possible to expose the checkbox values as a stored function? Then you might be able to do something like
SELECT * FROM users
WHERE user_id IN (SELECT my_package.checkbox_func FROM dual)
Personally, i like this approach:
with t as (select 'a,b,c,d,e' str from dual)
--
select val
from t, xmltable('/root/e/text()'
passing xmltype('<root><e>' || replace(t.str,',','</e><e>')|| '</e></root>')
columns val varchar2(10) path '/'
)
Which can be found among other examples in Thread: Split Comma Delimited String Oracle
If you feel like swamping in even more options, visit the OTN plsql forums.

LIKE on multiple values, all must match

I'm querying a legacy Oracle db to match a name using LIKE. It would be good if a multi-word pattern (could be 1 word, 3 words, 8 words, whatever) could be matched against names independently of word order. Eg, if the user searched for "rob jon", it would match
ROBERT JONES
JONATHON ADAM PROBE
but not match
ROB SMITH
PETER JONES
If the requirement was "at least one word should match" I could use REGEXP_LIKE(name, 'rob|jon", 'i').
But I can't work out how to make it match only when ALL words are found somewhere in the name. I could do this using Oracle Text, but I can't use that in this db. I also can't easily store the words in a table to join to either.
The query is currently static and uses bind variables, and so I would prefer not to add an extra LIKE predicate for each word in the search pattern.
Any ideas?
According to your question and the data presented in the post, I Used this table
create table name_pattern
(
name_1 varchar(200)
);
insert statements :
insert into name_pattern
select ( 'ROBERT JONES') from dual union all
select ('JONATHON ADAM PROBE') from dual union all
select ('ROB SMITH') from dual union all
select ('PETER JONES') from dual;
commit;
try this if it works for you if you are searching "rob" and "jon" separately.
select name from table_name where lower(name) like '%rob%' and lower(name) like '%jon%' ;
try this if you want to search "rob jon" combined and to get your expected result
select * from name_pattern where REGEXP_COUNT(name_1, 'rob|jon',1, 'i') =2;
You want your query to accept one bind variable containing a blank-separated list of name parts, e.g. 'rob jon'.
You can write a recursive query to get the single name parts from that string. Then use NOT EXISTS to only keep names for which not exists any mismatch.
with parameters as
(
select :namepartlist as namepartlist from dual
)
, nameparts (namepart, namepartlist, occurrence) as
(
select regexp_substr(namepartlist, '[^ ]+', 1, 1), namepartlist, 1
from parameters
union all
select regexp_substr(namepartlist, '[^ ]+', 1, occurrence + 1), namepartlist, occurrence + 1
from nameparts
where regexp_substr(namepartlist, '[^ ]+', 1, occurrence + 1) is not null
)
select *
from mytable
where not exists
(
select null
from nameparts
where lower(mytable.name) not like '%' || lower(nameparts.namepart) || '%'
)
order by name;
You can replace
where lower(mytable.name) not like '%' || lower(nameparts.namepart) || '%'
by
where not regexp_like(mytable.name, nameparts.namepart, 'i')
if you like that better.
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=5e48caaa20e9397afe65516504c62acd
if you prefer not to add an extra LIKE predicate for each word in the search pattern (the best solution IMHO) you can create a PL/SQL function to do the matching
FUNCTION PATTERN_MATCHES(pattern in varchar2, name in varchar2) returns number
is
Begin *pseudocode*
*convert pattern to lowercase*
*split pattern by spaces*
*for each part, Loop*
if NOT lower(name) like %part% then
return 0; --early exit
end;
End Loop;
return 1; --all parts have a match
end;
select * from employee where PATTERN_MATCHES(:pattern, name)=1
Then you can have a static parameterized query.
On the bright side, you'll be able to extend the behavior (add advanced search for example) without touching the front-end app.
PS: The function code is intentionally left for you to write. Ask if you need help with that.
PS2: returning "number" and not a boolean will help you with legacy database drivers. Feel free to use integer or a boolean if that works for you.
Shamelessy building on #Jfrd's solution ... is this any good ? (Performance will be shocking on large tables).
Basically, replace all spaces with '%' (not catered here for successive spaces).
var search_text varchar2(30)
exec :search_text := 'Rob Jon';
select name
from table_name
where lower(name) like REPLACE('%' || :search_text || '%', ' ', '%' ) ;

PLSQL Selecting from table using an array into a variable

I am trying to select some records from a table using an array into the variable init_av_days but its not working out. init_av_days is to be committed to another table after this select query. How best do you suggest i do this?
declare
myarray APEX_APPLICATION_GLOBAL.VC_ARR2;
init_av_days varchar2(10);
begin
myarray := APEX_UTIL.STRING_TO_TABLE(:P592_AVAILABILITY_DAYS);
For i in 1.. myarray.count loop
select availability_days
into init_av_days
from sl_available_days
where location_code = :P592_LOCATION_CODE
and availability_days = myarray(i);
end loop;
end;
init_av_days is to be committed to another table after this select query
Best? Skip PL/SQL entirely, I'd say. Something like this:
insert into another_table (some_column)
select availability_days
from sl_available_days
where location_code in = (select regexp_substr(:P592_LOCATION_CODE, '[^:]+', 1, level)
from dual
connect by level <= regexp_count(:P592_LOCATION_CODE, ':') + 1
)
and availability_days = <I don't know what APEX_APPLICATION_GLOBAL.VC_ARR2 contains>
This code should be fixed because I don't know the last condition; if you know how, do it. If not, explain what's in vcc_arr2, maybe we can assist.

Return all matches of a regular expression in Oracle

I have a table that contains a VARCHAR2 column called COMMANDS.
The data in this column is a bunch of difficult to read ZPL code that will be sent to a label printer, and amidst the ZPL there are several tokens in the form {TABLE.COLUMN}.
I would a like nice list of all the distinct {TABLE.COLUMN} tokens that are found in COMMANDS. I wrote the following regex to match the token format:
SELECT REGEXP_SUBSTR(COMMANDS,'\{\w+\.\w+\}') FROM MYTABLE;
The regex works, but it only returns the first matched token per row. Is there a way to return all regex matches for each row?
I'm using Oracle 11GR2.
Edit - Here is a small sample of data from a single row -- there are many such lines in each row:
^FO360,065^AEN,25,10^FD{CUSTOMERS.CUST_NAME}^FS
^FO360,095^AAN,15,12^FD{CUSTOMERS.CUST_ADDR1}^FS
So if that was the only row in table, I'd like to have returned:
{CUSTOMERS.CUST_NAME}
{CUSTOMERS.CUST_ADDR1}
You've provided sample of data saying that this is a single row but have presented it as two different rows. So this example based on your words.
-- Sample of data from your question + extra row for the sake of demonstration
-- id column is added to distinguish the rows(I assume you have one)
with t1(id, col) as(
select 1, '^FO360,065^AEN,25,10^FD{CUSTOMERS1.CUST_NAME}^FS^FO360,095^AAN,15,12^FD{CUSTOMERS1.CUST_ADDR1}^FS' from dual union all
select 2, '^FO360,065^AEN,25,10^FD{CUSTOMERS2.CUST_NAME}^FS^FO360,095^AAN,15,12^FD{CUSTOMERS2.CUST_ADDR2}^FS' from dual
),
cnt(c) as(
select level
from (select max(regexp_count(col, '{\w+.\w+}')) as o_c
from t1
) z
connect by level <= z.o_c
)
select t1.id, listagg(regexp_substr(t1.col, '{\w+.\w+}', 1, cnt.c)) within group(order by t1.id) res
from t1
cross join cnt
group by t1.id
Result:
ID RES
---------------------------------------------------------
1 {CUSTOMERS1.CUST_ADDR1}{CUSTOMERS1.CUST_NAME}
2 {CUSTOMERS2.CUST_ADDR2}{CUSTOMERS2.CUST_NAME}
As per #a_horse_with_no_name comment to the question, really, it's much simpler to just replace everything else that doesn't match the pattern. Here is an example:
with t1(col) as(
select '^FO360,065^AEN,25,10^FD{CUSTOMERS.CUST_NAME}^FS^FO360,095^AAN,15,12^FD{CUSTOMERS.CUST_ADDR1}^FS' from dual
)
select regexp_replace(t1.col, '({\w+.\w+})|.', '\1') res
from t1
Result:
RES
-------------------------------------------
{CUSTOMERS.CUST_NAME}{CUSTOMERS.CUST_ADDR1}
I think there isn't. You should write some PL/SQL to get the others matching tokens. My best advice to you is to use a pipelined function.
First, create a type:
create type strings as table of varchar2(200);
Then the function:
CREATE OR REPLACE function let_me_show
return strings PIPELINED as
l_n number;
l_r varchar2(200);
begin
for r_rec in
( SELECT commands
FROM MYTABLE )
loop
l_n := 1;
l_r := REGEXP_SUBSTR(r_rec.COMMANDS,'\{\w+\.\w+\}', 1, l_n);
while l_r is not null
loop
pipe row(l_r);
l_n := l_n + 1;
l_r := REGEXP_SUBSTR(r_rec.COMMANDS,'\{\w+\.\w+\}', 1, l_n);
end loop;
end loop;
end;
Now you can use the function to return the results:
select *
from table(let_me_show())

Oracle comma separated parameter to row and join with other tables

I am using Oracle 10g.
My Scenario:
I am getting some more than 4000 records in a comma separated string('ord0000,ord0001,ord0002,......') as a parameter. I need to compare these values against a table1 and find out the matching recordset.
For that purpose I created a function as below:
function get_split_values (
csv_string varchar2
) return split_table pipelined
as
Delimit_String varchar2(32767) := csv_string;
Delimit_index integer;
begin
loop
Delimit_index := instr(delimit_string,',');
if Delimit_index > 0 then
pipe row(substr(delimit_string,1,delimit_index-1));
delimit_string := substr(delimit_string,delimit_index+1);
else
pipe row(delimit_string);
exit;
end if;
end loop;
return;
end get_split_values;
Now when I used this function to join with my table1 in a procedure as below:
create procedure abc (parameter_csv varchar2,...)
as
begin
open cursor for
select t.col1 from table1 t join table(get_split_values(parameter_csv)) x
on x.column_value = t.col1;
...
end abc;
It works fine when the parameter_csv have some around 300 or 400 IDs like('ord0000,ord0001,ord0002,......') but when it contains more that records I got the error
"ORA 01460 : unimplemented or unreasonable conversion requested."
I don't understand what raise this error. Any ideas?
OR is there any best way to accomplish this task.
Initially I thought you were overflowing your varchar2(32767) but a quick look at your sample IDs indicates that you shouldn't be maxing out that early(400 ids).
A quick google of the error led me to this forum in OTN: http://forums.oracle.com/forums/thread.jspa?threadID=507725&start=15&tstart=0
And to this blog post: http://oraclequirks.blogspot.com/2008/10/ora-01460-unimplemented-or-unreasonable.html
which indicates that this might be an oracle bug
If its a bug with using the PL/SQL procedure, you can just split the string as part of an inline view. Something like.
SELECT T.col1
FROM table1 T
JOIN ( SELECT REGEXP_SUBSTR( parameter_csv, '[^,]+', 1, LEVEL ) AS id
FROM DUAL
CONNECT BY LEVEL <=
LENGTH( REGEXP_REPLACE( parameter_csv, '[^,]+', '' ) ) + 1
) X
ON X.id = T.col1;
NOTE: Doesn't handle things like duplicate IDs in the the csv, empty values in the csv ,, etc