Can we use LIKE operator along with MEMBER OF operator in a stored procedure? - sql

I have an array of data using which I select rows from a table. For that I use member of operator in where clause. I want to know if we can do that same but by using Like operator along with member of operator.
When my Array consists of{Delhi, Mumbai, Kolkata}
I select the rows which have these three values in their row.
This is how I do that:
select ...
Into...
From xyz where city member of array;
///Receiving the array from an in parameter of the stored procedure.
And it works perfectly fine.
But If my array has {Del, Mum, Kolk} //parts of the actual names
How do I use this array for the same purpose, maybe using Like operator.
Create or replace zz2(ar in array_collection, c out sys_refcursor)
Is
anotherabc tablename.city%type
Begin
Open c
For
Select ABC
Into anotherabc
From tablename where city member of ar;
End zz2;
I expect the output to have all the rows which have cities starting with the alphabet/characters present in the array. Using member of operator

Something like this?
Select ABC
Into anotherabc a
From tablename WHERE EXISTS
( select 1 FROM ( select column_value as city
FROM TABLE(ar) ) s where a.city like s.city||'%' )

There is no direct way to use LIKE with MEMBER OF.
If It is the protocol that your collection contains the first three characters of the city name then you can use substr() to match only the first three characters in MEMBER OF.
try the following thing:
DECLARE
TYPE t_tab IS TABLE OF varchar(3);
l_tab1 t_tab := t_tab('Del','Mom','Kol');
BEGIN
DBMS_OUTPUT.put('Is ''Delhi'' MEMBER OF l_tab1? ');
IF SUBSTR('Delhi',1,3) MEMBER OF l_tab1 THEN -- note the use of SUBSTR here
DBMS_OUTPUT.put_line('TRUE');
ELSE
DBMS_OUTPUT.put_line('FALSE');
END IF;
END;
/
db<>fiddle demo
Cheers!!

Related

ORA-01460 unimplemented or unreasonable conversion requested

I am passing ID's in oracle proc. ID's can be in 1000's. currently its able to process around 600 ID's, if I pass more than 600 ID's - I am getting ORA-01460 unimplemented or unreasonable conversion requested. ID is varchar2 datatype, how can I process 1000's of Id's in varchar2 or what will be the best strategy to handle this kind of issue. Any guidance/suggestion will be highly appreciated. Can this be solved using CLOB datatype?
//this is how I am processing Id's
create or replace procedure Emp(
emp_id in varchar2
)
//passing those id's in CTE before passing to subquery
WITH
EMP_LIST AS(
select regexp_substr(emp_id,'[^,]+', 1, level) from dual
connect by level <= LENGTH(regexp_substr(emp_id, '[^,]+'))+1
)
Pass a collection or VARRAY rather than passing a comma-delimited string:
CREATE TYPE number_list IS TABLE OF NUMBER(10,0);
Then you can use it something like:
CREATE PROCEDURE emp(
emp_ids IN number_list
)
IS
BEGIN
-- Do something with the ids like inserting them into a table
INSERT INTO employees ( id )
SELECT COLUMN_VALUE
FROM TABLE( emp_ids );
-- Or something like this:
SELECT something
INTO some_variable -- you need to define this variable first
FROM some_table
WHERE emp_id MEMBER OF emp_ids;
END;
/
Update
If you can't create anything then you can use a built-in collection like SYS.ODCINUMBERLIST:
CREATE PROCEDURE emp(
emp_ids IN SYS.ODCINUMBERLIST
)
IS
BEGIN
-- Do something with the ids like inserting them into a table
INSERT INTO employees ( id )
SELECT COLUMN_VALUE
FROM TABLE( emp_ids );
-- Or something like this:
SELECT something
INTO some_variable -- you need to define this variable first
FROM some_table
WHERE emp_id IN ( SELECT COLUMN_VALUE FROM TABLE( emp_ids ) );
END;
/
(Note: SYS.ODCI*LIST types are VARRAY data types and do not support the MEMBER OF operator like collections do; instead you can get the values from the VARRAY using a nested SELECT statement with a TABLE() collection expression.)
However, if you really can't CREATE anything then you won't be able to CREATE PROCEDURE .... not sure there is any solution to that apart from talking to your DBA.

Convert a piece of SQL into an Oracle function

