PL-SQL stored procedure split string - sql

What I would like to do is this:
In java I have an array like ['AB','BC','CD','DE'] which I want to concat to something like "AB,BC,CD,DE" and send it to the procedure as an argument.
In the procedure, my idea is, I would like to do something like
v_passedArgs --(AB,BC,CD,DE)
SELECT * FROM SOME_TABLE WHERE SOME_COL IN (v_passedArgs.split(','))
Is it possible to do something like that or maybe you have another idea?
Thank you

You can create a split function in the database and use it for splitting a string in SQL which has delimiters(',' in your example)
Is there a function to split a string in PL/SQL?
Refer the above link to create a split function and use it in your select statement

You have to create your own function.
You can work with an Oracle PL/SQL collection; here is a piece of code to return such a collection, from an input string list (p_list) with a given separator (p_sep):
CREATE OR REPLACE TYPE t_my_list AS TABLE OF VARCHAR2(100);
CREATE OR REPLACE
FUNCTION cto_table(p_sep in Varchar2, p_list IN VARCHAR2)
RETURN t_my_list
AS
l_string VARCHAR2(32767) := p_list || p_sep;
l_sep_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
l_tab t_my_list := t_my_list();
BEGIN
LOOP
l_sep_index := INSTR(l_string, p_sep, l_index);
EXIT
WHEN l_sep_index = 0;
l_tab.EXTEND;
l_tab(l_tab.COUNT) := TRIM(SUBSTR(l_string,l_index,l_sep_index - l_index));
l_index := l_sep_index + 1;
END LOOP;
RETURN l_tab;
END cto_table;
/
Then how to use it with the TABLE keyword in your SELECT - the TABLE keyword converts the collection into a object usable inside Oracle SQL queries:
SELECT * FROM SOME_TABLE WHERE SOME_COL IN (
select * from TABLE(cto_table(',', v_passedArgs))
)

Related

Return SQL Array from string [duplicate]

