I want to create a procedure that adds new rows to an already existing table.But with the current procedure I've, I'm rewriting the entire table. The code for the current procedure is
CREATE TYPE t_tf_row AS OBJECT (
id NUMBER,
description VARCHAR2(50));
CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
create or replace procedure add_n_rows(
n_rows in number)
is
l_tab t_tf_tab := t_tf_tab();
begin
for i in l_tab.count .. l_tab.count + n_rows
loop
l_tab.extend;
l_tab(l_tab.last) := t_tf_row(i, 'Description for '|| i);
end loop;
end;
Here, everytime I'm rewriting the entire l_tab. I want to update the one which is already updated. Suggest me the right method for the required procedure.Thanks
This is because you're re-creating the object. You need to pass an instantiated version of the object into the procedure as a parameter:
create or replace procedure add_n_rows(
Pn_rows in number
, P_tab in out t_tf_tab ) is
begin
for i in P_tab.count .. P_tab.count + Pn_rows
loop
P_tab.extend;
P_tab(l_tab.last) := t_tf_row(i, 'Description for '|| i);
end loop;
end;
I've declared P_tab as an OUT parameter, this means you can alter it. If you don't want to do this then remove "out" and declare a local variable of the type t_tf_tab, which you can then alter.
You can then call it separately, for instance:
declare
l_tab t_tf_tab := t_tf_tab();
begin
l_tab.extend;
l_tab(l_tab.last) := t_tf_row(1. 'Hello');
add_n_rows(3, l_tab);
end;
Related
How code procedure with nested table parameter? In table test I need insert data from loop eg. 1,2,3...
plsql
Declare
TYPE code_nt is table of varchar2(10);
l_codes code_nt := code_nt();
begin
FOR i IN 1..APEX_APPLICATION.G_F01.COUNT LOOP
l_codes.extend;
l_codes(i) := to_char(i);
END LOOP;
//here call procedure
//PKG_EMP.INSERT_EMP(PAR_1);
end;
package:
create or replace PACKAGE PKG_EMP AS
TYPE code_nt is table of varchar2(10);
l_codes code_nt := code_nt();
procedure INSERT_EMP (PAR_1 code_nt);
END;
create or replace PACKAGE BODY PKG_EMP AS
procedure INSERT_EMP (PAR_1 code_nt) AS
BEGIN
INSERT INTO test (ID) VALUES (value from code_nt);
END;
end;
Your code will not work as code_nt is a locally defined type in both your PL/SQL function and the PL/SQL anonymous block and despite having the same name and signature they are different data types.
You need to use the same data type in both:
Declare
l_codes PKG_EMP.code_nt := PKG_EMP.code_nt();
begin
FOR i IN 1..APEX_APPLICATION.G_F01.COUNT LOOP
l_codes.extend;
l_codes(i) := to_char(i);
-- or
-- l_codes(i) := TO_CHAR( APEX_APPLICATION.G_F01(i) );
END LOOP;
PKG_EMP.INSERT_EMP(l_codes);
END;
/
You can declare your package as:
CREATE PACKAGE PKG_EMP AS
TYPE code_nt is table of varchar2(10);
PROCEDURE INSERT_EMP (PAR_1 code_nt);
END;
/
CREATE PACKAGE BODY PKG_EMP AS
procedure INSERT_EMP (PAR_1 code_nt) AS
BEGIN
FORALL i IN 1 .. par_1.COUNT
INSERT INTO test (ID) VALUES ( par_1(i) );
END;
END;
/
db<>fiddle here
I am trying to pass dbms_sql.number_table from one procedure to another and then using it inside a dynamic plsql block. But the below code throws error as:
Error(6,17): PLS-00306: wrong number or types of arguments in call to '||'
create or replace
procedure proc1( v_in_table_name varchar2,
v_in_column_name varchar2,
v_in dbms_sql.number_table)
as
plsql_block varchar2(4000);
begin
plsql_block:='declare
begin
FORALL INDX IN 1 ..'||v_in.count||' SAVE EXCEPTIONS
UPDATE '||v_in_table_name||'
Set '||v_in_column_name||'=123 WHERE col2='||v_in||'(INDX)||;
end';
execute immediate plsql_block;
end proc1;
You should make two changes:
First(and its case of error) its when you concatinate string with collection.
If you want to send collection into pl/sql block you shoud use the param.
And second you should add ; in the end of dynamic pl\sql:
create or replace
procedure proc1(v_in dbms_sql.number_table)
as
plsql_block varchar2(4000);
begin
plsql_block:='declare
l_collect dbms_sql.number_table := :number_table ;
begin
FORALL INDX IN 1 ..'||v_in.count||' SAVE EXCEPTIONS
UPDATE table_name
Set col1=123 WHERE col2=l_collect(INDX);
end;';
execute immediate plsql_block using v_in;
end proc1;
But after all changes I would like to ask: Are you realy need to use dynamic pl\sql?
There is no need for using a dynamic block. Also i dont think it can work as well. You can use this way;
create or replace
procedure proc1(v_in dbms_sql.number_table)
as
plsql_block varchar2(4000);
l_collect dbms_sql.number_table;
begin
l_collect := v_in;
FORALL INDX IN 1 ..l_collect.count SAVE EXCEPTIONS
UPDATE table_name
Set col1=123
WHERE col2=l_collect(INDX);
commit;
end proc1;
Execution:
SQL> DECLARE
var DBMS_SQL.number_table;
BEGIN
var (1) := 1;
var (2) := 2;
var (3) := 3;
proc1 (var);
END;
/
PL/SQL procedure successfully completed.
EDIT: As per your edit the code becomes like:
create or replace procedure proc1 (v_in_table_name varchar2,
v_in_column_name varchar2,
v_in dbms_sql.number_table)
as
plsql_block varchar2 (4000);
begin
plsql_block := q'[ FORALL INDX IN 1 ..v_in.count SAVE EXCEPTIONS
UPDATE :table_name
Set :col1=123
WHERE col2=v_in(INDX)]';
execute immediate plsql_block using v_in_table_name, v_in_column_name;
commit;
end proc1;
I'm new to oracle. I've come across table functions. The code for the function is
CREATE TYPE t_tf_row AS OBJECT (
id NUMBER,
description VARCHAR2(50));
CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
CREATE OR REPLACE FUNCTION get_tab_tf (p_rows IN NUMBER) RETURN t_tf_tab AS
l_tab t_tf_tab := t_tf_tab();
BEGIN
FOR i IN 1 .. p_rows LOOP
l_tab.extend;
l_tab(l_tab.last) := t_tf_row(i, 'Description for ' || i);
END LOOP;
RETURN l_tab;
END;
Now, I want to create a procedure which will update the table type using for loop.
The code for the procedure is
create or replace procedure add_n_rows(n_rows in number) is
l_tab t_tf_tab := t_tf_tab();
begin
for i in t_tf_tab.count .. t_tf_tab.count + n_rows
loop
l_tab.extend;
l_tab(l_tab.last) := t_tf_row(i, 'Description for '|| i);
end loop;
end;
I'm getting error saying that COUNT should be declared.
ORA-06550: line 1, column 37:
PLS-00302: component 'COUNT' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
Could you help me out in using the right procedure for .count in for loop.Thanks
You need to use the variable name not the type.
create or replace procedure add_n_rows(
n_rows in number)
is
l_tab t_tf_tab := t_tf_tab();
begin
for i in l_tab.count .. 1_tab.count + n_rows
loop
l_tab.extend;
l_tab(l_tab.last) := t_tf_row(i, 'Description for '|| i);
end loop;
end;
/
I need to return the table as a result of this select. The argument passed to the function opredelyaet from which an array of associative data will be displayed as a table:
select * from table(task_2.get_con_coll('save'));
I wrote this code and to me it is correct and I do not see that I have missed, or that it is not paved with
Сreates an object
-- the creation of an array type
create or replace type con_coll_type is object(
id integer,
user_name varchar2(255));
Then he created the package
create or replace package task_2 is
/*
-- may need to be so?
type con_coll_type is record(
id integer,
user_name varchar(255));
-- */
--Create associative array
type con_coll_t is table of con_coll_type index by varchar2(255);
-- need using this
function get_con_coll(coll_name varchar2) return con_coll_t
pipelined;
end task_2;
create or replace package body task_2 is
function get_con_coll(coll_name varchar2) return con_coll_t
pipelined is
indx varchar(255);
coll_edit con_coll_t;
coll_delete con_coll_t;
coll_save con_coll_t;
begin
-- Filling collection
coll_edit(1) := con_coll_type(1, 'some_name_1');
coll_edit(2) := con_coll_type(2, 'some_name_2');
coll_delete(3) := con_coll_type(3, 'some_name_3');
coll_delete(4) := con_coll_type(4, 'some_name_4');
coll_save(5) := con_coll_type(5, 'some_name_5');
coll_save(6) := con_coll_type(6, 'some_name_6');
-- If the parameter is passed to the function "Save" - a collection of output
if coll_name = 'save' then
indx := coll_save.first;
loop
exit when indx is null;
-- pipelined output
pipe row(con_coll_type(coll_save(indx).id,
coll_save(indx).user_name));
indx := coll_save.next(indx);
end loop;
end if;
end get_con_coll;
end task_2;
What's wrong with my code? I can not understand what I missed.
First, the Table type should be out of the package:
create type con_coll_t is table of con_coll_type;
And, if you have default values for your types, you can implement them like that:
coll_delete con_coll_t;
begin
coll_delete := con_coll_t(con_coll_type(3, 'some_name_3'),
con_coll_type(4, 'some_name_4'));
end;
With that being said, I think your general code should be something like that:
-- the creation of an array type
create or replace type con_coll_type is object(
id integer,
user_name varchar2(255));
create or replace type con_coll_t is table of con_coll_type;
create or replace package task_2 is
function get_con_coll(coll_name varchar2) return con_coll_t
pipelined;
end task_2;
create or replace package body task_2 is
function get_con_coll(coll_name varchar2) return con_coll_t
pipelined is
indx varchar(255);
coll_edit con_coll_t;
coll_delete con_coll_t;
coll_save con_coll_t;
begin
-- Filling collection
coll_edit := con_coll_t( con_coll_type(1, 'some_name_1')
, con_coll_type(2, 'some_name_2'));
coll_delete := con_coll_t( con_coll_type(3, 'some_name_3')
, con_coll_type(4, 'some_name_4'));
coll_save := con_coll_t( con_coll_type(5, 'some_name_5')
, con_coll_type(6, 'some_name_6'));
-- If the parameter is passed to the function "Save" - ??a collection of output
if coll_name = 'save' then
indx := coll_save.first;
loop
exit when indx is null;
-- pipelined output
pipe row(con_coll_type(coll_save(indx).id,
coll_save(indx).user_name));
indx := coll_save.next(indx);
end loop;
end if;
end get_con_coll;
end task_2;
That's what I wanted and what made
create or replace type con_coll_type is object(id integer,
user_name varchar2(255));
create or replace type con_coll_t is table of con_coll_type;
create or replace package task_2 is
type list_of_oper is table of con_coll_t index by varchar2(255);
function get_con_coll(coll_name varchar2) return con_coll_t
pipelined;
end task_2;
create or replace package body task_2 is
function get_con_coll(coll_name varchar2) return con_coll_t
pipelined is
indx varchar(255);
indx_2 varchar(255);
coll_main list_of_oper;
begin
coll_main('edit') := con_coll_t(con_coll_type(1, 'some_name_1'),
con_coll_type(2, 'some_name_2'));
coll_main('delete') := con_coll_t(con_coll_type(3, 'some_name_3'),
con_coll_type(4, 'some_name_4'));
coll_main('save') := con_coll_t(con_coll_type(5, 'some_name_5'),
con_coll_type(6, 'some_name_6'));
indx := coll_main.first;
loop
exit when indx is null;
if indx = coll_name then
indx_2 := coll_main(indx).first;
loop
exit when indx_2 is null;
pipe row(coll_main(indx)(indx_2));
/*pipe row(con_coll_type(coll_main(indx)(indx_2).id,
coll_main(indx)(indx_2).user_name));*/
indx_2 := coll_main(indx).next(indx_2);
end loop;
end if;
indx := coll_main.next(indx);
end loop;
return;
end get_con_coll;
end task_2;
select *
from table(task_2.get_con_coll('edit'))
union all
select *
from table(task_2.get_con_coll('delete'))
union all
select *
from table(task_2.get_con_coll('save'));
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;