Oracle Insert Missing Records - oracle

I have written a package for building a reporting table. The simplified code for the function I am testing follows:
function do_build return integer is
V_RESULT PLS_INTEGER := 0;
cursor all_entities is
select e.id_number
from entity e
;
BEGIN
c_count := 0; -- this variable is declared at the package level outside of this function
for rec in all_entities LOOP
BEGIN
insert into reporting (
select *
from table(get_report_data(rec.id_number))
);
c_count := c_count + 1;
if MOD(c_count, 1000) = 0 Then
-- record status to table
commit;
end if;
EXCEPTION
WHEN OTHERS THEN
-- record exception to table
END;
END LOOP;
return V_RESULT;
END;
A little background: get_report_data is a function that returns a dataset with all of the input entity's reporting data.
About 1000 records out of 1 million are missing from the "reporting" table when the build completes. No exceptions are thrown and other than the missing records, everything appears to have been successful (function returns 0 to caller).
When I run the get_report_data for the entity records that do not have their reporting data recorded, the records show up fine. In fact, I can do an adhoc "insert into reporting (select * from table(get_reporting_data(missing_id))" and the information will be inserted.
Why would these records be skipped/fail to insert? Should I be looping a different way? Any better way to do it?

You're only committing every 1000 rows. You're not committing the last batch. Add a commit after the END LOOP;
BEGIN
c_count := 0; -- this variable is declared at the package level outside of this function
for rec in all_entities LOOP
BEGIN
insert into reporting (
select *
from table(get_report_data(rec.id_number))
);
c_count := c_count + 1;
if MOD(c_count, 1000) = 0 Then
-- record status to table
commit;
end if;
EXCEPTION
WHEN OTHERS THEN
-- record exception to table
END;
END LOOP;
COMMIT; -- <-- Add this commit to pick up last few records
return V_RESULT;
END;

Can this be a concurrency issue? If the records are committed in the ENTITY table while you loop is running they won't be processed.
BTW: Using WHEN OTHERS in this way is asking for trouble.
BTW2: Why not simply use:
INSERT INTO reporting
SELECT rep.*
FROM entity e
CROSS JOIN table(get_report_data(e.id_number)) rep;

Related

No data found in oracle

I have a procedure in oracle that looks like this
create or replace procedure check_display_stock as
id_brg number(20);
rowcount number (20);
begin
-- 1. insert datas from display into temp_display
insert into temp_display
select id_barang,stok,min_stok
from display
where stok <= min_stok;
--2. select the number of datas in temp_display
select count(rownum)
into rowcount
from temp_display;
while(rowcount != 0)
loop
-- Error: no data found
select id_barang
into id_brg
from temp_display
where rownum = 1;
--just another procedure to do other things
insert_spb(id_brg);
delete from temp_display where rownum = 1;
end if;
end loop;
end check_display_stock;
An error occurs when I tried to select into that says no data found.
I don't understand why this happened.
You never decrement rowcount so you will end up deleting the rows in temp_display one-by-one and then keep going (potentially forever) and on the next iteration after you have already emptied the table you will try to select id_barang into id_brg ... and it will fail as you have already emptied the table.
Instead, you could use BULK COLLECT INTO or a CURSOR to bypass the temporary table:
create or replace procedure check_display_stock
as
begin
FOR cur IN ( select id_barang,
stok,
min_stok
from display
where stok <= min_stok
)
LOOP
insert_spb(cur.id_barang);
END LOOP;
END;
/
db<>fiddle here

PL/SQL Unable to get return value of a function when called through trigger - Oracle

