WHERE condition for "starts with any of the values in a CSV list" [duplicate] - oracle

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
how to convert csv to table in oracle
I have a query in PL/SQL that is built to handle input in a variable as a "starts-with" filter:
WHERE product_group LIKE strProductGroup || '%'
As a new "feature", the input variable could contain comma separated values. So, where before I would expect something like "ART", I could now see "ART,DRM".
I'd like to avoid building this query as a string and using EXECUTE IMMEDIATE, if possible. Can anyone think of a way to write a WHERE condition that is the equivalent of saying "starts with any of the values in a CSV list" in Oracle 10g?

Assuming that you don't have any restrictions on creating a couple of additional objects (a collection type and a function), you can parse the list into a collection that you can reference in your query. Tom Kyte has a good discussion on this in his variable "IN" list thread.
If you use Tom's myTableType and in_list function, for example
SQL> create or replace type myTableType as table
of varchar2 (255);
2 /
Type created.
ops$tkyte#dev8i> create or replace
function in_list( p_string in varchar2 ) return myTableType
2 as
3 l_string long default p_string || ',';
4 l_data myTableType := myTableType();
5 n number;
6 begin
7 loop
8 exit when l_string is null;
9 n := instr( l_string, ',' );
10 l_data.extend;
11 l_data(l_data.count) :=
ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
12 l_string := substr( l_string, n+1 );
13 end loop;
14
15 return l_data;
16 end;
17 /
Then you can search for equality relatively easily.
WHERE product_group IN (SELECT column_value
FROM TABLE( in_list( strProductGroup )))
But you want to do a LIKE which is a bit more challenging since you can't do a LIKE on an in-list. You could, however, do something like
select *
from emp e,
(select '^' || column_value search_regexp
from table( in_list( 'KIN,BOB' ))) a
where regexp_like( e.ename, a.search_regexp )
This will search the EMP table for any employees where the ENAME begins with either KIN or BOB. In the default SCOTT.EMP table, this will return just one row, the row where the ENAME is "KING"

I found another post that gave me an idea. In my specific case, the values in the input will all be 3 characters, so I can do the following:
AND SUBSTR(product_group, 0, 3) IN
(SELECT regexp_substr(strProductGroup, '[^,]+', 1, LEVEL)
FROM dual
CONNECT BY LEVEL <= length(regexp_replace(strProductGroup, '[^,]+')) + 1)
I like this solution, because it does not require additional types or functions, but is pretty limited to my specific case.

Related

Convert Varchar2 to Char array in Oracle

I have a varchar2 field and want to split it to array of chars
Like 'ABCDEF' --> 'A' 'B' 'C' 'D' 'E'
How can i convert my Field Values to chars array?
If you actually mean a PL/SQL collection of characters, you could do something like
SQL> ed
Wrote file afiedt.buf
1 declare
2 type char_arr is table of char(1) index by pls_integer;
3 l_str varchar2(100) := 'ABCDEF';
4 l_arr char_arr;
5 begin
6 for i in 1 .. length(l_str)
7 loop
8 l_arr(i) := substr( l_str, i, 1 );
9 end loop;
10 dbms_output.put_line( l_arr.count );
11* end;
SQL> /
6
PL/SQL procedure successfully completed.
Without understanding the business requirements, though, I would tend to be very suspicious. When you find yourself breaking apart strings in PL/SQL, that almost always implies that you have stored data in a non-atomic form and need to address the data model issue.

Oracle Invalid Number error while passing comma separated numbers

