Not able to handle ORA-01476: “divisor is equal to zero” - oracle

I have a simple procedure in which I am using a cursor to fetch some valid items. Using these valid items, I am obtaining some values and doing a calculation on them. The code looks like this:
PROCEDURE p_loadanalyse
IS
qty_1 NUMBER;
qty_2 NUMBER := 0;
V_CALC Number := 0;
Item_No_Rec valid_items%Rowtype;
CURSOR c_validitem
IS
SELECT DISTINCT item_no
From valid_items A;
Begin
For Item_No_Rec In C_Validitem
Loop
SELECT ROUND(SUM(Qty1),2)
INTO qty_1 -- FCST_QTY_PCD
FROM qty1_table
WHERE item_No = item_No_rec.item_No;
SELECT SUM(Qty2)
INTO qty_2
FROM qty2_table
WHERE A.Item_No = Item_No_Rec.Item_No;
V_CALC := (QTY_1 /QTY_2)*100;
Dbms_Output.Put_Line('deviation' ||V_Deviation);
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
qty_1 := 0;
qty_2 := 0;
WHEN OTHERS THEN
DBMS_output.put_line('There is an error');
END;
The problem is obviously in the statement V_CALC := (QTY1 /QTY2)*100; when QTY2 is 0. So I try to handle it like:
IF QTY_2 <> 0 THEN
V_CALC := (QTY_1 /QTY_2)*100;
ELSE
V_CALC := 0;
END IF;
This surprisingly hangs my SQL Developer when I try to compile the procedure. And gives a timeout occurred while trying to lock object after 10-11 mins. What I did was not rocket science and Oracle should have behaved the way I wanted, but instead I had to think and try all other ways of handling this divide by zero thingy like:
Including EXCEPTION WHEN ZERO_DIVIDE THEN QTY_2 := 0;
Using CASE instead of IF
After trying all permutations and combinations for 3 days, I am out of ideas and reasons and still with no output. Any hints or suggestions are welcome.

I doubt SQL developer is hanging because of your if statement or a divide by zero error. You may want to see if other users are using the procedure or if there are any processes in the database that need to be killed.
This can be shown if you can recreate the procedure but with another name..

Close the procedure before recreating it. SQL Developer block opened procedures.
Additionaly, try create it with different name of package/procedure.
And to be sure it's not zero division problem, create procedure empty...

Related

How to handle exceptions in for loop for insert rows using a stored procedure? Oracle PLSQL

I'm coding a complex PLSQL block (complex for me hahaha) to insert rows using information from the FOR LOOP CURSOR and add parameters to insert using a stored procedure. The current problem is there are around 200 rows to be inserted but when a simple row fail to insert all rows inserted broke and oracle execute a ROLLBACK command (I think so). So... How could I handle exceptions to insert succefully all rounds I can and when any rows fail show it in screen? Thanks
FOR i IN c_mig_saldos LOOP
IF i.tipo_comprobante = 'P' THEN -- Nota de debito (positivo)
v_cmp_p.prn_codigo := 'VIV';
v_cmp_p.tcm_codigo := 'NRA';
v_cmp_p.cmp_fecha_emision := TRUNC(SYSDATE);
v_cmp_p.cmp_fecha_contable := TRUNC(SYSDATE);
v_cmp_p.cmp_observacion := 'GENERACION AUTOMATICA DE SALDOS';
v_cmp_p.cli_codigo := i.cli_codigo;
v_tab_dco_p(1).cnc_codigo := 'VIA';
v_tab_dco_p(1).dco_precio_unitario := i.total_final;
v_tab_dco_p(1).dco_cantidad := 1;
v_tab_dco_p(1).dco_importe := i.total_final;
-- Insert a new row using stored procedure but when a itereted fail, no rows has inserted in table
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
COMMIT;
END IF;
END LOOP;
-- Insert a new row using stored procedure but when a itereted fail, no rows has inserted in table
begin
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
exception
when others then --this could be made more specific but you didn't say what type of error you were getting
-- Log to a table so you can export failed inserts later.
-- Make sure log table cols are large enough to store everything that can possibly be inserted here...
ErrorLogProc(the, cols, you, want, to, see, and, SQLERRM);
end;
In the ErrorLogProc() I'd recommend a couple things, here is a snippet of some things I do in my error logging proc that you may find helpful (it's just a few snippets, not intended to fully work, but you should get the idea)...
oname varchar2(100);
pname varchar2(100);
lnumb varchar2(100);
callr varchar2(100);
g1B CHAR(2) := chr(13)||chr(10);
PRAGMA AUTONOMOUS_TRANSACTION; --important!
begin
owa_util.who_called_me(oname, pname, lnumb, callr);
--TRIM AND FORMAT FOR ERRORLOG
lv_errLogText := 'Package: '||pname||' // Version: '||version_in||' // Line Number: '||lnumb||' // Error: ';
lv_string1 := mid(errStr_in,1,4000-Length(lv_errLogText));
lv_errLogText := lv_errLogText||lv_string1;
lv_errLogText := lv_errLogText||g1B||'Error Backtrace: '||replace(dbms_utility.format_error_backtrace,'ORA-', g1b||'ORA-');
insertIntoYourErrorLogTable(lv_errLogText, and, whatever, else, you, need);
commit;
To keep this simple, since there's not enough information to know the what and why of the question, this will kick out some text information about failures as desired.
SQL> set serveroutput on
Then here's an anonymous PL/SQL block:
BEGIN
FOR i IN c_mig_saldos
LOOP
-- not relevant
BEGIN
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
EXCEPTION
-- The goal of this is to ignore but output information about your failures
WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('whatever you want about v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p or SQLERRM/SQLCODE or the error stack - not enough info in the question');
END;
END LOOP;
END;
/
Note: I have removed the commit from the execution.
Then if you like what you see...
SQL> commit;
Ideally, if I knew more about why the insert failures were expected to occur and what I wanted to do about them, I would not insert them in the first place.
Agree with comment that more information is needed, but a couple of things to consider:
Does this need to be done as a loop - if you can write your query as a select statement, then you can do a simple insert without the need for PLSQL, which would be simpler and likely quicker (set based SQL vs row-by-row PLSQL)
You say a ROLLBACK is occuring - you have a commit inside your IF statement, so any records which make it into your IF statement and succesfully make it through your insert procedure will be committed i.e. they will not be rolled back; You should consider if some records you think are being rolled back are actually not making it into the IF statement at all
Can you provide example data, or an example of the error you are receiving?