I am calling a function in oracle in an after update trigger. The Function is returning a value that is equated to perform a select and an insert operation.
The issue is when I am calling this function in the trigger it is getting terminated, that is it is not performing the corresponding insert operation. But the function is working fine when I execute it by itself. Also, if the trigger is run by removing the condition which is returned by the function, it is getting executed as expected.
Function:
CREATE OR REPLACE FUNCTION VERIFY_FINAL
(case_id IN number)
RETURN varchar2
IS
is_marked_final varchar2(4);
loop_count number(2);
cursor c1 is
SELECT sub_case_status from
cdm_master_sub_case
where master_id = (case_id);
BEGIN
is_marked_final := 'Y';
loop_count := 0;
FOR rec in c1
LOOP
IF (rec.sub_case_status = '1') THEN
is_marked_final := 'Y';
ELSIF (rec.sub_case_status = '2') THEN
is_marked_final := 'Y';
ELSE
loop_count := loop_count + 1;
END if;
END LOOP;
IF (loop_count > 0) THEN
is_marked_final := 'N';
END if;
RETURN is_marked_final;
END;
Trigger:
CREATE OR REPLACE TRIGGER CDM_MASTER_SUB_CASE_TRIGGER
AFTER UPDATE
on CDM_MASTER_SUB_CASE
FOR EACH ROW
DECLARE
check_var varchar2(4);
unique_id varchar2(100);
transaction_id number(10);
BEGIN
transaction_id := :new.MASTER_ID;
check_var := VERIFY_FINAL(transaction_id);
IF (check_var = 'Y') THEN
select UNIQUE_CUST_ID
INTO unique_id
from ASM355.cdm_matches
where MASTER_ID = :new.MASTER_ID
and rownum = 1;
INSERT INTO tracking_final_cases (MASTER_ID,unique_cust)
values (:new.master_id,unique_id);
END if;
END;
I would appreciate it if anyone can point me in the right direction.
1.) As tmrozek points out a return of 'N' will not do the associated insert. I might suggest having an ELSE to that IF that does something to indicate if that is what is happening.
2.) I would also point out that your SELECT INTO, if it does not find a corresponding value, would cause issues. You might want to do something to ensure that this trigger is failsafe, or have you considered what you want the code to do if that situation occurs? (Error out? Insert a null unique_id?)
3.) If you are looking at the results from a different session, bear in mind that the inserted tracking_final_cases will not be visible until you commit your changes in the session that called the trigger.
I don't know your table data but it is possible to your function to return 'N' so it wouldn't meet your trigger condition (check_var = 'Y').
If you run command like that:
update CDM_MASTER_SUB_CASE
set sub_case_status = 3;
you will probably get your problem.
Thanks guys for the time, it got resolved. I was querying a select statement in the function body over a table on which the corresponding trigger was created.

Strange behaviour of BULK COLLECT