My table "COMMA_SEPERATED" looks something like this
ID NAME CITY
--- ---- -----------------------------
1 RAJ CHENNAI, HYDERABAD, JABALPUR
2 SAM BHOPAL,PUNE
I want to separate each City as a new record so my SQL is: (working fine)
SELECT id, TRIM(CITY_NEW)
FROM COMMA_SEPERATED, xmltable
(
'if (contains($X,",")) then ora:tokenize($X,"\,") else $X'
passing city AS X
columns CITY_NEW varchar2(4000) path '.'
);
I want to convert this piece of SQL into a function so that i can simply call the function like this
SELECT id, split_function(city) FROM COMMA_SEPERATED
Output:
1 CHENNAI
1 HYDERABAD
1 JABALPUR
2 BHOPAL
2 PUNE
Can anyone help on how to do that? I am very new to PL/SQL.
The query you're trying to get to:
SELECT id, split_function(city) FROM COMMA_SEPERATED
won't work, because you're trying to return multiple rows for each source row. You have to make it a bit more complicated than that unfortunately.
If the goal is to hide the splitting mechanism then the closest I can think of is to create a function which returns a collection of strings, which could be pipelined:
create or replace function split_function (p_string varchar2)
return sys.odcivarchar2list pipelined as
begin
for r in (
select result
from xmltable (
'if (contains($X,",")) then ora:tokenize($X,"\,") else $X'
passing p_string as x
columns result varchar2(4000) path '.'
)
)
loop
pipe row (trim(r.result));
end loop;
end split_function;
/
Your proposed call would then give you one row per ID with a collection:
select id, split_function(city) from comma_seperated;
ID SPLIT_FUNCTION(CITY)
---------- -----------------------------------------------------------------
1 ODCIVARCHAR2LIST('CHENNAI', 'HYDERABAD', 'JABALPUR')
2 ODCIVARCHAR2LIST('BHOPAL', 'PUNE')
which isn't quite what you want; but you can use a table collection expression and cross-join to convert into multiple rows instead:
select cs.id, t.column_value as city
from comma_seperated cs
cross join table(split_function(cs.city)) t;
ID CITY
---------- ------------------------------
1 CHENNAI
1 HYDERABAD
1 JABALPUR
2 BHOPAL
2 PUNE
db<>fiddle demo.
That isn't as simple as you hoped for, but is arguably still better than cross-joining to the xmltable(), particularly if you want to reuse that splitting logic/function in multiple places, as well as hide the details of how the split is done - which would let you change the mechanism easily if you wanted to, e.g. to use a more common regular expression to do the splitting.
Apart what #Alex showed, you can also create an object and get the object returned via function. See below:
--Created an object to hold your result columns
create or replace type Obj IS OBJECT (id number, city varchar2(20));
/
--Table of object
create or replace type var_obj is table of Obj;
/
--Function with return as with Object type.
create or replace function splt_fnct
return var_obj
as
var var_obj:=var_obj();
begin
Select obj(col,col1)
bulk collect into var
from (
Select distinct col , regexp_substr(col1,'[^,]+',1,level) col1
from tbl
connect by regexp_substr(col1,'[^,]+',1,level) is not null
order by 1);
return var;
end;
/
--Selecting result
select * from table(splt_fnct);
Edit: I was trying with #Alex solution and got some error as shown below:
create or replace function splt_fnct(input_strng varchar2)
return var_obj
as
var var_obj:=var_obj();
begin
Select obj(col,col1)
bulk collect into var
from (
select tbl.col, t.rslt --<--This column name should the same as used in colmns clause in the below query. Its giving error "invalid column". How to handle this case.
FROM tbl, xmltable
(
'if (contains($X,",")) then ora:tokenize($X,"\,") else $X'
passing col1 AS X
columns input_strng varchar2(4000) path '.'
) t
);
return var;
end;
/
Correction as per #Alex suggestion:
create or replace function splt_fnct(input_strng varchar2)
return var_obj
as
var var_obj:=var_obj();
begin
select obj(tbl.col, t.rslt)
bulk collect into var
FROM tbl, xmltable
(
'if (contains($X,",")) then ora:tokenize($X,"\,") else $X'
passing input_strng AS X
columns rslt varchar2(4000) path '.'
) t;
return var;
end;
/

Where column in <function returns a collection > in oracle