I'd like to create an in-memory array variable that can be used in my PL/SQL code. I can't find any collections in Oracle PL/SQL that uses pure memory, they all seem to be associated with tables. I'm looking to do something like this in my PL/SQL (C# syntax):
string[] arrayvalues = new string[3] {"Matt", "Joanne", "Robert"};
Edit:
Oracle: 9i
You can use VARRAY for a fixed-size array:
declare
type array_t is varray(3) of varchar2(10);
array array_t := array_t('Matt', 'Joanne', 'Robert');
begin
for i in 1..array.count loop
dbms_output.put_line(array(i));
end loop;
end;
Or TABLE for an unbounded array:
...
type array_t is table of varchar2(10);
...
The word "table" here has nothing to do with database tables, confusingly. Both methods create in-memory arrays.
With either of these you need to both initialise and extend the collection before adding elements:
declare
type array_t is varray(3) of varchar2(10);
array array_t := array_t(); -- Initialise it
begin
for i in 1..3 loop
array.extend(); -- Extend it
array(i) := 'x';
end loop;
end;
The first index is 1 not 0.
You could just declare a DBMS_SQL.VARCHAR2_TABLE to hold an in-memory variable length array indexed by a BINARY_INTEGER:
DECLARE
name_array dbms_sql.varchar2_table;
BEGIN
name_array(1) := 'Tim';
name_array(2) := 'Daisy';
name_array(3) := 'Mike';
name_array(4) := 'Marsha';
--
FOR i IN name_array.FIRST .. name_array.LAST
LOOP
-- Do something
END LOOP;
END;
You could use an associative array (used to be called PL/SQL tables) as they are an in-memory array.
DECLARE
TYPE employee_arraytype IS TABLE OF employee%ROWTYPE
INDEX BY PLS_INTEGER;
employee_array employee_arraytype;
BEGIN
SELECT *
BULK COLLECT INTO employee_array
FROM employee
WHERE department = 10;
--
FOR i IN employee_array.FIRST .. employee_array.LAST
LOOP
-- Do something
END LOOP;
END;
The associative array can hold any make up of record types.
Hope it helps,
Ollie.
You can also use an oracle defined collection
DECLARE
arrayvalues sys.odcivarchar2list;
BEGIN
arrayvalues := sys.odcivarchar2list('Matt','Joanne','Robert');
FOR x IN ( SELECT m.column_value m_value
FROM table(arrayvalues) m )
LOOP
dbms_output.put_line (x.m_value||' is a good pal');
END LOOP;
END;
I would use in-memory array. But with the .COUNT improvement suggested by uziberia:
DECLARE
TYPE t_people IS TABLE OF varchar2(10) INDEX BY PLS_INTEGER;
arrayvalues t_people;
BEGIN
SELECT *
BULK COLLECT INTO arrayvalues
FROM (select 'Matt' m_value from dual union all
select 'Joanne' from dual union all
select 'Robert' from dual
)
;
--
FOR i IN 1 .. arrayvalues.COUNT
LOOP
dbms_output.put_line(arrayvalues(i)||' is my friend');
END LOOP;
END;
Another solution would be to use a Hashmap like #Jchomel did here.
NB:
With Oracle 12c you can even query arrays directly now!
Another solution is to use an Oracle Collection as a Hashmap:
declare
-- create a type for your "Array" - it can be of any kind, record might be useful
type hash_map is table of varchar2(1000) index by varchar2(30);
my_hmap hash_map ;
-- i will be your iterator: it must be of the index's type
i varchar2(30);
begin
my_hmap('a') := 'apple';
my_hmap('b') := 'box';
my_hmap('c') := 'crow';
-- then how you use it:
dbms_output.put_line (my_hmap('c')) ;
-- or to loop on every element - it's a "collection"
i := my_hmap.FIRST;
while (i is not null) loop
dbms_output.put_line(my_hmap(i));
i := my_hmap.NEXT(i);
end loop;
end;
Sample programs as follows and provided on link also https://oracle-concepts-learning.blogspot.com/
plsql table or associated array.
DECLARE
TYPE salary IS TABLE OF NUMBER INDEX BY VARCHAR2(20);
salary_list salary;
name VARCHAR2(20);
BEGIN
-- adding elements to the table
salary_list('Rajnish') := 62000; salary_list('Minakshi') := 75000;
salary_list('Martin') := 100000; salary_list('James') := 78000;
-- printing the table name := salary_list.FIRST; WHILE name IS NOT null
LOOP
dbms_output.put_line ('Salary of ' || name || ' is ' ||
TO_CHAR(salary_list(name)));
name := salary_list.NEXT(name);
END LOOP;
END;
/
Using varray is about the quickest way to duplicate the C# code that I have found without using a table.
Declare your public array type to be use in script
type t_array is varray(10) of varchar2(60);
This is the function you need to call - simply finds the values in the string passed in using a comma delimiter
function ConvertToArray(p_list IN VARCHAR2)
RETURN t_array
AS
myEmailArray t_array := t_array(); --init empty array
l_string varchar2(1000) := p_list || ','; - (list coming into function adding final comma)
l_comma_idx integer;
l_index integer := 1;
l_arr_idx integer := 1;
l_email varchar2(60);
BEGIN
LOOP
l_comma_idx := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_idx = 0;
l_email:= SUBSTR(l_string, l_index, l_comma_idx - l_index);
dbms_output.put_line(l_arr_idx || ' - ' || l_email);
myEmailArray.extend;
myEmailArray(l_arr_idx) := l_email;
l_index := l_comma_idx + 1;
l_arr_idx := l_arr_idx + 1;
END LOOP;
for i in 1..myEmailArray.count loop
dbms_output.put_line(myEmailArray(i));
end loop;
dbms_output.put_line('return count ' || myEmailArray.count);
RETURN myEmailArray;
--exception
--when others then
--do something
end ConvertToArray;
Finally Declare a local variable, call the function and loop through what is returned
l_array t_array;
l_Array := ConvertToArray('email1#gmail.com,email2#gmail.com,email3#gmail.com');
for idx in 1 .. l_array.count
loop
l_EmailTo := Trim(replace(l_arrayXX(idx),'"',''));
if nvl(l_EmailTo,'#') = '#' then
dbms_output.put_line('Empty: l_EmailTo:' || to_char(idx) || l_EmailTo);
else
dbms_output.put_line
( 'Email ' || to_char(idx) ||
' of array contains: ' ||
l_EmailTo
);
end if;
end loop;

PL/SQL Check if a variable is in a list from Execute Immediate