PL/SQL select into a nested array

I'm trying to select values from my table and stock them into an array to manipulate them. this is what i have done.
DECLARE
TYPE student IS TABLE OF VARCHAR2(20);
s student := student();
n number := 1;
BEGIN
FOR i IN (SELECT name from HR.STUDENT) loop
s(n) := i.name;
n := n + 1;
end loop;
end;
when i do this i get this error in SQL Developer
An in-limit subscript was greater than the count of a varray
or too large for a nested table.
So i'm asking is this the right way to get a my table values into the student Type? any hints ?
i'm new to PL/SQL so i maybe saying wrong things. correct me when i do so. Thank you.
Any easier way to accomplish this would be:
SELECT name
BULK COLLECT INTO s
FROM HR.STUDENT;
With no need for the loop at all.
The reason the error occurs (I think) is that you are trying to access and index of the table which is not yet available. I believe you need to manually call extend prior to the access, which would then make the next index of the array available e.g.
FOR i IN (SELECT name from HR.STUDENT)
LOOP
s.extend;
s(n) := i.name;
n := n+1;
END LOOP;
Also I believe you need to have s students := students(); - you seem to be missing a colon.

Oracle 11g: In PL/SQL is there any way to get info about inserted and updated rows after MERGE DML statement?

I would like to know is there any way to receive information in PL/SQL how many rows have been updated and how many rows have been inserted while my PL/SQL script using MERGE DML statement.
Let's use Oracle example of merge described here: MERGE example
This functionality is used in my function but also I'd like to log information how many rows has beed updated and how many rows have been inserted.
There is a not a built-in way to get separate insert and update counts, no. SQL%ROWCOUNT would tell you the number of rows merged, as you probably already know, but there is no equivalent to get separate values for the inserts and updates.
This article by Adrian Billington shows a way to get the information by including a function call in the merge, which might add a little overhead.
There's a similar, and perhaps simpler, trick from MichaelS on the Oracle forums, which I can't take any credit for at all either, of course. I'm tempted to reproduce it here but I'm not sure if that's allowed, but essentially it's using sys_context to maintain a count, in much the same way that Adrian's solution did with a package variable. I'd use that one, as it's cleaner and I think it's easier to follow and maintain.
Still perilously close to a link-only answer but I don't want to plagiarise others' work either...
Workarounds pointed by #AlexPoole works, but for me it's strange why don't count updates, inserts and even possible deletes by more natural way with triggers.
Suppose we have simple test table:
create table test_table (id number, col number)
Define simple package for counters
create or replace package pkg_test_table_counter as
procedure reset_counter;
procedure count_insert;
procedure count_update;
procedure count_delete;
function get_insert_count return number;
function get_update_count return number;
function get_delete_count return number;
end;
... and package body:
create or replace package body pkg_test_table_counter as
vUpdateCount number := 0;
vInsertCount number := 0;
vDeleteCount number := 0;
procedure reset_counter is
begin
vUpdateCount := 0;
vInsertCount := 0;
vDeleteCount := 0;
end;
procedure count_insert is
begin
vInsertCount := vInsertCount + 1;
end;
procedure count_update is
begin
vUpdateCount := vUpdateCount + 1;
end;
procedure count_delete is
begin
vDeleteCount := vDeleteCount + 1;
end;
function get_insert_count return number is
begin
return vInsertCount;
end;
function get_update_count return number is
begin
return vUpdateCount;
end;
function get_delete_count return number is
begin
return vDeleteCount;
end;
end;
To count number of rows during execution of single DML statement we need to reset it in before statement trigger
create or replace trigger trg_test_table_counter_reset
before insert or update or delete on test_table
begin
pkg_test_table_counter.reset_counter;
end;
... and increment appropriate counter in trigger for each row:
create or replace trigger trg_test_table_counter_count
before insert or update or delete on test_table
for each row
begin
if inserting then
pkg_test_table_counter.count_insert;
end if;
if updating then
pkg_test_table_counter.count_update;
end if;
if deleting then
pkg_test_table_counter.count_delete;
end if;
end;
So, after executing MERGE statement without additional tricks inside DML query text it's always possible to get exact number of affected rows:
select
pkg_test_table_counter.get_insert_count insert_count,
(
pkg_test_table_counter.get_update_count
-
pkg_test_table_counter.get_delete_count
) update_count,
pkg_test_table_counter.get_delete_count delete_count
from dual
Note that delete operations also counted as updates for MERGE , but to keep package useful for another operations there are two separate counters.
SQLFiddle test