I want to achieve something like this:
Example:
Table Customers has columns customer_no, name , age.
some_package package has the following types defined in its spec:
type cust_type is record (custs Customers.customer_no);
type rec_type is table of cust_type index by binary_integer;
function some_function return rec_type;
I am trying to create a view the goes like this:
select ....
from customers c, tablex, tabley
where c.customer_no in some_function() and
... <<other clauses>>
I cannot avoid using some_function() as the logic uses dynamic SQL statements.
I get invalid data type error when i try to compile the view
Is it possible to achieve this in Oracle sql? I don't want to use another function and loops to do this.
Thanks.
No, type rec_type is declared in a package and can be used in PL/SQL blocks but cannot be used in SQL statements.
If you want to use a type in SQL statements then you will need to declare it using a CREATE TYPE statement like this:
CREATE TYPE customers_tab IS TABLE OF NUMBER;
or you can use an existing type like SYS.ODCINUMBERLIST.
Then change the package to:
CREATE OR REPLACE PACKAGE some_package
AS
FUNCTION some_function RETURN SYS.ODCINUMBERLIST;
END;
/
CREATE OR REPLACE PACKAGE BODY some_package
AS
FUNCTION some_function RETURN SYS.ODCINUMBERLIST
AS
t_customers SYS.ODCINUMBERLIST;
BEGIN
SELECT customer_no
BULK COLLECT INTO t_customers
FROM customers
WHERE MOD( customer_no, 3 ) = 0; -- or whatever your query is.
RETURN t_customers;
END;
END;
/
Then you can do:
SELECT *
FROM Customers c
INNER JOIN
TABLE( some_package.some_function() ) t
ON ( c.customer_no = t.COLUMN_VALUE );

How to use in statement with nested table

Hey there I have a function, and part of the function is to make sure that the selected value is within the passed in table of varchar2s. To start I declare a varchar2 table type like so.
create or replace type Varchar2Table is table of varchar2(200)
Then I have the function which accepts the nested table parameter and has a select statement on them.
function SelectPeople(inputNames Varchar2Table) return People
begin
--stuff
select * from person_table where name in inputNames; --line of interest
--more stuff
end;
This doesn't seem to work though, I get the following error:
ORA-00932: inconsistent datatypes: expected NUMBER got
ENGSPL5.VARCHAR2TABLE
Any suggestions?
The TABLE operator allows nested tables to be used in SQL statements. The function was also missing an IS and an INTO.
create or replace type Varchar2Table is table of varchar2(200);
create table person_table(id number, name varchar2(100));
create or replace function SelectPeople(inputNames Varchar2Table) return number
is --Missing "IS".
type numberTable is table of number; --Need a collection to store results.
numbers numberTable;
begin
select id
bulk collect into numbers --Missing "INTO".
from person_table
where name in (select column_value from table(inputNames)); --Missing "TABLE".
--Alternatively a multiset condition can be used.
--where name member of inputNames;
--Dummy return value to make the function compile.
return 1;
end;
/

Select multiple rows as array

I have a table where two people may have the same name, but separate IDs. I am given the name, and need to request their IDs.
When I use a command like:
SELECT id_num INTO cust_id FROM Customers WHERE name=CName;
If I use this command on the command line (psql), it returns 2 results (for example).
But when I use it as part of an SQL script (PL/pgSQL), it always just grabs the first instance.
I tried selecting into cust_id[], but that produced an error. So what is the proper way to select ALL results, and pump them into an array or another easy way to use them?
In declare
DECLARE id_nums bigint[];
in select
id_nums := ARRAY(select cust_id from Customers WHERE name = CName);
If you prefer loop use
DECLARE id_num bigint;
FOR id_num in select cust_id from Customers WHERE name = CName LOOP
your code here
END LOOP;
Read plpgsql control structures in postgresql docs 9.1.
To put data from individual rows into an array, use an array constructor:
DECLARE id_nums int[]; -- assuming cust_id is of type int
id_nums := ARRAY (SELECT cust_id FROM customers WHERE name = cname);
Or the aggregate function array_agg()
id_nums := (SELECT array_agg(cust_id) FROM customers WHERE name = cname);
Or use SELECT INTO for the assignment::
SELECT INTO id_nums
ARRAY (SELECT cust_id FROM customers WHERE name = cname);