ORACLE : How to take a count rows in a table inside a procedure while the table is being populated by another procedure (oracle job) - oracle

I want to take count of a table inside a parent procedure while the table is being populated by another/child procedure. This second/chile procedure is started as a job from inside the first/parent.
The above is a simplistic description of certain decisions I need to take inside the first/parent based on the count. Example, If the row-count value of the table read inside the first/parent reaches a certain value I want to kill/remove the second/child job.
However, I am always getting the count(*) = 0 inside the first/parent, even though a select * on the table shows that the child procedure is populating the table.
What could be the reason and what is the way of getting a count ?
Below is a test script :
create table test_j(col1 number);
create or replace PROCEDURE parent_job_sp
as
g_jobid_child number := -1;
v_child_cnt number := -1;
begin
DBMS_OUTPUT.PUT_LINE ('START - parent_job_sp');
DBMS_JOB.SUBMIT(g_jobid_child,'GFF_LOAD.child_job_sp;');
COMMIT;
DBMS_OUTPUT.PUT_LINE ('g_jobid_child='||g_jobid_child);
while (v_child_cnt<=8)
loop
DBMS_OUTPUT.PUT_LINE ('v_child_cnt='||v_child_cnt);
execute immediate 'select count(*) from test_j' into v_child_cnt;
if v_child_cnt >= 8
then
BEGIN
DBMS_JOB.remove(g_jobid_child);
COMMIT;
DBMS_OUTPUT.PUT_LINE ('REMOVED JOB '||g_jobid_child);
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE=-23421
THEN DBMS_OUTPUT.PUT_LINE('THEN');
ELSE DBMS_OUTPUT.PUT_LINE('ELSE');
END IF;
END;
end if;
DBMS_OUTPUT.PUT_LINE ('OUTSIDE IF');
end loop;
DBMS_OUTPUT.PUT_LINE ('OUTSIDE WHILE');
exception --- proc-level exception
when others then
DBMS_OUTPUT.PUT_LINE('parent_job_sp - '||SQLERRM);
end; --- end proc
create or replace PROCEDURE child_job_sp
as
PRAGMA AUTONOMOUS_TRANSACTION;
begin
for cur_test in
(
select rownum row_num from dual connect by level<=1000
)
loop
insert into test_j values (cur_test.row_num);
commit;
end loop;
COMMIT;
exception --- proc-level exception
when others then
DBMS_OUTPUT.PUT_LINE('child_job_sp - '||SQLERRM);
end; --- end proc
--- then run from SQL*Plus : exec parent_job_sp
--- I always get v_child_cnt=0 even though the result of the select count(*) from test_j is 1000.
What could be the reason and what is the way of getting a count ?

Related

Ignore lines that causes errors