Here is he problem desc.
I am trying to pass comma separated integer values(stored in a Varchar variable) to a IN conditon.
local_var VARCHAR2(20);
local_var := 1,2;
WHERE someid IN (local_var) --Just pasted the where clause where IN condition is being used
someid is of type NUMBER,
I get an invalid number error while executing this statement in a cursor. I am assuming this problem is because of someid being a NUMBER,
What is the possible solution? I cannot pass the values say 1,2 directly to IN clause, because I need to build the same based on some conditions,
I have tried passing comma separated values in quotes too, didnt seem to work
Using Oracle version 11.2
I have tried using
local_var := '1,2';
local_var := '''1','2'''; etc..
Nothing seemed to work
Urgent help required! Thanks in advance
You are getting that error, obviously, because someid is of number datatype and you are passing a comma separated string, which is considered as one value of string datatype and not a list of values as you would expect, to the IN clause. But you can transform that string, using regexp_substr and regexp_count regular expression functions, into table rows an then use them in the IN clause. Here is an example:
SQL> declare
2 cursor your_cursor(c_str_value varchar2) is
3 with str_values as(
4 select regexp_substr(c_str_value, '[^,]+', 1, level) as value
5 from dual
6 connect by level <= regexp_count(c_str_value, '[^,]+')
7 )
8 select first_name -- here goes your query
9 from employees e
10 where e.department_id in (select to_number(value)
11 from str_values);
12
13 l_local_var varchar2(20);
14 begin
15 l_local_var := '100,102,103'; -- your comma separated string
16 for i in your_cursor(l_local_var) -- for the sake of demonstration
17 loop
18 dbms_output.put_line(i.first_name);
19 end loop;
20 end;
21 /
Nancy
Daniel
John
Ismael
Jose Manuel
Luis
PL/SQL procedure successfully completed
You can not pass a list as arguments like that.
You could go with using a Dynamic PL/SQL block. Something like:
DECLARE
local_var VARCHAR2(20) := '1,2';
req VARCHAR(1000);
BEGIN
req := 'BEGIN
FOR c IN (SELECT someid FROM my_table WHERE someid IN (' || local_var || '))
LOOP
DBMS_OUTPUT.PUT_LINE('someid ' || c.someid);
END LOOP;
END;';
EXECUTE IMMEDIATE req;
END;
But… Be very careful of PL/SQL injections.
your variable type is VARCHAR2 and you are assigning number types, t'y u r getting that error. please use single quotations.
for example #
local_var VARCHAR2(20);
local_var := '1';
WHERE someid IN (local_var);
this is the way
create table a(local_var varchar2(10))
insert into a values('2');
select * from a;
declare
y varchar2(10);
cursor c1(v_val varchar2) is select * from a;
Begin
y := '1,2';
for i in c1(y)
loop
dbms_output.put_line(i.local_var);
end loop ;
end;
/

How to use parameters in a 'where value in...' clause?

This works when I have only one state code as a parameter.
How can I get code to work when I have more than one state_code in parm_list?
Requirements:
(1)I don't want to hard code the state codes in my cursor definition
(2) I do want to allow for more than one state code in my where clause
For example: I want to run this code for parm_list = ('NY','NJ','NC').
I'm encountering difficulties in reconciling single quotes in parm_list with the single quotes in the 'where state_code in ' query.
set serveroutput on;
DECLARE
parm_list varchar2(40);
cursor get_state_codes(in_state_codes varchar2)
is
select state_name, state_code from states
where state_code in (in_state_codes);
BEGIN
parm_list := 'NY';
for get_record in get_state_codes(parm_list) loop
dbms_output.put_line(get_record.state_name || get_record.state_code);
end loop;
END;
Using dynamic SQL is the simplest approach from a coding standpoint. The problem with dynamic SQL, though, is that you have to hard parse every distinct version of the query which not only has the potential of taxing your CPU but has the potential to flood your shared pool with lots of non-sharable SQL statements, pushing out statements you'd like to cache, causing more hard parses and shared pool fragmentation errors. If you're running this once a day, that's probably not a major concern. If hundreds of people are executing it thousands of times a day, that is likely a major concern.
An example of the dynamic SQL approach
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_deptnos varchar2(100) := '10,20';
3 l_rc sys_refcursor;
4 l_dept_rec dept%rowtype;
5 begin
6 open l_rc for 'select * from dept where deptno in (' || l_deptnos || ')';
7 loop
8 fetch l_rc into l_dept_rec;
9 exit when l_rc%notfound;
10 dbms_output.put_line( l_dept_rec.dname );
11 end loop;
12 close l_rc;
13* end;
SQL> /
ACCOUNTING
RESEARCH
PL/SQL procedure successfully completed.
Alternately, you can use a collection. This has the advantage of generating a single, sharable cursor so you don't have to worry about hard parsing or flooding the shared pool. But it probably requires a bit more code. The simplest way to deal with collections
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_deptnos tbl_deptnos := tbl_deptnos(10,20);
3 begin
4 for i in (select *
5 from dept
6 where deptno in (select column_value
7 from table(l_deptnos)))
8 loop
9 dbms_output.put_line( i.dname );
10 end loop;
11* end;
SQL> /
ACCOUNTING
RESEARCH
PL/SQL procedure successfully completed.
If, on the other hand, you really have to start with a comma-separated list of values, then you will have to parse that string into a collection before you can use it. There are various ways to parse a delimited string-- my personal favorite is to use regular expressions in a hierarchical query but you could certainly write a procedural approach as well
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_deptnos tbl_deptnos;
3 l_deptno_str varchar2(100) := '10,20';
4 begin
5 select regexp_substr(l_deptno_str, '[^,]+', 1, LEVEL)
6 bulk collect into l_deptnos
7 from dual
8 connect by level <= length(replace (l_deptno_str, ',', NULL));
9 for i in (select *
10 from dept
11 where deptno in (select column_value
12 from table(l_deptnos)))
13 loop
14 dbms_output.put_line( i.dname );
15 end loop;
16* end;
17 /
ACCOUNTING
RESEARCH
PL/SQL procedure successfully completed.
One option is to use INSTR instead of IN:
SELECT uo.object_name
,uo.object_type
FROM user_objects uo
WHERE instr(',TABLE,VIEW,', ',' || uo.object_type || ',') > 0;
Although this looks ugly, it works well and as long as no index on the column being tested was going to be used (because this prevents the use of any index) the performance won't suffer much. If the column being tested is a primary key for instance, definitely this should not be used.
Another option is:
SELECT uo.object_name
,uo.object_type
FROM user_objects uo
WHERE uo.object_type IN
(SELECT regexp_substr('TABLE,VIEW', '[^,]+', 1, LEVEL)
FROM dual
CONNECT BY regexp_substr('TABLE,VIEW', '[^,]+', 1, LEVEL) IS NOT NULL);
In this case, the list of values should be concatenated into a single varchar variable, delimited by commas (or anything you like.)