I tinkered together the following PL/SQL BULK-COLLECT which works astonishingly fast for updates on huge tables (>50.000.000). The only problem is, that it does not perform the updates of the remaining < 5000 rows per table. 5000 is the given limit for the FETCH instruction:
DECLARE
-- source table cursor (only columns to be updated)
CURSOR base_table_cur IS
select a.rowid, TARGET_COLUMN from TARGET_TABLE a
where TARGET_COLUMN is null;
TYPE base_type IS
TABLE OF base_table_cur%rowtype INDEX BY PLS_INTEGER;
base_tab base_type;
-- new data
CURSOR new_data_cur IS
select a.rowid,
coalesce(b.SOURCE_COLUMN, 'FILL_VALUE'||a.JOIN_COLUMN) TARGET_COLUMN from TARGET_TABLE a
left outer join SOURCE_TABLE b
on a.JOIN_COLUMN=b.JOIN_COLUMN
where a.TARGET_COLUMN is null;
TYPE new_data_type IS TABLE OF new_data_cur%rowtype INDEX BY PLS_INTEGER;
new_data_tab new_data_type;
TYPE row_id_type IS TABLE OF ROWID INDEX BY PLS_INTEGER;
row_id_tab row_id_type;
TYPE rt_update_cols IS RECORD (
TARGET_COLUMN TARGET_TABLE.TARGET_COLUMN%TYPE
);
TYPE update_cols_type IS
TABLE OF rt_update_cols INDEX BY PLS_INTEGER;
update_cols_tab update_cols_type;
dml_errors EXCEPTION;
PRAGMA exception_init ( dml_errors,-24381 );
BEGIN
OPEN base_table_cur;
OPEN new_data_cur;
LOOP
FETCH base_table_cur BULK COLLECT INTO base_tab LIMIT 5000;
IF base_table_cur%notfound THEN
DBMS_OUTPUT.PUT_LINE('Nothing to update. Exiting.');
EXIT;
END IF;
FETCH new_data_cur BULK COLLECT INTO new_data_tab LIMIT 5000;
FOR i IN base_tab.first..base_tab.last LOOP
row_id_tab(i) := new_data_tab(i).rowid;
update_cols_tab(i).TARGET_COLUMN := new_data_tab(i).TARGET_COLUMN;
END LOOP;
FORALL i IN base_tab.first..base_tab.last SAVE EXCEPTIONS
UPDATE (SELECT TARGET_COLUMN FROM TARGET_TABLE)
SET row = update_cols_tab(i)
WHERE ROWID = row_id_tab(i);
COMMIT;
EXIT WHEN base_tab.count < 5000; -- changing to 1 didn't help!
END LOOP;
COMMIT;
CLOSE base_table_cur;
CLOSE new_data_cur;
EXCEPTION
WHEN dml_errors THEN
FOR i IN 1..SQL%bulk_exceptions.count LOOP
dbms_output.put_line('Some error occured');
END LOOP;
END;
Where is my mistake? It looks correct to me though.
The problem is this line:
IF base_table_cur%notfound THEN
The cursor meets %NOTFOUND when the number of records found is less than the LIMIT value. So if the last fetch is not exactly 5000 those records won't be processed.
It's a common gotcha for people using BULK COLLECT ... LIMIT for the first time. The solution is to change the exit condition to
EXIT when base_tab.count() = 0;
"I need to ensure, that the base_table_cur is not empty and exit if it is. I'l get an error if it is empty"
The new_data_cur cursor includes the table which is selected in base_table_cur cursor. So I don't think you need the two loops. You need a simple test to see whether the first cursor returns something, then just loop round the second cursor.
I'm not entirely clear on your logic, so I have changed as little as possible to demonstrate the sort of structure I think you need. However, the UPDATE statement looks a little odd, so you may still run into issues.
OPEN base_table_cur;
FETCH base_table_cur BULK COLLECT INTO base_tab LIMIT 1;
if base_table_tab.count = 0 then
DBMS_OUTPUT.PUT_LINE('Nothing to update. Exiting.');
else
OPEN new_data_cur;
LOOP
FETCH new_data_cur BULK COLLECT INTO new_data_tab LIMIT 5000;
exit when new_data_tab.count() = 0;
FOR i IN base_tab.first..base_tab.last LOOP
row_id_tab(i) := new_data_tab(i).rowid;
update_cols_tab(i).TARGET_COLUMN := new_data_tab(i).TARGET_COLUMN;
END LOOP;
FORALL i IN base_tab.first..base_tab.last SAVE EXCEPTIONS
UPDATE (SELECT TARGET_COLUMN FROM TARGET_TABLE)
SET row = update_cols_tab(i)
WHERE ROWID = row_id_tab(i);
END LOOP;
CLOSE new_data_cur;
end if;
COMMIT;
CLOSE base_table_cur;

Oracle Function that return table of number problems and performance