I have a big Oracle script with thousands of package call inside a BEGIN - END;
Is there a way to ignore the lines that causes error and continue executing the next lines? Some sort of "On Error Resume Next" in vb.
If you have only one BEGIN END section, then you can use EXCEPTION WHEN OTHERS THEN NULL.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
exception when others then null;
end;
SQL> /
PL/SQL procedure successfully completed.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
--exception when others then null;
end;
/
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 6
SQL>
The whole concept of "ignore errors" is a bug, and a lie if any errors occur. That is not to say you cannot trap errors and continue processing, just that you MUST handle the errors. For example, assume the use case: "Data has been loaded into a stage table from multiple .csv files. Now load into the tables A and Table B according to ....".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, col_a2);
insert into table_b (col1, col2)
values (rec.col_b1, col_b2);
exception
when others then null;
end;
end loop;
process_message := 'Load Tables A,B Complete';
end ;
Now suppose a user created the a .csv file entered "n/a" in numeric columns where there was no value or the value was unknown. The result of this all too common occurrence is all such rows were not loaded, but you have no way to know that until the user complains their data was not loaded even though you told them it was. Further you have no way of determining the problem.
A much better approach is to "capture and report".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
load_error_occurred boolean := False;
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, rec.col_a2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
begin
insert into table_b (col1, col2)
values (rec.col_b1, rec.col_b2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
end loop;
if load_error_occurred then
process_message := 'Load Tables A,B Complete: Error(s) Detected';
else
process_message := 'Load Tables A,B Complete: Successful No Error(s)';
end if;
end Load_Tables_A_B_from_Stage ;
Now you have informed the user of the actual status, and where you are contacted you can readily identify the issue.
User here is used in the most general sense. It could mean a calling routine instead of an individual. Point is you do not have to terminate your process due to errors but DO NOT ignore them.
I don't think there is any magic one-liner that will solve this.
As others have, use a editor to automate the wrapping of each call within a BEGIN-EXCEPTION-END block might be quicker/easier.
But, if feel a little adventurous, or try this strategy:
Let's assume you have this:
BEGIN
proc1;
proc2;
proc3;
.
.
.
proc1000;
END;
You could try this (untested, uncompiled but might give you an idea of what to try):
DECLARE
l_progress NUMBER := 0;
l_proc_no NUMBER := 0;
e_proc_err EXCEPTION;
-- A 'runner' procedure than manegrs the counters and runs/skips dpending on these vals
PROCEDURE run_proc ( pname IN VARCHAR2 ) IS
BEGIN
l_proc_no := l_proc_no + 1;
IF l_proc_no >= l_progress
THEN
-- log 'Running pname'
EXECUTE IMMEDIATE 'BEGIN ' || pname || '; END;' ;
l_progress := l_progress + 1;
ELSE
-- log 'Skipping pname'
END IF;
EXCEPTION
WHEN OTHERS THEN
-- log 'Error in pname'
l_progress := l_progress + 1;
RAISE e_proc_err;
END;
BEGIN
l_progress := 0;
<<start>>
l_proc_no := 0;
run_proc ( 'proc1' );
run_proc ( 'proc2' );
run_proc ( 'proc3' );
.
.
run_proc ( 'proc1000' );
EXCEPTION
WHEN e_proc_err THEN
GOTO start;
WHEN OTHERS THEN
RAISE;
END;
The idea here is to add a 'runner' procedure to execute each procedure dynamically and log the run, skip, error.
We maintain a global count of the current process number (l_proc_no) and overall count of steps executed (l_progress).
When an error occurs we log it, raise it and let it fall into the outer blocks EXCEPTION handler where it will restart via an (evil) GOTO.
The GOTO is placed such that the overall execution count is unchanged but the process number is reset to 0.
Now when the run_proc is called it sees that l_progress is greater than l_proc_no, and skips it.
Why is this better than simply wrapping a BEGIN EXCEPTION END around each call?
It might not be, but you make a smaller change to each line of code, and you standardise the logging around each call more neatly.
The danger is a potential infinite loop which is why I specify e_proc_err to denote errors within the called procedures. But it might need tweaking to make it robust.

PL/SQL exception handling - log errors

How do I record the oracle error in a pl/sql script? I have been to the oracle error handling documentation and I see the built in exceptions, but what if I do not know what the exception is? How can I log this in an exception block?
I want to do something like the below.
exception
when others then DBMS_OUTPUT.PUT_LINE(the error)
It's easier to create error logging database trigger with conditional logging, for example my actual error_logging trigger: https://github.com/xtender/xt_scripts/blob/master/error_logging/on_database.sql
create table ERROR_LOG
(
id NUMBER,
username VARCHAR2(30),
errcode INTEGER,
seq INTEGER,
tmstmp TIMESTAMP(6),
msg VARCHAR2(4000),
sql_text CLOB
)
/
create sequence err_seq
/
create or replace trigger trg_error_logging
after servererror
on database
disable
declare
v_id number := err_seq.nextval();
v_tmstmp timestamp:= systimestamp;
n int;
sql_text dbms_standard.ora_name_list_t;
v_sql_text clob;
begin
-- only if plsql_debug is set to TRUE:
for r in (select * from v$parameter p where p.name='plsql_debug' and upper(p.value)='TRUE') loop
v_sql_text:=null;
n := ora_sql_txt(sql_text);
for i in 1..n loop
v_sql_text := v_sql_text || sql_text(i);
end loop;
for i in 1.. ora_server_error_depth
loop
if i=1 then
insert into error_log(id,seq,tmstmp,username,errcode,msg,sql_text)
values( v_id, i, v_tmstmp, user, ora_server_error(i), ora_server_error_msg(i), v_sql_text);
else
insert into error_log(id,seq,tmstmp,username,errcode,msg)
values( v_id, i, v_tmstmp, user, ora_server_error(i), ora_server_error_msg(i) );
end if;
end loop;
commit;
end loop;
END;
/
select object_name,object_type,status from user_objects o where object_name='TRG_ERROR_LOGGING'
/
alter trigger trg_error_logging enable
/
As you can see it logs all errors into the table ERROR_LOG, but only if session parameter plsql_debug is set to true. Obviously, you can change it to own parameters or conditions.

FORALL......SAVE EXCEPTIONS

We are using FORALL.....SAVE EXCEPTIONS. At the end of the loop, we have this:
FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP<BR><BR>
DBMS_OUTPUT.PUT_LINE('ERROR CREATING STAGING TICKER: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));<BR><BR>
DBMS_OUTPUT.PUT_LINE('INDEX INFO: ' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);<BR>
END LOOP;
Is there any way for me to get at actual VALUES in that array? Say if a customers email was too long.....for me to actually display the value which caused the error? Rather than just some index number?
Thanks!
You can use the loop variable i to display the content of the exception array in your case. See below an example procedure:
CREATE OR REPLACE PROCEDURE PROC1 (V_EMP_ID DBMS_SQL.NUMBER_TABLE)
IS
lv_error_string VARCHAR2(4000);
BEGIN
FORALL INDX IN V_EMP_ID.FIRST..V_EMP_ID.LAST SAVE EXCEPTIONS
UPDATE EMPLOYEES
---trying to rasie an exception by using a calculation
SET SALARY=SALARY * 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
WHERE ID_E= V_EMP_ID(INDX);
EXCEPTION
WHEN OTHERS
THEN
FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
---Am printing the value of the exception array.
dbms_output.put_line('exception Raised for record' ||V_EMP_ID(i));
END LOOP;
END;
/
Ouput:
SQL> DECLARE
empid DBMS_SQL.NUMBER_TABLE;
BEGIN
empid (1) := 1;
empid (2) := 9;
PROC1 (empid);
END;
/
exception Raised for record 1
PL/SQL procedure successfully completed.

Procedure to delete non existing rows oracle

I wrote a procedure in PL/SQL to delete rows from a table,However,if that record does not exist,then throws some error like this: DBMS_OUTPUT.PUT_LINE('No such record'); My procedure is:
CREATE OR REPLACE PROCEDURE del_cn2
(c_cntry_id IN COUNTRIES.COUNTRY_ID%TYPE
)
IS
v_error_code NUMBER;
BEGIN
DELETE from countries
WHERE country_id =c_cntry_id;
IF SQL%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('No such record');
END IF;
EXCEPTION WHEN OTHERS THEN
v_error_code :=SQLCODE;
IF v_error_code =-2292 THEN
RAISE_APPLICATION_ERROR(-20004,'Organization '||TO_CHAR(c_cntry_id)||' site
details defined for it.');
END IF;
END;
/
However,when I execute this procedure and provide a record that does not exist in my table,it gives message "Procedure completed successfully" I am using this to execute:
Execute procedure del_cn2('JJ');
Can someone please suggest?
If you want an exception to be thrown when a value that does not exist in the table is passed in, you would need to actually throw an exception. You shouldn't use dbms_output for any sort of error output. That is a very simplistic debugging tool-- you shouldn't assume that the caller will ever be able to see that output.
My guess is that you want something like
CREATE OR REPLACE PROCEDURE del_cn2
(c_cntry_id IN COUNTRIES.COUNTRY_ID%TYPE
)
IS
BEGIN
DELETE from countries
WHERE country_id =c_cntry_id;
IF SQL%ROWCOUNT = 0
THEN
raise_application_error( -20001, c_cntry_id || ' no such value.' );
END IF;
END;
try to set serverout to ON
example:
create table tst_delete (col1 int);
create procedure p_test_delete as
BEGIN
DELETE FROM tst_delete
WHERE col1 = 1;
IF (SQL%NOTFOUND)
THEN
dbms_output.put_line('No records found');
END IF;
END;
then call the procedure in SqlPlus
SQL> exec p_test_delete;
PL/SQL procedure successfully completed
same issue that you described - no insformation...
next try with output activated
SQL> set serverout on
SQL> exec p_test_delete;
No records found
PL/SQL procedure successfully completed
SQL>

Getting error ORA-01722 and ORA-06512 in the procedure

I am getting error while trying to run this procedure. I am passing 36 to p_nb_months and p_msg_per_loop is taking default value.
Code is as below.
procedure prc_purge(p_nb_month IN number default 210, p_msg_per_loop IN number default 10000) as
TYPE selREC IS RECORD (
EMAIL desinscription.EMAIL%type,
IDRA desinscription.IDRA%type,
D_DATE desinscription.desinscription_date%type
);
type t_stringtab is table of selREC;
tab_liste_tables t_stringtab;
i number(10);
cursor c_table_order is
select EMAIL,IDRA,desinscription_date from desinscription where desinscription_date < trunc(add_months(sysdate,-'||p_nb_month||'));
begin
g_proc_name := 'PRC_PURGE';
prc_log('Begining procedure');
open c_table_order;
fetch c_table_order bulk collect into tab_liste_tables;
close c_table_order;
for i in 1..tab_liste_tables.count Loop
begin
DBMS_APPLICATION_INFO.set_action(action_name => 'Purging DESINCRIPTION and INSCRIPTION tables');
execute immediate 'DELETE FROM DESINSCRIPTION WHERE EMAIL=:1 AND IRDA=:2' using tab_liste_tables(i).EMAIL,tab_liste_tables(i).IDRA;
execute immediate 'DELETE FROM INSCRIPTION WHERE EMAIL=:1 AND IDRA=:2 AND INSCRIPTION_DATE < TRUNC(:3)' using tab_liste_tables(i).EMAIL,tab_liste_tables(i).IDRA,tab_liste_tables(i).D_DATE;
exception when others then
prc_log('Error in procedure : '||chr(10)||DBMS_UTILITY.FORMAT_ERROR_STACK() || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE(),'ERROR');
raise_application_error(-20000,'Erreur durant la purge ECOM. Consulter WRK_LOG pour plus d''information');
end;
end loop;
commit;
tab_liste_tables.delete;
prc_log('End of procedure');
end prc_purge;
Second argument of function add_month must be number data type, so just change your cursor from
cursor c_table_order is
select EMAIL,IDRA,desinscription_date from desinscription where desinscription_date < trunc(add_months(sysdate,-'||p_nb_month||'));
to
cursor c_table_order is
select EMAIL,IDRA,desinscription_date from desinscription where desinscription_date < trunc(add_months(sysdate,-p_nb_month));
Also you can define your table type without declaring record type, just try:
type t_stringtab is table of c_table_order%ROWTYPE;

Resources