Oracle Invalid Number error while passing comma separated numbers - oracle

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;
/

Related

why this simple programm creates an end-of-file on communicatoin channel error

CREATE OR REPLACE TYPE a IS OBJECT
(
b integer,
c varchar2(10)
);
/
declare
cursor ca return a is select 1,'e' from dual;
va a;
begin
null;
for cur in ca
loop
DBMS_OUTPUT.PUT_LINE('do nothing');
end loop;
end;
ORA-03113: end-of-file on communication channel
Process ID: 803778
Session ID: 64 Serial number: 4181
the loop as only one element and fast nothing is done in the loop.
But I get the error end-of-file communication channel
As #littlefoot said it works fine if I use a record defined in a package or no record at all. I don't know why it doesn't work with an object
code
Simply put, you can't do that.
Oracle does not support returning object types from a cursor. Cursors *always* return a rowtype. So to do something similar to what you want, you can define a table which has a column of the object type you're interested in, and then have the cursor return the rowtype of that table - i.e. something like:
CREATE OR REPLACE TYPE a IS OBJECT
(
b integer,
c varchar2(10)
);
CREATE TABLE tt
(t_a a);
INSERT INTO tt VALUES (a(1, 'e'));
declare
cursor ca return tt%ROWTYPE is select t_a from tt;
an_a a;
begin
FOR aRow IN ca LOOP
an_a := aRow.t_a;
dbms_output.put_line('an_a.b=' || an_a.b);
dbms_output.put_line('an_a.c=''' || an_a.c || '''');
end loop;
end;
db<>fiddle here
You never said what a is.
Documentation says that - if you use return clause, then it returns rowtype.
RETURN: Specifies the datatype of a cursor return value. You can use the %ROWTYPE attribute in the RETURN clause to provide a record type that represents a row in a database table or a row returned by a previously declared cursor. Also, you can use the %TYPE attribute to provide the datatype of a previously declared record.
A cursor body must have a SELECT statement and the same RETURN clause as its corresponding cursor spec. Also, the number, order, and datatypes of select items in the SELECT clause must match the RETURN clause.
ROWTYPE: A record type that represents a row in a database table or a row fetched from a previously declared cursor or cursor variable. Fields in the record and corresponding columns in the row have the same names and datatypes.
So, if your code were like this, it would work:
SQL> set serveroutput on
SQL>
SQL> declare
2 type a is record(val1 number, val2 varchar2(10));
3
4 cursor ca return a is select 1 ,'e' from dual;
5 va a;
6 begin
7 for cur in ca
8 loop
9 DBMS_OUTPUT.PUT_LINE(cur.val1 ||', '|| cur.val2 ||', do nothing');
10 end loop;
11 end;
12 /
1, e, do nothing
PL/SQL procedure successfully completed.
SQL>
Or, simpler, if you remove the return clause from your own code, it would also work:
SQL> declare
2 cursor ca --return a
3 is select 1,'e' from dual;
4 va a;
5 begin
6 null;
7 for cur in ca
8 loop
9 DBMS_OUTPUT.PUT_LINE('do nothing');
10 end loop;
11 end;
12 /
do nothing
PL/SQL procedure successfully completed.
SQL>
If you're asking what's the reason of end-of-file on communication channel, I wouldn't know.

how to look up another field in Oracle Function

Table 1
ID
----------
1
2
3
4
5
Table 2
ID Desc
------------------------------
A1 Apple
A2 Pear
A3 Orange
I am trying to create a Function in Oracle, so that it add the prefix 'A' in Table 1, and after that I want to look up in Table 2 to get the DESC returned. It has to be a function.
Thank you!!!
You may use the following for creation of such a function :
Create or Replace Function Get_Fruit( i_id table2.description%type )
Return table2.description%type Is
o_desc table2.description%type;
Begin
for c in ( select description from table2 where id = 'A'||to_char(i_id) )
loop
o_desc := c.description;
end loop;
return o_desc;
End;
where
no need to include exception handling, because of using cursor
instead of select into clause.
using table_name.col_name%type for declaration of data types for
arguments or variables makes the related data type of the columns
dynamic. i.e. those would be able to depend on the data type of the
related columns.
the reserved keywords such as desc can not be used as column names
of tables, unless they're expressed in double quotes ("desc")
To call that function, the following might be preferred :
SQL> set serveroutput on
SQL> declare
2 i_id pls_integer := 1;
3 o_fruit varchar2(55);
4 begin
5 o_fruit := get_fruit( i_id );
6 dbms_output.put_line( o_fruit );
7 end;
8 /
Apple
PL/SQL procedure successfully completed
I am not sure with your question- Are you trying to achieve something like this:-
CREATE OR REPLACE FUNCTION Replace_Value
(
input_ID IN VARCHAR2
) RETURN VARCHAR2
AS
v_ID varchar(2);
BEGIN
begin
SELECT distinct a.ID into v_id from Table 2 a where a.ID in (select 'A'||b.id from table1 b where b.id=input_ID);
exception
when others then
dbms_output.put_line(sqlcode);
end;
RETURN v_id;
END Replace_Value;
Are you trying for something like this?
CREATE OR replace FUNCTION replace_value (table_name IN VARCHAR2,
input_id IN INTEGER)
RETURN VARCHAR2
AS
v_desc VARCHAR(20);
BEGIN
SELECT descr
INTO v_desc
FROM table2
WHERE id = 'A' || input_id
AND ROWNUM = 1; -- only needed if there are multiple rows for each id.
RETURN v_desc;
END replace_value;
You may also add an exception handling for NO_DATA_FOUND or INVALID_NUMBER

Pass an array to bind variables

I have a table that contains queries, for example:
select text from queries;
TEXT
1 select item from items where item_no between :low_item_no and :high_item_no and description <> :irellevant
The queries already contains the place holders for the bind variables.
The values themselves exists in variables table:
select * from vars;
ID NAME VALUE
1 1 low_item_no 100
2 2 high_item_no 300
3 3 irellevant_desc old
I have a package that takes the query and execute it with
execute immediate statement
but how do I bind those variables?
I don't know how much variables I have in such query, it's not static.
I wish to have a way to do something like that:
Execute immedaite my_query_str using v_array_of_vars;
Until now I don't know of a way to do something like that, only with list of variables for example:
Execute immedaite my_query_str using v_1, v_2, v_3;
Thanks!
I don't think you can do this with execute immediate as too much is unknown at compile time, so you'll have to use the dbms_sql package instead.
Here's a quick demo that gets the query and variables based on a common query ID. This assumes that the values in vars.name actually match the bind variable names in queries.text, and I haven't included any checks or error handling for that or other potential issues, or dealt with multiple select-list items or data types - just the basics:
declare
my_query_str queries.text%type;
my_cursor pls_integer;
my_result pls_integer;
my_col_descs dbms_sql.desc_tab2;
my_num_cols pls_integer;
my_item items.item%type;
begin
select text into my_query_str from queries where query_id = 42;
dbms_output.put_line(my_query_str);
-- open cursor
my_cursor := dbms_sql.open_cursor;
-- parse this query
dbms_sql.parse(my_cursor, my_query_str, dbms_sql.native);
-- bind all variables by name; assumes bind variables match vars.name
for r in (select name, value from vars where query_id = 42) loop
dbms_output.put_line('Binding ' || r.name || ' || with <' || r.value ||'>');
dbms_sql.bind_variable(my_cursor, r.name, r.value);
end loop;
my_result := dbms_sql.execute(my_cursor);
dbms_output.put_line('execute got: ' || my_result);
dbms_sql.describe_columns2(my_cursor, my_num_cols, my_col_descs);
dbms_sql.define_column(my_cursor, 1, my_item, 30); -- whatever size matches 'item'
-- fetch and do something with the results
while true loop
my_result := dbms_sql.fetch_rows(my_cursor);
if my_result <= 0 then
exit;
end if;
dbms_sql.column_value(my_cursor, 1, my_item);
dbms_output.put_line('Got item: ' || my_item);
end loop;
dbms_sql.close_cursor(my_cursor);
end;
/
You don't seem to really need an array; but if you wanted to you could create and populate an associative array as name/value pairs and then use that fir the binds.
This is just a starting point; you may have to deal with an unknown number and/or types of columns being returned, though if that's the case processing them meaningfully will be a challenge. Perhaps you need to return the result of the query as a ref cursor, which is even simpler; demo using the SQL*Plus variable and print commands:
var rc refcursor;
declare
my_query_str queries.text%type;
my_cursor pls_integer;
my_result pls_integer;
begin
select text into my_query_str from queries where query_id = 42;
dbms_output.put_line(my_query_str);
-- open cursor
my_cursor := dbms_sql.open_cursor;
-- parse this query
dbms_sql.parse(my_cursor, my_query_str, dbms_sql.native);
-- bind all variables by name; assumes bind variables match vars.name
for r in (select name, value from vars where query_id = 42) loop
dbms_output.put_line('Binding ' || r.name || ' || with <' || r.value ||'>');
dbms_sql.bind_variable(my_cursor, r.name, r.value);
end loop;
my_result := dbms_sql.execute(my_cursor);
dbms_output.put_line('execute got: ' || my_result);
:rc := dbms_sql.to_refcursor(my_cursor);
end;
/
print rc
Notice you don't close the cursor inside the PL/SQL block in this scenario.
You could also convert to a ref cursor and then fetch from that within your procedure - there's a bulk-collect example in the docs - but again you'd need to know the number and types of the select-list items to do that.

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

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.

How can I populate in memory tables, in PLSQL?

I don't know if it is the correct terminology but I call "in memory tables" to the objects created like this:
create type InMemReg is object (field1 varchar2(10), field2 varchar2(20), field3 number);
create type InMemTab is table of InMemReg;
In this case my "in memory table" is "InMemTab". My question is how can I populate this kind of object, when i don't know previously the numbers of elements? I have seen in some places this type of initialization:
declare
v_uno InMemReg := InMemReg('a','b',1999);
v_dos InMemReg := InMemReg('A','Z',2000);
t_tres InMemTab := InMemTab();
begin
t_tres := InMemTab(v_uno, v_dos);
In this situation I have explicitly 2 objects before initialize "t_tres", but in a dynamic scenario where I could have n numbers of elements I don't know how to populate it.
In another OO language could be something like this:
t_tres.add(OtherObject)
The type InMemTab is a nested table in Oracle parlance.
The equivalent to the add method would be to call the extend method and then to assign OtherObject to the last position in the nested table.
SQL> ed
Wrote file afiedt.buf
1 declare
2 v_uno InMemReg := InMemReg('a','b',1999);
3 v_dos InMemReg := InMemReg('A','Z',2000);
4 t_tres InMemTab := InMemTab();
5 begin
6 t_tres.extend;
7 t_tres( t_tres.count ) := v_uno;
8 t_tres.extend;
9 t_tres( t_tres.count ) := v_dos;
10 dbms_output.put_line( 't_tres has ' || t_tres.count || ' elements.' );
11* end;
12 /
t_tres has 2 elements.
PL/SQL procedure successfully completed.
You can factor that out into an add procedure as well
SQL> ed
Wrote file afiedt.buf
1 declare
2 v_uno InMemReg := InMemReg('a','b',1999);
3 v_dos InMemReg := InMemReg('A','Z',2000);
4 t_tres InMemTab := InMemTab();
5 procedure add( p_nt IN OUT InMemTab,
6 p_elem IN InMemReg )
7 as
8 begin
9 p_nt.extend;
10 p_nt( p_nt.count ) := p_elem;
11 end;
12 begin
13 add( t_tres, v_uno );
14 add( t_tres, v_dos );
15 dbms_output.put_line( 't_tres has ' || t_tres.count || ' elements.' );
16* end;
17 /
t_tres has 2 elements.
PL/SQL procedure successfully completed.
It is common to populate the collection from the data itself, meaning you are not explicitly adding sets of strings and numbers, you're pulling the data in from other tables. Because this is a common and natural thing to do with collections, Oracle made it easy via "BULK COLLECT INTO" clause in pl/sql. For example:
DECLARE
TYPE EmployeeSet IS TABLE OF employees%ROWTYPE;
underpaid EmployeeSet;
-- Holds set of rows from EMPLOYEES table.
CURSOR c1 IS SELECT first_name, last_name FROM employees;
TYPE NameSet IS TABLE OF c1%ROWTYPE;
some_names NameSet;
-- Holds set of partial rows from EMPLOYEES table.
BEGIN
-- With one query,
-- bring all relevant data into collection of records.
SELECT * BULK COLLECT INTO underpaid FROM employees
WHERE salary < 5000 ORDER BY salary DESC;
-- Process data by examining collection or passing it to
-- eparate procedure, instead of writing loop to FETCH each row.
DBMS_OUTPUT.PUT_LINE
(underpaid.COUNT || ' people make less than 5000.');
FOR i IN underpaid.FIRST .. underpaid.LAST
LOOP
DBMS_OUTPUT.PUT_LINE
(underpaid(i).last_name || ' makes ' || underpaid(i).salary);
END LOOP;
-- You can also bring in just some of the table columns.
-- Here you get the first and last names of 10 arbitrary employees.
SELECT first_name, last_name
BULK COLLECT INTO some_names
FROM employees
WHERE ROWNUM < 11;
FOR i IN some_names.FIRST .. some_names.LAST
LOOP
DBMS_OUTPUT.PUT_LINE
('Employee = ' || some_names(i).first_name
|| ' ' || some_names(i).last_name);
END LOOP;
END;
/
You don't typically need to worry about extending or how many elements you'll have, you can usually slurp it in and then use the built in features of the collection as you like (counts, loop through, compare different collections, set operations, etc)

Resources