Usually I fill arrays like this:
TYPE name_options IS VARRAY(6) OF VARCHAR2(300);
dd_name_options_c name_options;
dd_name_options_c := name_options(string1, string2, string3, string4);
But what if I have two blocks that generate strings and I want to save all the strings to one array:
-- Block 1
....
dd_name_options_c := name_options(string1, string2, string3, string4);
....
-- Block 2
....
dd_name_options_c := name_options(string5, string6);
So, in the end, the array will contain 6 strings:
string1, string2, string3, string4, string5, string6
How can I do this?
you can use extend
declare
TYPE name_options IS VARRAY(6) OF VARCHAR2(300);
dd_name_options_c name_options;
begin
dd_name_options_c := name_options('a', 'b', 'c', 'd');
dd_name_options_c.Extend(2);
dd_name_options_c(5) := 'e';
dd_name_options_c(6) := 'f';
dbms_output.put_line(dd_name_options_c.count());
end;
/
what if I have two blocks that generate strings and I want to save all the strings to one array
If the blocks are separate program units or otherwise disconnected, maybe even can be executed in different orders, you need some logic which can tell whether the target array is already populated. One solution is to use a (private) PL/SQL procedure to manage this.
procedure populate_varray
( p_tgt in out name_options
, p_new in name_options)
is
n pls_integer;
begin
if p_tgt is null
or p_tgt.count() = 0
then
p_tgt := p_new;
elsif p_new is not null
and p_new.count() > 0
then
n := p_tgt.count();
for idx in 1 .. p_new.count() loop
p_tgt.extend();
p_tgt(n + idx) := p_new(idx);
end loop;
end if;
end populate_varray;
Note: untested code, please comment below if there are errors :)
You would call it like this:
-- Block 1
....
populate_varray ( dd_name_options_c
, name_options(string1, string2, string3, string4));
....
-- Block 2
....
populate_varray (dd_name_options_c
, name_options(string5, string6) );
Related
I'm new to oracle forms and i have a problem. I have two multiple records blocks with same fields in the form.
I query data for one block, it gets populated .
After that i need to copy all rows to new block. One helpful guy suggested following, but it keeps me in endless loop. Can you please help me resolve this?
:
declare
-- local variables; should contain all items you'd want to copy
l_empno emp.empno%type;
l_ename emp.ename%type;
l_job emp.job%type;
-- l_currec will contain current row number in the first block
l_currec number := 0;
-- l_exit will be used if we're at the end of the first block
l_exit varchar2(1) := 'N';
begin
loop
-- go to the source (first block) and the [last row you were in + 1]
go_block('first');
l_currec := l_currec + 1;
go_record(l_currec);`
-- check whether this is the last row of the first block; if so, exit the loop
if :system.last_record = 'true' then
l_exit := 'Y';
end if;
-- save current row's items
l_empno := :first.empno;
l_ename := :first.ename;
l_job := :first.job
-- go to the second block's bottom and create a new record
go_block('second');
last_record;
create_record;
-- put stored values into the second block's items
:second.empno := l_empno;
:second.ename := l_ename;
:second.job := l_job;
-- exit the loop if this was the last record to be copied
exit when l_exit = 'Y';
end loop;
end;
end;
if :system.last_record = 'true' then
:system.last_record returns 'TRUE' or 'FALSE' (in uppercase), so this IF statement will never be true. The 'true' in quotes must be 'TRUE'.
I am converting number into word with own code using length function.
Here is the code:
CREATE OR REPLACE FUNCTION num_to_words (NUM IN NUMBER)
RETURN VARCHAR2
IS
v_length NUMBER;
v_words VARCHAR2(100);
word_1 VARCHAR2(100);
BEGIN
word_1 := 'HUNDRED';
v_length := LENGTH(NUM);
IF v_length = 3
THEN v_words := SUBSTR(TO_CHAR(TO_DATE(NUM,'J'),'JSP'),1,3) ||' '|| word_1;
END IF;
RETURN(v_words);
END;
Problem is when I enter "100", it successfully converts into "ONE HUNDRED".
How can I implement the code for converting "101" into "ONE HUNDRED ONE".
The JSP conversion will do that for you; you don't need to extract the first digit, or supply the 'hundred' text yourself:
IF v_length <= 3 THEN
v_words := TO_CHAR(TO_DATE(NUM,'J'),'JSP');
END IF;
If you pass in 101 the result is ONE HUNDRED ONE. And because I changed = 3 to <= 3 it will work for any 1, 2 or 3-digit value, so passing in 11 returns ELEVEN.
For longer values you might be looking for something like this, which breaks the number down into groups and converts each group onto words plus the corresponding scale (e.g. 'million'). If you want non-standard grouping then it's a bit more involved, but there's a recent example for lakh here.
How can I check if two associative arrays are same or not?
E.g.
ARRAY1('ID') := 1;
ARRAY1('NAME') := 'Joe';
ARRAY2('ID') := 1;
ARRAY2('NAME') := 'Joe';
-- and i want to do like this
IF ARRAY1 = ARRAY2 THEN
-- Do something.
END IF;
You can compare two associative array using a function to compare the keys and values:
DECLARE
TYPE test_array IS TABLE OF VARCHAR2(200) INDEX BY VARCHAR2(10);
array1 test_array;
array2 test_array;
array3 test_array;
FUNCTION is_equal(
arr1 test_array,
arr2 test_array
) RETURN BOOLEAN
IS
i VARCHAR2(10);
BEGIN
i := arr1.FIRST;
WHILE i IS NOT NULL LOOP
-- Check if the keys in the first array do not exists in the
-- second array or if the values are different.
IF ( NOT arr2.EXISTS( i ) )
OR ( arr1(i) <> arr2(i) )
OR ( arr1(i) IS NULL AND arr2(i) IS NOT NULL )
OR ( arr1(i) IS NOT NULL AND arr2(i) IS NULL )
THEN
RETURN FALSE;
END IF;
i := arr1.NEXT(i);
END LOOP;
i := arr2.FIRST;
WHILE i IS NOT NULL LOOP
-- Check if there are any keys in the second array that do not
-- exists in the first array.
IF ( NOT arr1.EXISTS( i ) )
THEN
RETURN FALSE;
END IF;
i := arr2.NEXT(i);
END LOOP;
RETURN TRUE;
END;
BEGIN
array1('ID') := '1';
array1('NAME') := 'Joe';
array2('ID') := '1';
array2('NAME') := 'Joe';
array3('ID') := '1';
array3('NAME') := 'Joe';
array3('XYZ') := 'ABC';
IF is_equal( array1, array2 ) THEN
DBMS_OUTPUT.PUT_LINE( '1 and 2 are the same' );
ELSE
DBMS_OUTPUT.PUT_LINE( '1 and 2 are different' );
END IF;
IF is_equal( array1, array3 ) THEN
DBMS_OUTPUT.PUT_LINE( '1 and 3 are the same' );
ELSE
DBMS_OUTPUT.PUT_LINE( '1 and 3 are different' );
END IF;
END;
/
Outputs:
1 and 2 are the same
1 and 3 are different
Oracle documentation is your friend.
https://docs.oracle.com/database/121/LNPLS/composites.htm#LNPLS99915
In particular:
You cannot compare associative array variables to the value NULL or to each other.
Which means, if you need to compare associative arrays to each other, you have only two choices... check them element by element, for example in a loop (on the elements of one array - and make sure you check to see if they have the same number of elements); or, if you need to do this often, write your own function that compares arrays in that manner, and call it whenever you need to.
Just don't ask why Oracle is not providing a native PL/SQL function for this; you will very likely get no answers.
I have 2 associative_arrays of number and I am trying to built a dynamic string for a dynamic query.
This is my for statement:
for indx_opt in 1..IDOPTarray.count loop
IF indx_opt=1 AND IDFGCFCParray.count=1 THEN
sql_stmt_2:=sql_stmt_2||' and wopt.id_ft_opt = ';
sql_stmt_2:=sql_stmt_2|| (IDOPTarray(indx_opt));
end if;
if indx_opt=1 AND IDFGCFCParray.count>1 then
sql_stmt_2:=sql_stmt_2||' and wopt.id_ft_opt in(';
sql_stmt_2:=sql_stmt_2||(IDOPTarray(indx_opt));
elsif indx_opt>=1 AND IDFGCFCParray.count>=0 then
sql_stmt_2:=sql_stmt_2||','||(IDOPTarray(indx_opt))||')';
With 2 number in IDOPTarray I get a correct result:
and wopt.id_ft_opt in(27,28)
Instead with more then 2 number in IDOPTarray I get this result:
,17228),17229),17230)
What I want to get is this:
where w.id = 303 and wopt.id_ft_opt in (17228,17229,17230)
if I have 5 numbers, I want to get this for the 'where' clause:
where w.id = 321 and wopt.id_ft_opt in (17228,17229,17230,17231,17232)
I want a dynamic output of my string.
IDFGCFCParray is the 2nd array, but is not important right now in order to get the output I want.
Does somebody can help me? thank you.
You have to close bracket only if indx_opt is equil to IDOPTarray.count.
And simple example.
declare
type list_number is table of number;
xx list_number := new list_number(1,2,3,5,7,8);
str varchar2(4000);
begin
for i in xx.first .. xx.last loop
if i = 1 then
str := ' condition in ('||xx(i);
else
str := str||','||xx(i);
end if;
if i = xx.last then
str := str||')';
end if;
end loop;
dbms_output.put_line(str);
end;
If you colleciton is sql levle type you can do this in this way
declare
xx list_number := new list_number(1,2,3,5,7,8);
str varchar2(4000);
begin
SELECT 'condition in ('||LISTAGG(column_value, ',') WITHIN GROUP (ORDER BY column_value)||')' into str from table(xx);
dbms_output.put_line(str);
end;
I am working on an Oracle procedure that calls another procedure within it. One of my parameters (parm1) can contain one or more values in a comma separated list. How can I loop through these values to pass them one at a time to another procedure?
Here is an example of what I would like it to do:
When Parm1 = 123,312
callProcedure2(123)
callProcedure2(321)
-or-
When Parm1 123
callProcedure2(123)
I think this can be accomplished using a loop but I can't figure out how to get it to use each value as a separated call within the loop.
Any help would be appreciated!
Thanks!
CURSOR V_CUR IS
select regexp_substr(Parm1 ,'[^,]+', 1, level) As str from dual
connect by regexp_substr(Parm1, '[^,]+', 1, level) is not null;
This curor will give you result like this
123
321
Now iterate the cursor and call the procedure in loop.
For i IN V_CUR
LOOP
callProdcedure2(i.str);
END LOOP;
Just loop through substrings:
declare
parm1 varchar2(1000) := '123,234,345,456,567,789,890';
vStartIdx binary_integer;
vEndIdx binary_integer;
vCurValue varchar2(1000);
begin
vStartIdx := 0;
vEndIdx := instr(parm1, ',');
while(vEndIdx > 0) loop
vCurValue := substr(parm1, vStartIdx+1, vEndIdx - vStartIdx - 1);
-- call proc here
dbms_output.put_line('->'||vCurValue||'<-');
vStartIdx := vEndIdx;
vEndIdx := instr(parm1, ',', vStartIdx + 1);
end loop;
-- Call proc here for last part (or in case of single element)
vCurValue := substr(parm1, vStartIdx+1);
dbms_output.put_line('->'||vCurValue||'<-');
end;
There is a utility procedure COMMA_TO_TABLE and array type DBMS_UTILITY.UNCL_ARRAY dedicated for this task. Since Oracle 10g.
It is well document here.
Here is a sample solution:
SET SERVEROUTPUT ON
DECLARE
csvListElm VARCHAR2(4000) := 'elm1, elm2,elm3 ,elm4 , elm5';
csvListTable DBMS_UTILITY.UNCL_ARRAY;
csvListLen BINARY_INTEGER;
currTableName VARCHAR2(222);
BEGIN
DBMS_UTILITY.COMMA_TO_TABLE(csvListElm, csvListLen, csvListTable);
FOR csvElm IN 1..(csvListTable.COUNT - 1) LOOP
dbms_output.put_line('-- CSV element : <'||csvListTable(csvElm)||'>');
dbms_output.put_line('-- Trimmed CSV element: <'||trim(csvListTable(csvElm))||'>');
END LOOP;
END;
/
Sample output:
-- CSV element : <elm1>;
-- Trimmed CSV element: <elm1>;
-- CSV element : < elm2>;
-- Trimmed CSV element: <elm2>;
-- CSV element : <elm3 >;
-- Trimmed CSV element: <elm3>;
-- CSV element : <elm4 >;
-- Trimmed CSV element: <elm4>;
-- CSV element : < elm5>;
-- Trimmed CSV element: <elm5>;
It is possible to use a function that you can use in a for loop (without regexp for ThinkJet):
Create a type and function
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 call it in the for loop:
DECLARE
parm1 varchar2(4000) := '123,234,345,456,567,789,890';
BEGIN
FOR x IN (select * from (table(cto_table(',', parm1)) ) )
LOOP
dbms_output.put_line('callProdcedure2 called with ' || x.COLUMN_VALUE);
callProdcedure2(x.COLUMN_VALUE);
END LOOP;
END;
/
Notice the default name COLUMN_VALUE given by Oracle, which is necessary for the use I want to make of the result.
Result as expected:
callProdcedure2 called with 123
callProdcedure2 called with 234
...