I have (sometimes) a memory block in my oracle database that turn crasy... a lot of session sundenless block each other and the probleme is in a function that return a table of number and is use in another procedure.
Edit : Sessions is 'blocked' with Read By Other Session Wait Event
First, my table of number :
CREATE OR REPLACE TYPE liste_lots as TABLE OF number(10)
and in large the function who populate the table :
function get_ot_idem_cursor( .. ) return liste_lots is
res_type liste_lots;
p_restriction_level number;
cursor curs_lvl_1 is select [...] ;
row_lvl_1 curs_lvl_1%rowtype;
cursor curs_lvl_0 is select [...] ;
row_lvl_0 curs_lvl_0%rowtype;
begin
res_type := liste_lots();
p_restriction_level := get_edi_line_restriction(p_edi_line);
if p_restriction_level = 1 then
open curs_lvl_1;
loop
fetch curs_lvl_1 into row_lvl_1;
exit when curs_lvl_1%notfound;
begin
res_type.extend;
res_type(res_type.last) := row_lvl_1.lot_id;
exception
when others then
dbms_output.put_line('problème get_ot_idem_cursor ');
dbms_output.put_line(sqlerrm);
close curs_lvl_1;
end;
end loop;
close curs_lvl_1;
else
open curs_lvl_0;
loop
fetch curs_lvl_0 into row_lvl_0;
exit when curs_lvl_0%notfound;
begin
res_type.extend;
res_type(res_type.last) := row_lvl_0.lot_id;
exception
when others then
dbms_output.put_line('problème get_ot_idem_cursor ');
dbms_output.put_line(sqlerrm);
close curs_lvl_0;
end;
end loop;
close curs_lvl_0;
end if;
return res_type;
exception
when others then
if curs_lvl_0%isopen then
close curs_lvl_0;
end if;
if curs_lvl_1%isopen then
close curs_lvl_1;
end if;
end;
and is used on another part like that :
liste_ots := get_ot_idem_cursor(v_lot, v_sr_ligne_lot.id );
select min(l.lot_id) into result
from lot l
where l.des_tiers_id = p_pf_tiers_id
and l.lot_nature = 'POS'
and l.exp_tiers_id = v_sr_ligne_lot.ramasse_tiers_id
and ot_id in ((select * from TABLE(liste_ots)))
and l.lot_datheurcharg > sysdate - 3;
When the db become crasy (session block, very slow) this is the part of the code who is pointed :
select * from TABLE(liste_ots)
the problem is not all the time, then if you have any idea or advise...
thanks in advance (sorry for my bad english)
Use a bulk collect instead of a plain loop (fetching records one by one) as there is no additional logic done in the loop. In general, always avoid switching a context (SQL to/from PL/SQL)
open curs_lvl_1;
fetch curs_lvl_1 bulk collect into res_type;
close curs_lvl_1;

given two scripts for deleting records in table(6 million rows) want to know which script will be better and why?

I have table with 6 million records I am running archival script to delete around 5 million records.
My script1 will do the deletion but the DBA said my script1 getting into more buffer gets and
he recommended the approach which is script2. I am confused how come script2 is better than script1.
So please review the scripts and reply which is the best approach and why.
script1 :
PROCEDURE archival_charging_txn(p_no_hrs IN NUMBER,
p_error_code OUT NUMBER,
p_error_msg OUT VARCHAR2) IS
v_sysdate DATE := SYSDATE - p_no_hrs / 24;
TYPE t_txn_id IS TABLE OF scg_charging_txn.txn_id%TYPE INDEX BY PLS_INTEGER;
v_txn_id t_txn_id;
CURSOR c IS
SELECT txn_id FROM scg_charging_txn WHERE req_time < v_sysdate; /* non unique index */
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT
INTO v_txn_id LIMIT 10000;
IF v_txn_id.COUNT > 0 THEN
FORALL i IN v_txn_id.FIRST .. v_txn_id.LAST
DELETE FROM scg_charging_txn WHERE txn_id = v_txn_id(i); /* Primary key based */
END IF;
COMMIT;
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
COMMIT;
p_error_code := 0;
EXCEPTION
WHEN OTHERS THEN
p_error_code := 1;
p_error_msg := substr(SQLERRM, 1, 200);
END archival_charging_txn;
script 2:
PROCEDURE archival_charging_txn_W(p_no_hrs IN NUMBER,
p_error_code OUT NUMBER,
p_error_msg OUT VARCHAR2) IS
BEGIN
DELETE FROM scg_charging_txn WHERE req_time < SYSDATE - p_no_hrs / 24;
COMMIT;
p_error_code := 0;
EXCEPTION
WHEN OTHERS THEN
p_error_code := 1;
p_error_msg := substr(SQLERRM, 1, 200);
END archival_charging_txn_W;
the 1st script reads the table entries using the cursor, therefore more buffer gets, while the 2nd script just deletes the table entries.
I would prefer the 2nd script. in case you get troubles with locking the table for too log time, make a LOOP UNTIL SQL%NOTFOUND with DELETE ... AND ROWNUM <= 10000; COMMIT; inside.
If you're deleting 5 million of 6 million rows, performance will suck.
You're better off doing CTAS.
Something like:
create table new_scg_charging_txn nologging as select * from scg_charging_txn WHERE req_time >= SYSDATE - p_no_hrs / 24;
If downtime is unacceptable, you may be able to do something similar, but wrap it with DBMS_REDEFINITION.

Resources