In a function, I'm trying to check if a variable is in a list (this list is from a EXECUTE IMMEDIATE), but with no success, I couldn't find the right syntax for that.
I also tried this solutions on PL/SQL - Use “List” Variable in Where In Clause, How to pass varchar with single quotes to Stored Proc in Oracle [duplicate] and How to add values to a VARRAY using a loop, but with no success.
I created the TABLE TYPE as below, but I don't know if it's the right way of doing this.
CREATE OR REPLACE TYPE DS_FUNCESP.TP_BI_LIST AS TABLE OF VARCHAR2(4000)
What I tried:
CREATE OR REPLACE FUNCTION DS_FUNCESP.FNBIGB_CheckDataMissing2
(pOwn IN VARCHAR2, pTab IN VARCHAR2, pCol IN VARCHAR2) RETURN NUMBER IS
v_str VARCHAR2(2000);
BEGIN
v_Results := DS_FUNCESP.TP_BI_LIST();
v_Qtd := 1;
v_str := ' SELECT ''TEST1'' AS NM_COLUMN FROM DUAL UNION ALL
SELECT ''TEST2'' AS NM_COLUMN FROM DUAL UNION ALL
SELECT ''TEST3'' AS NM_COLUMN FROM DUAL';
EXECUTE IMMEDIATE v_str into v_Results;
-- I tried to show the TABLE TYPE to check if I was in the right way
DBMS_OUTPUT.PUT_LINE('Total rows: '||v_Results.COUNT);
-- Check if variable is in a table/array/list (which one is the right one?)
IF pCol IN (v_Results) THEN dbms_output.put_line('YES'); ELSE dbms_output.put_line('NO');
END IF;
EXCEPTION WHEN OTHERS THEN
RETURN 0;
END FNBIGB_CheckDataMissing2;
The code above doesn't work, it gives the error:
PLS-00383: type mismatch found at 'PCOL' inside an IN or NOT IN clause
How can I do this? What is the best way?
Use the MEMBER OF operator:
CREATE FUNCTION DS_FUNCESP.FNBIGB_CheckDataMissing2 (
pOwn IN VARCHAR2,
pTab IN VARCHAR2,
pCol IN VARCHAR2
) RETURN NUMBER
IS
v_str VARCHAR2(2000);
v_results DS_FUNCESP.TP_BI_LIST;
BEGIN
v_str := ' SELECT ''TEST1'' AS NM_COLUMN FROM DUAL UNION ALL
SELECT ''TEST2'' AS NM_COLUMN FROM DUAL UNION ALL
SELECT ''TEST3'' AS NM_COLUMN FROM DUAL';
EXECUTE IMMEDIATE v_str BULK COLLECT INTO v_Results;
DBMS_OUTPUT.PUT_LINE('Total rows: '||v_Results.COUNT);
IF pCol MEMBER OF v_Results THEN
dbms_output.put_line('YES');
ELSE
dbms_output.put_line('NO');
END IF;
RETURN 1;
END;
/
(Also, correcting v_Columas to v_Results and EXECUTE IMMEDIATE ... INTO to EXECUTE IMMEDIATE ... BULK COLLECT INTO, declaring the variables and adding a RETURN statement.)
db<>fiddle

Passing values to IN clause on a function oracle

I need to return a cursor within a function:
CREATE OR REPLACE FUNCTION test_cursor (
bigstring IN VARCHAR2
)
RETURN cursor
IS
row_test table_colors := table_colors(bigstring);
c1 CURSOR;
BEGIN
OPEN c1 FOR
select * from cars where color IN (select column_value
from table(row_test));
RETURN c1;
END test_cursor;
table_colors is:
create or replace type table_colors as table of varchar2(20);
But when I test it passing like blue, red, pink, white or 'blue', 'red', 'pink', 'white' always throws the same error
ORA-06502: PL/SQL; numeric or value error: character string buffer too small
on this line row table_colors := table_colors(bigstring);
What I am doing wrong here?
The problem is that bigstring is a single scalar value that may happen to contain commas and single quotes not a list of values. You would need to parse the string to extract the data elements. If each of the individual elements within bigstring happens to be a valid Oracle identifier, you could use the built-in dbms_utility.comma_to_table function. Were it my system, though, I'd feel more comfortable with my own parsing function. Assuming that bigstring is just a comma-separated list, I'd use a version of Tom Kyte's str2tbl function
create or replace function str2tbl( p_str in varchar2 )
return table_colors
as
l_str long default p_str || ',';
l_n number;
l_data table_colors := table_colors();
begin
loop
l_n := instr( l_str, ',' );
exit when (nvl(l_n,0) = 0);
l_data.extend;
l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
l_str := substr( l_str, l_n+1 );
end loop;
return l_data;
end;
Now, you can realistically implement str2tbl using regular expressions in a single SQL statement as well. That might be a touch more efficient. I'd expect, however, that string parsing is well down on your list of performance issues so I would tend to stick with the simplest thing that could possibly work.
Your procedure would then become
CREATE OR REPLACE FUNCTION test_cursor (
bigstring IN VARCHAR2
)
RETURN sys_refcursor
IS
row_test table_colors := str2tbl(bigstring);
c1 sys_refcursor;
BEGIN
OPEN c1 FOR
select * from cars where color IN (select column_value
from table(row_test));
RETURN c1;
END test_cursor;
Please show the definition of table_colors. It appears that table_colors(bigstring) is returning a value incompatible with assignment to table_colors.
As a matter of good practice, initialization of non trivial values should be done inside of the begin ... end rather than in the definition section. That allows you to trap the error within the function or procedure rather than the error cascading outwards. For example, rather than:
IS
row_test table_colors := table_colors(bigstring);
c1 CURSOR;
BEGIN ...
You should use
IS
row_test table_colors;
c1 CURSOR;
BEGIN
row_test := row_test;
...