using long string(over 4000) in oracle query

I know that in sql varchar2 can only be around 4000.
I know that in oracle PL varchcar2 can be around 32000.
I have a varchar2 variable defined that is over 4000 characters long and I want to use it in a query. I don't want to insert the value into a table. The value is a dilimited string that I am parsing and inserting into a table with this query. This query works when the variable is less than 4000 characters long. Is there a way to make it work with up to 32000 characters?
create global temporary table t(single_element varchar(500),element_no number);
declare
--declared as 32767 but this string contains less than 4000 characters.
--This will work. If you expand the string to 32000 characters it will not work.
myvar varchar2(32767) := 'tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4';
begin
delete from t;
insert into t
SELECT SUBSTR(str, start_pos, (next_pos-start_pos)) AS single_element, element_no
FROM (
SELECT
ilv.str,
nt.column_value AS element_no,
INSTR(ilv.str, '^~', DECODE(nt.column_value, 1, 0, 1), DECODE(nt.column_value, 1, 1, nt.column_value-1)) + 2 AS start_pos,
INSTR(ilv.str, '^~', 1, DECODE(nt.column_value, 1, 1, nt.column_value)) AS next_pos
FROM (
select '~' || myvar || '^~' as str,
(Length(myvar) - length(replace(myvar,'^~','')))/2 + 2 as no_of_elements
from dual) ilv,
TABLE(
CAST(
MULTISET(
SELECT ROWNUM FROM dual CONNECT BY ROWNUM < ilv.no_of_elements
) AS number_ntt )) nt
);
end;
The error I get when expanding "myvar" to 32000 characters is
can bind a LONG value only for insert into a LONG column
Is there a way I can get around this size restraint because i'm not actually inserting this value into a table, i'm just using it in the query?
Do you have to define the variable as a VARCHAR2? Could you define it as a CLOB instead?
If I change the declaration of MYVAR from a VARCHAR2(32767) to a CLOB and define the NUMBER_NTT type, your code runs for me
SQL> ed
Wrote file afiedt.buf
SP2-0161: line 2 truncated.
1 declare
2 myvar clob := 'tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testms
<<snip>>
~tcd3~#testmsg3^~tcd4~#testmsg4';
4 begin
5 delete from t;
6 insert into t
7 SELECT SUBSTR(str, start_pos, (next_pos-start_pos)) AS single_element, elem
ent_no
8 FROM (
9 SELECT
10 ilv.str,
11 nt.column_value AS element_no,
12 INSTR(ilv.str, '^~', DECODE(nt.column_value, 1, 0, 1), DECODE
(nt.column_value, 1, 1, nt.column_value-1)) + 2 AS start_pos,
13 INSTR(ilv.str, '^~', 1, DECODE(nt.column_value, 1, 1, nt.colu
mn_value)) AS next_pos
14 FROM (
15 select '~' || myvar || '^~' as str,
16 (Length(myvar) - length(replace(myvar,'^~','')))/2 + 2 as n
o_of_elements
17 from dual) ilv,
18 TABLE(
19 CAST(
20 MULTISET(
21 SELECT ROWNUM FROM dual CONNECT BY ROWNUM < ilv.n
o_of_elements
22 ) AS number_ntt )) nt
23 );
24* end;
25 /
PL/SQL procedure successfully completed.
SQL> select count(*) from t;
COUNT(*)
----------
172
That being said, that's not how I'd parse a delimited string, particularly in PL/SQL. But it does the job.
OK, well this skirts close to the edges of your implementation bias, although remember that forall IS a bulk-binding operation, not a real loop, but have you looked at the dbms_utility.comma_to_table function?
It is an optimized internal oracle parsing function, although with some limitations as you can read about here: http://www.techiegyan.com/2009/02/17/oracle-breaking-comma-separated-string-using-dbms_utilitycomma_to_table/
You would need to replace() to make it comma-delimited, and also double-quotes-enclose if you have parsed fields that starts with numbers, special characters, contains commas, etc
But if your data will allow - it sure makes your code look cleaner (and will likely work much faster too)
declare
myvar varchar2(32000) := 'tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3';
mycnt binary_integer;
myresults sys.dbms_utility.lname_array;
begin
sys.dbms_utility.comma_to_table('"'||replace(myvar,'^~','","')||'"', mycnt, myresults );
delete from t;
forall ix in myresults.first..myresults.last
insert into tvalues (myresults(ix));
commit;
end;