Cursor occasionally lose data when open in run time

This is my cursor with SELECT statement which always return right result. But when i use it in a procedure, it sometimes get wrong result (return only one row).
This problem appears with a large frequency (~5-10 times per 1000 procedure call). Maybe this is known issues with oracle 10gR2 or some disadvantages in my procedure.
I hope I can understand why this happening and get the better solutions because I get ~5000 procedure calls per day.
This is my cursor which declared in procedure:
CURSOR cur_result (var_num NUMBER)
IS
SELECT
m_username,
m_password
FROM
M_USERS
WHERE
m_status = 1
AND rownum <= var_num;
This is my procedure body:
p_num:=5;
-- the IF statement for guarantee the SELECT statement always
-- get more rows than p_num
LOCK TABLE M_USERS IN EXCLUSIVE MODE;
OPEN cur_result (p_num);
LOOP
FETCH
cur_result
INTO
p_username,
p_password;
-- the problems here: only 1 row is queried
-- the LOOP will exit after second time fetch
IF (cur_result%NOTFOUND AND (cur_result%ROWCOUNT < p_num) ) THEN
iserror := 1;
EXIT;
END IF;
EXIT WHEN cur_result%NOTFOUND;
UPDATE
M_USERS
SET
m_status = 2
WHERE
m_username = p_username
AND m_password = p_password;
END LOOP;
CLOSE cur_result;
Thanks in advance.

pl/sql loop records select oracle plsql

I have a select statement that I am trying to loop over and increment a variable based on the condition of the select statement, then return the variable as an out so I can do something with it in some front end code. I am using oracle 11g and I am seeing a few ways I can do this... but I am not sure which is the best way. I have some of what I am trying to do below, but again stopped because of confusion.
First I am setting my proc and 'in variable'
PROCEDURE SEEKER (pMonkeyID IN Number, vMarkCounter OUT Number)
AS
BEGIN
CURSOR seeker_cur IS
Select Mokney_approved, Monkey_vaulted
from MonkeyBookApps
where MonkeyID = pMonkeyID
and Monkey_doc_type = 'BANANA'
order by docu_approv_timestamp,monkey_doc_type,monkey_doc_approved desc
OPEN seeker_cur;
begin
OPEN Seeker_cur;
vMarkCounter := 0;
Here is the part I am not sure about. Should I loop and then exit if the condition is not met or should I do an if statement and somehow determine if there is a record that could be greater than one? If so how would that work? Is there a benefit to doing one way over the other? So... I am going to sudo-code what I am trying to do (below):
FOR (however many records) in Seeker_cur
IF seeker_cur (not found) or (returns no records)
EXIT or (break for loop);
ELSE
LOOP
vMarkCounter := vMarkCounter + 1;
EXIT WHEN seeker_cur is out of records (somehow)
END IF;
END LOOP;
END;
END SEEKER;
I am sure there are a few ways to do this. What ways would you suggest?
why dont you use implicit cursor , it will open and close itself:
DECLARE
CURSOR seeker_cur IS
Select Mokney_approved, Monkey_vaulted
from MonkeyBookApps
where MonkeyID = pMonkeyID
and Monkey_doc_type = 'BANANA'
order by docu_approv_timestamp,monkey_doc_type,monkey_doc_approved desc;
vMarkCounter number:=0;
BEGIN
FOR i IN seeker_cur
LOOP
vMarkCounter := vMarkCounter+1;
END LOOP;
dbms_output.put_line(vMarkCounter);
END;
It seems to me that the solution your problem might be as simple as this:
SELECT COUNT(*)
INTO l_some_local_variable
FROM monkey_book_apps
WHERE monkey_id = p_monkey_id
AND monkey_doc_type = 'BANANA';
RETURN l_some_local_variable;
Avoiding PL/SQL loops and using the simplest SQL possible is (almost always) the most efficient way. Tom Kyte calls the row-by-row execution of LOOPs "slow-by-slow".

Resources