I'm trying to create a procedure that will allow me to write an existing row to another table dynamically but the row declaration and insert-statement in the following snippet don't work. The error message indicates that the view hasn't been identified although outputting the target_table.table_name works just fine.
More will be added to the block later on - such as a column with the operation (e.g. INSERT or UPDATE). This is just a simple example and the last procedure (pass_reference) is used to trigger the procedure.
Any help would be much appreciated.
CREATE OR REPLACE PROCEDURE denormalize (new_cursor sys_refcursor, target_table_name varchar)
IS
target_table user_tables%rowtype;
sql_target_table varchar(200) := 'select * from user_tables where table_name = :target_table_name';
row target_table%rowtype;
BEGIN
execute immediate sql_target_table into target_table using target_table_name;
LOOP
fetch new_cursor into row;
exit when new_cursor%notfound;
insert into target_table values row;
commit;
END LOOP;
END denormalize;
/
CREATE OR REPLACE PROCEDURE pass_reference
AS
new_cursor sys_refcursor;
BEGIN
open new_cursor for select * from sales where sales_id=1;
denormalize(new_cursor, 'NEW_SALES');
END;
/
please check this code, it's not working only as for example, as you see for working columns in your cursor should be named as columns in destination table.
I take this code from package that create html table in mail base on view, hope you found this example useful
good luck
declare
in_view_name varchar2(30);
in_table_name varchar2(30) := 'your_new_table';
out_rc number;
out_rc_txt varchar2(1000);
l_cursor number;
l_sql varchar2(50) := 'select * from ' || in_view_name;
l_col_cnt binary_integer;
l_col_tab dbms_sql.desc_tab;
l_column_value varchar2(4000);
l_is_empty boolean := true;
l_insert_header varchar2(1000);
l_insert varchar2(32000);
begin
out_rc := 0;
out_rc_txt := 'OK';
l_cursor := dbms_sql.open_cursor;
dbms_sql.parse(l_cursor, l_sql, dbms_sql.native);
l_col_cnt := dbms_sql.execute(l_cursor);
dbms_sql.describe_columns(l_cursor, l_col_cnt, l_col_tab);
l_insert_header := 'insert into '||in_table_name||'(';
if l_col_cnt > 0 then
-- header
for i in l_col_tab.first .. l_col_tab.last loop
dbms_lob.append(l_insert_header, l_col_tab(i).col_name);
if i != l_col_tab.last then
dbms_lob.append(l_insert_header, ',');
end if;
dbms_sql.define_column(l_cursor, i, l_column_value, 4000);
end loop;
l_insert_header := l_insert_header || ') values(';
-- data
while dbms_sql.fetch_rows(l_cursor) > 0 loop
l_is_empty := false;
l_insert := l_insert_header;
for i in l_col_tab.first .. l_col_tab.last loop
dbms_sql.column_value(l_cursor, i, l_column_value);
l_insert := l_insert || '''' || l_column_value || ''','
if not in_attachment then
dbms_lob.append(out_table, l_td);
end if;
if (not in_attachment) or (l_column_value is not null) then
dbms_lob.append(out_table, nvl(l_column_value, l_nbsp));
end if;
if (not in_attachment) or (i != l_col_tab.last) then
dbms_lob.append(out_table, l_tdc);
end if;
end loop;
l_insert := substr(l_insert, 1, length(l_insert) - 1) || ')';
execute immediate l_insert;
end loop;
end if;
dbms_sql.close_cursor(l_cursor);
end;
Related
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;
I am using the following stored procedures in an Oracle database to export the output of the query to a CSV file.
CREATE OR REPLACE PROCEDURE run_query(p_tbl_name IN VARCHAR2
) IS
Select_Stmt VARCHAR2(100) := 'select * from '||p_tbl_name;
p_dir VARCHAR2 (100) := 'DATA_PUMP_DIR';
v_finaltxt VARCHAR2(4000);
v_v_val VARCHAR2(4000);
v_n_val NUMBER;
v_d_val DATE;
v_ret NUMBER;
c NUMBER;
d NUMBER;
col_cnt INTEGER;
f BOOLEAN;
rec_tab DBMS_SQL.DESC_TAB;
col_num NUMBER;
v_fh UTL_FILE.FILE_TYPE;
BEGIN
c := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(c, Select_Stmt, DBMS_SQL.NATIVE);
d := DBMS_SQL.EXECUTE(c);
DBMS_SQL.DESCRIBE_COLUMNS(c, col_cnt, rec_tab);
FOR j in 1..col_cnt
LOOP
CASE rec_tab(j).col_type
WHEN 1 THEN DBMS_SQL.DEFINE_COLUMN(c,j,v_v_val,2000);
WHEN 2 THEN DBMS_SQL.DEFINE_COLUMN(c,j,v_n_val);
WHEN 3 THEN DBMS_SQL.DEFINE_COLUMN(c,j,v_d_val);
ELSE
DBMS_SQL.DEFINE_COLUMN(c,j,v_v_val,2000);
END CASE;
END LOOP;
-- This part outputs the HEADER
v_fh := UTL_FILE.FOPEN(upper(p_dir),p_tbl_name||'.csv','w',32767);
FOR j in 1..col_cnt
LOOP
v_finaltxt := ltrim(v_finaltxt||','||lower(rec_tab(j).col_name),',');
END LOOP;
UTL_FILE.PUT_LINE(v_fh, v_finaltxt);
-- This part outputs the DATA
LOOP
v_ret := DBMS_SQL.FETCH_ROWS(c);
EXIT WHEN v_ret = 0;
v_finaltxt := NULL;
FOR j in 1..col_cnt
LOOP
CASE rec_tab(j).col_type
WHEN 1 THEN DBMS_SQL.COLUMN_VALUE(c,j,v_v_val);
v_finaltxt := ltrim(v_finaltxt||',"'||v_v_val||'"',',');
WHEN 2 THEN DBMS_SQL.COLUMN_VALUE(c,j,v_n_val);
v_finaltxt := ltrim(v_finaltxt||','||v_n_val,',');
WHEN 3 THEN DBMS_SQL.COLUMN_VALUE(c,j,v_d_val);
v_finaltxt := ltrim(v_finaltxt||','||to_char(v_d_val,'DD/MM/YYYY HH24:MI:SS'),',');
ELSE
DBMS_SQL.COLUMN_VALUE(c,j,v_v_val);
v_finaltxt := ltrim(v_finaltxt||',"'||v_v_val||'"',',');
END CASE;
END LOOP;
-- DBMS_OUTPUT.PUT_LINE(v_finaltxt);
UTL_FILE.PUT_LINE(v_fh, v_finaltxt);
END LOOP;
UTL_FILE.FCLOSE(v_fh);
DBMS_SQL.CLOSE_CURSOR(c);
END;
/
The above stored procedure is working perfectly and I got the output by using the following script to run this stored procedure
exec run_query person_list;
Here stored_procedure_name is run_query and the table_name is person_list.
Now my question is that how can I use the WHERE clause after the select statement on "line 3" -
Select_Stmt VARCHAR2(100) := 'select * from '||p_tbl_name;
or are there any other ways I can use the where clause.
Thanks in advance.
Select_Stmt VARCHAR2(100) := 'select * from '||p_tbl_name||' where name=''david''';
You have to have the character delimiters; and since you have it inside quotes you have to duplicate them.
How to wite EXECUTE IMMEDIATE in the cursor select Query.
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE)
IS
l_cur_query VARCHAR2 (5000) := 'SELECT * from table X where c1='|| in_from_date;
CURSOR cur_attendee_data
IS
EXECUTE IMMEDIATE l_cur_query;
TYPE rec_attendee_data IS TABLE OF cur_attendee_data%ROWTYPE
INDEX BY PLS_INTEGER;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data;
LOOP
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
EXIT WHEN l_cur_attendee_data.COUNT = 0;
DBMS_OUTPUT.put_line ('here in first insert');
lrec := return_attendee_report ();
out_attendee_tab :=
return_attendee_arr_result (return_attendee_report ());
out_attendee_tab.DELETE;
FOR i IN 1 .. l_cur_attendee_data.COUNT
LOOP
BEGIN
NULL;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error occurred : ' || SQLERRM);
END;
END LOOP;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('HERE INSIIDE OTHERS' || SQLERRM);
END;
here I try to use initialize cursor outside begin block but it rises exception, how to write a dynamic query for the cursor,
cur_attendee_data is
EXECUTE IMMEDIATE l_cur_query;
Error(113,8): PLS-00103: Encountered the symbol "CUR_ATTENDEE_DATA" when expecting one of the following: := . ( # % ;
If you fetch all rows with BULK COLLECT INTO ... then you need only one loop, the second loop is useless.
The basic solution would be this one:
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE) IS
l_cur_query VARCHAR2 (5000) := 'SELECT * from {table X} where c1=:d';
cur_attendee_data SYS_REFCURSOR;
TYPE rec_attendee_data IS TABLE OF {table X}%ROWTYPE;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data FOR l_cur_query USING in_from_date;
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
FOR i IN 1 .. l_cur_attendee_data.COUNT LOOP
-- do whatever you like to do with l_cur_attendee_data(i)
END LOOP;
CLOSE cur_attendee_data;
END;
However, I don't see any reason to make dynamic SQL. You can simply run
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE) IS
cur_attendee_data SYS_REFCURSOR;
TYPE rec_attendee_data IS TABLE OF {table X}%ROWTYPE;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data FOR SELECT * from {table X} where c1 = in_from_date;
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
FOR i IN 1 .. l_cur_attendee_data.COUNT LOOP
-- do whatever you like to do with l_cur_attendee_data(i)
END LOOP;
CLOSE cur_attendee_data;
END;
You can use below code instead -
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE)
IS
l_cur_query VARCHAR2 (100) := 'SELECT * from table X where c1=:in_from_date';
TYPE t_cur IS REF CURSOR;
cur_attendee_data t_cur
TYPE rec_attendee_data IS TABLE OF cur_attendee_data%ROWTYPE
INDEX BY PLS_INTEGER;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data FOR l_cur_query USING in_from_date;
LOOP
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
EXIT WHEN l_cur_attendee_data.COUNT = 0;
DBMS_OUTPUT.put_line ('here in first insert');
lrec := return_attendee_report ();
out_attendee_tab :=
return_attendee_arr_result (return_attendee_report ());
out_attendee_tab.DELETE;
FOR i IN 1 .. l_cur_attendee_data.COUNT
LOOP
BEGIN
NULL;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error occurred : ' || SQLERRM);
END;
END LOOP;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('HERE INSIIDE OTHERS' || SQLERRM);
END;
You need to use open cursor for '' as following:
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE)
IS
l_cur_query VARCHAR2 (5000) := 'SELECT * from table X where c1='|| in_from_date;
cur_attendee_data SYS_REFCURSOR; -- JUST DECLARED THE CURSOR
--TYPE rec_attendee_data IS TABLE OF cur_attendee_data%ROWTYPE
-- INDEX BY PLS_INTEGER; -- this declaration must be at schema level
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data for l_cur_query; -- OPEN THE CURSOR WITH DYNAMIC QUERY
..
.. -- YOUR CODE AS IT IS
..
Cheers!!
i'm getting below error while running the procedure. is the select correct.. trying to pass table name as parameter. Pls help.
PLS-00364: loop index variable 'CURSOR1' use is invalid
PROCEDURE generate_uniqueId(p_table_name IN VARCHAR2) is
--
--
CURSOR unique_id_cur
IS
SELECT /*PARALLEL(20)*/ unique_id
FROM p_table_name;
--
v_file UTL_FILE.file_type;
V_file_name Varchar2 (150);
V_file_parm Varchar2:= ora11g/test/;
v_output varchar2(200);
BEGIN
v_file_name := p_table_name || '.lst';
v_file := UTL_FILE.fopen ('ora11g/test/', 'unique_ID_file', 'A');
FOR cursor1 IN unique_id_cur
LOOP
BEGIN
v_output := cursor1.unique_id;
UTL_FILE.put_line (v_file, v_output);
END;
END LOOP;
UTL_FILE.fflush (v_file);
UTL_FILE.fclose (v_file);
END generate_uniqueId ;
Your PL/SQL code having many syntax issue.
You can pass the table name in cursor declaration in form of "from p_table_name".
CURSOR unique_id_cur
IS
SELECT /*PARALLEL(20)*/ unique_id
FROM p_table_name;
If you are using "VARCHAR2" data type then you have to provide the length of data should contain a variable. Also the string "ora11g/test/" should kept in single quota -'- as 'ora11g/test/'
V_file_parm Varchar2:= ora11g/test/;
The directory that you are passing, am not sure whether it is going to accept or not. But, should create a logical directly in Oracle using "CREATE DIRECTORY" and then pass the newly directory created in "UTL_FILE.FOPEN".
Here is the sample idea to create a procedure that will compile and work for you, create or real procedure like below.
create or replace PROCEDURE generate_uniqueId(p_table_name IN VARCHAR2 default 'abc')
as
CURSOR unique_id_cur
IS
SELECT object_id as unique_id
FROM all_tables
where rownum <=5;
v_file UTL_FILE.file_type;
V_file_name Varchar2(150);
V_file_parm Varchar2(200) := 'ora11g/test/';
v_output varchar2(200);
BEGIN
v_file_name := p_table_name || '.lst';
v_file := UTL_FILE.fopen ('ora11g/test/', 'unique_ID_file', 'A');
FOR cursor1 IN unique_id_cur
LOOP
BEGIN
v_output := cursor1.unique_id;
UTL_FILE.put_line (v_file, v_output);
END;
END LOOP;
UTL_FILE.fflush (v_file);
UTL_FILE.fclose (v_file);
END generate_uniqueId ;
EDIT-1
create or replace PROCEDURE generate_uniqueId(p_table_name IN VARCHAR2 default 'abc')
as
type t1 is table of varchar2(255) index by pls_integer;
v_unique_ids t1;
v_sql varchar2(4000);
v_file UTL_FILE.file_type;
V_file_name Varchar2(150);
V_file_parm Varchar2(200) := 'ora11g/test/';
v_output varchar2(200);
BEGIN
v_file_name := p_table_name || '.lst';
v_file := UTL_FILE.fopen ('ora11g/test/', 'unique_ID_file', 'A');
v_sql := 'select /* PARALLEL(20) */ unique_id from '|| p_table_name;
execute immediate v_sql bulk collect into v_unique_ids;
FOR cursor1 IN v_unique_ids.first..v_unique_ids.last
LOOP
BEGIN
v_output := v_unique_ids(cursor1);
UTL_FILE.put_line (v_file, v_output);
END;
END LOOP;
UTL_FILE.fflush (v_file);
UTL_FILE.fclose (v_file);
END generate_uniqueId ;
I have a function that returns a list of records, and then im looping over the list and piping them, however during piping I am getting ORA-01403: no data found error.
Below is the code I am using, and I am getting this error on some rows, not all of them.
NOTE: tab_pipe.t_tab and tab.t_tab are tables of the same record tab.r_tab.
Function pipelinedFunction(ref varchar2, seq varchar2) Return tab_pipe.t_tab pipelined Is
pragma autonomous_transaction;
errtxt varchar2(400);
tab tab.t_tab;
begin
tab := generate_table(ref, seq);
for i in 1 .. tab.count loop
begin
pipe row(tab(i));
EXCEPTION
when others then
v_errtxt := sqlerrm;
insert into test_kc values('an error occurred piping the row i = ' || i || ' - sqlerrm = ' || v_errtxt); commit;
end;
end loop;
return;
end pipelinedFunction;
Maybe there is no entry in tab for every value of i.
Try a loop using first and next
declare
l_index PLS_INTEGER;
BEGIN
l_index := tab.FIRST;
WHILE (l_index IS NOT NULL)
LOOP
pipe row(tab(l_index));
l_index := tab.NEXT(l_index);
END LOOP;
END;