oracle blob text search

Is it possible to search through blob text using sql statement?
I can do select * from $table where f1 like '%foo%' if the f1 is varchar, how about f1 is a blob? Any counter part for this?
This is quite possible and easy to do.
Simply use dbms_lob.instr in conjunction with utl_raw.cast_to_raw
So in your case, if t1 is a BLOB the select would look like:
select *
from table1
where dbms_lob.instr (t1, -- the blob
utl_raw.cast_to_raw ('foo'), -- the search string cast to raw
1, -- where to start. i.e. offset
1 -- Which occurrance i.e. 1=first
) > 0 -- location of occurrence. Here I don't care. Just find any
;
If it is a Word or PDF document, look into Oracle Text.
If you are storing plain text it should be a CLOB, not a BLOB, and then you can still query using LIKE. A BLOB contains binary data that Oracle doesn't know the structure of, so it cannot search it in this way.
This works for CLOBs of any length (at least on Oracle 12C):
SQL> create table t1 (c clob);
Table created.
SQL> declare
2 x clob;
3 begin
4 for i in 1..100 loop
5 x := x || rpad('x', 32767, 'x');
6 end loop;
7 x := x || 'z';
8 for i in 1..100 loop
9 x := x || rpad('x', 32767, 'x');
10 end loop;
11 insert into t1 values (x);
12 end;
13 /
PL/SQL procedure successfully completed.
SQL> select dbms_Lob.getlength(c) from t1 where c like '%z%';
DBMS_LOB.GETLENGTH(C)
---------------------
6553401
Note that there is only one 'z' in that 6,554,401 byte CLOB - right in the middle of it:
SQL> select instr(c, 'z') from t1;
INSTR(C,'Z')
------------
3276701
the below code is to display the details from blob as text using UTL_RAW.CAST_TO_VARCHAR2 function then we use substr function to cut the text from the start of expected data till end. however, you can use instr function, LENGTH function , if you know the location of the data you are looking for
select NVL(SUBSTR(UTL_RAW.CAST_TO_VARCHAR2(blob_body),
INSTR(UTL_RAW.CAST_TO_VARCHAR2(blob_body), '<ns:xml_element>') + LENGTH('<ns:xml_element>'),
INSTR(UTL_RAW.CAST_TO_VARCHAR2(blob_body), '</ns:xml_element>') - (
INSTR(UTL_RAW.CAST_TO_VARCHAR2(blob_body), '<ns:xml_element>') + LENGTH('<ns:xml_element>'))),
utl_raw.cast_to_varchar2(DBMS_LOB.SUBSTR(blob_body))
) blob_body
from dual
where SUBSTR(UTL_RAW.CAST_TO_VARCHAR2(blob_body),
INSTR(UTL_RAW.CAST_TO_VARCHAR2(blob_body), '<ns:xml_element>') + LENGTH('<ns:xml_element>'),
INSTR(UTL_RAW.CAST_TO_VARCHAR2(blob_body), '</ns:xml_element>') - (
INSTR(UTL_RAW.CAST_TO_VARCHAR2(blob_body), '<ns:xml_element>') + LENGTH('<ns:xml_element>'))) like '%foo%';
Select * From TABLE_NAME
and dbms_lob.instr("BLOB_VARIABLE_NAME", utl_raw.cast_to_raw('search_text'), 1, 1) > 0

Resources