PLSQL SELECT INTO FROM parameter

I created a function that should return the max id from a table(parameter)
CREATE OR REPLACE FUNCTION getmaxid
(
P_TABLE IN VARCHAR2
)
RETURN NUMBER IS
v_maxId NUMBER(38);
BEGIN
SELECT MAX(id) INTO v_maxId FROM P_TABLE;
RETURN v_maxId;
END getmaxid
However, i keep getting the error message "ORA-00942: table or view does not exist" on this line:
SELECT MAX(id) INTO v_maxId FROM P_TABLE;
Like explained earlier, you need to use dynamic SQL to perform the operation. In this case, p_table is a variable. The solution to this is to build a string that will contain the SQL and dynamically execute it one you've build the query.
The example below uses, DUAL, but the table name is arbitrary.
Here is what you're looking for, take the function outside of the block, I left it like this so that you can test it..
DECLARE
FUNCTION getmaxid (p_table IN VARCHAR2)
RETURN NUMBER
IS
v_maxid NUMBER (38);
v_select VARCHAR2 (200);
cnt SYS_REFCURSOR;
BEGIN
v_select := 'SELECT COUNT(*) FROM ' || p_table;
DBMS_OUTPUT.put_line (v_select);
EXECUTE IMMEDIATE v_select INTO v_maxid;
RETURN v_maxid;
END getmaxid;
BEGIN
DBMS_OUTPUT.put_line (getmaxid ('DUAL'));
END;

how to sort the contents of CLOB field

I have a table and some fields are CLOB type and the content in the CLOB was delimited by some separator such as '|' and usually the content in the filed looks like this : name2|name1|name3..., actually the length of the content is more than 40000 characters, so is there any way to sort the content by asc? I want to look the content like this: name1|name2|name3...
can any body help me?
If it's even remotely possible, I'd strongly suggest you change your data model - add a details table for the names. This will solve you a lot of pain in the future.
Anyhow, if you absolutely need to store a pipe-separated list of names in your CLOB field, I'd suggest this approach:
break the CLOB into separate rows (using a pipelined function)
sort the rows
aggregate the rows into a new CLOB
A (somewhat naive and untested) implementation of this approach:
create type stringtabletype as table of varchar2(4000);
create or replace function split_CLOB(p_Value in CLOB,
p_Separator in varchar2 default '|')
return stringtabletype
pipelined as
l_Offset number default 1;
l_Str varchar2(4000);
idx number;
begin
idx := dbms_lob.instr(lob_loc => p_Value,
pattern => p_Separator,
offset => l_Offset);
dbms_output.put_line(idx);
while (idx > 0)
loop
l_Str := dbms_lob.substr(p_Value,
idx - l_Offset,
l_Offset);
pipe row(l_Str);
l_Offset := idx+1;
idx := dbms_lob.instr(p_Value,
p_Separator,
l_Offset);
dbms_output.put_line(idx);
end loop;
-- pipe remainder of string
l_Str := dbms_lob.substr(p_Value,
dbms_lob.getlength(p_Value) - l_Offset + 1,
l_Offset);
pipe row(l_str);
return;
end;
create or replace function sort_stringtabletype(p_Values in stringtabletype)
return stringtabletype as
l_Result stringtabletype;
begin
select column_value bulk collect
into l_Result
from table(p_Values)
order by column_value;
return l_Result;
end;
create or replace function stringtabletype_to_CLOB(p_Values in stringtabletype,
p_Separator in varchar2 default '|')
return CLOB as
l_Result CLOB;
begin
dbms_lob.createtemporary(l_Result, false);
for i in 1 .. p_Values.count - 1
loop
dbms_lob.writeappend(l_Result,
length(p_Values(i)),
p_Values(i));
dbms_lob.writeappend(l_Result,
length(p_Separator),
p_Separator);
end loop;
dbms_lob.writeappend(l_Result,
length(p_Values(p_Values.count)),
p_Values(p_Values.count));
return l_Result;
end;
Example usage:
select stringtabletype_to_CLOB (
sort_stringtabletype(
split_CLOB('def|abc|ghic', '|')
)
) from dual
You could then use an UPDATE statement like
update my_table
set clob_field = stringtabletype_to_CLOB (
sort_stringtabletype(
split_CLOB(my_table, '|')
)