How to have my Oracle PL/SQL block update all entries related to the row_ids referenced by my cursor - oracle

I currently have a Oracle stored procedure that is taking data from tables. The problem is I am aggregating /grouping , and I don't want to grab the IDs otherwise that will throw the grouping off. I want to update a column called 'correlated_flag_id' to '1' (done) in the value table after ive inserted the aggregated/grouped result set. I only want to grab IDs that are correlated to the
values that my first cursor grabbed to derive the results. Below is my attempt (which I don't think is correct):
Create or Replace PROCEDURE PROC is
CURSOR c1 is
select sum(v.value_tx) as sum_of_values
, max(v.create_dt) as latest_create_dt
, v.data_date
from value v
group by v.data_date, max(v.create_dt)
BEGIN
Open c1;
LOOP
Fetch c1 into l_var;
insert into value (value_id, value_tx, create_dt, data_date)
values (null, l_var.sum_of_values, l_var.latest_create_dt, l_var.data_Date);
END LOOP;
Close c1;
commit;
--- the bottom is not correct, but i've reached a roadblock
Update value
set correlated_flag_id = 777
where value_id in (select v.value_id from value where trunc(create_dt) <> trunc(sysdate)) (???));
commit;
END PROC;
Thanks in advance and please let me know if there is any more details that I need to provide.

cursor's select is kind of wrong; why are you grouping by a MAX function? It isn't allowed here
switch to cursor FOR loop as it is easier to maintain. You don't have to open a cursor, fetch, exit the loop (which you did not do at all), close the cursor
I'm not sure what VALUE_930 table is doing here, you never mentioned it
your words say "update correlated ID to 1", while code says "update it to 777"
don't commit in a procedure; let caller decide whether it should be done
I'd suggest you to either use a tool which offers code formatting, or format it yourself. Your procedure is not a result of a flood, so don't treat it that way
Finally, here's a suggestion which might (or might not) work as we don't have your tables nor data, but - at least - looks decent.
create or replace procedure proc is
begin
for cur_r in (select v.data_date,
sum(v.value_tx) as sum_of_values,
max(v.create_dt) as latest_create_dt
from value v
group by v.data_date)
loop
insert into value (value_id, value_tx, create_dt, data_date)
values (null, cur_r.sum_of_values, cur_r.latest_create_dt, cur_r.data_date);
update value set
correlated_flag_id = 777
where data_date = cur_r.data_date;
end loop;
end proc;
/

Related

PL/SQL Cursor whose query is based on a variable

I've got a PL/SQL block that's basically
DECLARE
PIDM NUMBER(8);
CLM_TEST_SCORE NUMBER(5);
CURSOR C_STUDENT IS
select PIDM
from SOSC.DW_ALL_COLLECTOR
order by PIDM;
CURSOR C_CLM_SCORES IS
select max(to_number(SORTEST_TEST_SCORE))
from SATURN.SORTEST
where SORTEST_PIDM = pidm;
BEGIN
OPEN C_STUDENT;
LOOP
CLM_TEST_SCORE := '';
FETCH c_Student INTO pidm;
EXIT WHEN c_Student%notfound;
OPEN C_CLM_SCORES;
FETCH C_CLM_SCORES INTO CLM_TEST_SCORE;
CLOSE C_CLM_SCORES;
insert into some_table (CLM_TEST_SCORE)
values (CLM_TEST_SCORE);
END LOOP
END
As far as I'm aware, the pidm referred to in C_CLM_SCORES is the PIDM NUMBER(8) declared in line 2. That would mean that the query the cursor refers to mutates every iteration of the LOOP, depending on the current value of pidm. That doesn't jive with my understanding of cursors as a query-in-progress, as the underlying query changes every LOOP. Maybe it's the original author taking advantage of a clever DB algorithm?
This code works. I just have absolutely no idea why. What the heck is going on here?
You have an overly confusing block of code that is a nightmare to debug as you have:
SQL statements that refer to column name and local variables with the same identifier (PIDM and CLM_TEST_SCORE).
Cursors that change every iteration because they contain a bind variable referring to local variables (PIDM).
Highly inefficient use of loops.
If you want to make it clearer, you can rewrite the PL/SQL block so that you do not have duplicate identifiers and use a parameterised cursor:
DECLARE
v_PIDM SOSC.DW_ALL_COLLECTOR.PIDM%TYPE;
v_CLM_TEST_SCORE some_table.CLM_TEST_SCORE%TYPE;
CURSOR C_STUDENT IS
select PIDM
from SOSC.DW_ALL_COLLECTOR
order by PIDM;
CURSOR C_CLM_SCORES(p_pidm NUMBER) IS
select max(to_number(SORTEST_TEST_SCORE))
from SATURN.SORTEST
where SORTEST_PIDM = p_pidm;
BEGIN
OPEN C_STUDENT;
LOOP
FETCH c_Student INTO v_pidm;
EXIT WHEN c_Student%notfound;
OPEN C_CLM_SCORES(v_pidm);
FETCH C_CLM_SCORES INTO v_CLM_TEST_SCORE;
CLOSE C_CLM_SCORES;
insert into some_table (CLM_TEST_SCORE)
values (v_CLM_TEST_SCORE);
END LOOP;
END;
/
However, that is still very inefficient as each iteration performs a SELECT and an INSERT and will generate log entries. You can make it much simpler and more efficient to rewrite the whole thing as a single SQL statement:
INSERT INTO some_table (clm_test_score)
SELECT ( select max(to_number(SORTEST_TEST_SCORE))
from SATURN.SORTEST s
where s.SORTEST_PIDM = c.pidm )
FROM SOSC.DW_ALL_COLLECTOR c;
db<>fiddle here
The code in the question is an advertisement for "Why should implicit cursors be used?". If you rewrite your code as below it becomes much easier to understand:
BEGIN
FOR rowStudent IN (select PIDM
from SOSC.DW_ALL_COLLECTOR
order by PIDM)
LOOP
FOR rowScores IN (select max(to_number(SORTEST_TEST_SCORE)) AS CLM_TEST_SCORE
from SATURN.SORTEST
where SORTEST_PIDM = rowStudent.PIDM)
LOOP
insert into some_table (CLM_TEST_SCORE)
values (rowScores.CLM_TEST_SCORE);
END LOOP; -- rowScores
END LOOP; -- rowStudent
END;
This eliminates all of the variables and cursor definitions, and all the code is right in front of you where you can see it at a glance.
If you wanted to tighten it up a bit further you could use a join to get down to just one cursor:
BEGIN
FOR rowStudent_scores IN (SELECT d.PIDM, MAX(TO_NUMBER(s.SORTEST_TEST_SCORE)) AS CLM_TEST_SCORE
FROM SOSC.DW_ALL_COLLECTOR d
INNER JOIN SATURN.SORTEST s
ON s.SORTEST_PIDM = d.PIDM
GROUP BY d.PIDM)
LOOP
insert into some_table (CLM_TEST_SCORE)
values (rowStudent_scores.CLM_TEST_SCORE);
END LOOP; -- rowStudent_scores
END;

Getting the corresponding record of a Cursor/Select when in a Cursor LOOP statement

The question seems easy. I have built a package, where there is a quite massive cursor, let's say on all invoices of my company for the whole year.
CURSOR c_invoices(p_year IN INTEGER) IS
SELECT all_invoices.invoicenumber,
all_invoices.invoicedate,
all_invoices.customernumber
FROM all_invoices
WHERE all_invoices.year = p_year
;
After opening it and using a LOOP statement, I want to get some data from another table (forbidden_customers), but only if the customer is in this very last table.
What I'd like to do, is to open another cursor (or a SELECT ?) at the very beginning of my package, browsing the whole table(forbidden_customers), and then getting to the corresponding record when in my invoices LOOP.
So, something like :
CURSOR c_forbidden_customers IS
SELECT forbidden_customers.customernumber,
forbidden_customers.customeradress
FROM forbidden_customers
;
And then :
OPEN c_invoices(v_year);
LOOP FETCH c_invoices INTO invoices_cursor;
BEGIN
EXIT WHEN c_invoices%NOTFOUND;
*IF invoices_cursor.customernumber IS FOUND IN c_forbidden_customers ...
THEN ...*
This is what I do meanwhile (I know it is bad):
SELECT COUNT(*)
INTO v_exist /*INTEGER*/
FROM forbidden_customers
WHERE forbidden_customers.customernumber= p_customernumber
IF v_exist <> 0
THEN...
I tried to make it as clear as possible. Thank you for your time
Don't do it twice; join both tables in the same cursor and use it. Also, if you switch to a cursor FOR loop, you'll save yourself from some typing as Oracle will do most of boring stuff for you (declaring cursor variable, opening the cursor, closing it, exiting the loop ...):
create or replace procedure p_test (p_year in integer) is
begin
for c_invoices in
(select a.invoicenumber,
a.invoicedate,
a.customernumber,
c.customeraddress
from all_invoices a join forbidden_customers c on c.customernumber = a.customernumber
where a.year = p_year)
loop
-- do something
end loop;
end;
If the table forbidden_customers is not large and it will fit oracle's session memory, you can use a pl/sql table to store all id's from forbidden_customers and check it later. The check is done in memory only, so it is much faster than any regular select.
create table all_invoices
(id number,
year number,
customer_number number);
create table forbidden_customers
(customer_number number);
CREATE OR REPLACE TYPE t_number_table IS TABLE OF number
/
CREATE OR REPLACE PROCEDURE test23
IS
forbidden_customers_list t_number_table;
CURSOR c_invoices (p_year IN INTEGER)
IS
SELECT all_invoices.customer_number
FROM all_invoices
WHERE all_invoices.year = p_year;
BEGIN
SELECT customer_number
BULK COLLECT INTO forbidden_customers_list
FROM forbidden_customers;
FOR rec_invoices in c_invoices(2022) loop
if forbidden_customers_list.exists(rec_invoices.customer_number) then
null;
end if;
end loop;
end;
/

Code not working. It takes an eternity to run

The code below runs for an eternity.
As you can see i have to take values from one table and use that value to check if the second table contains it or not and insert into the third table values from the first table.
Is there any other way of doing this?
create or replace PROCEDURE KPI_AVAILABILITY (
v_programid varchar2
)
AS
v_MASTER_KPI_ID number;
v_UDF varchar2(100);
v_count number;
cursor c1 is
(select MASTER_KPI_ID,UDF from KPI_MASTER
where UDF is not null
and ISACTIVE = 1
--order by MASTER_KPI_ID,udf
);
BEGIN
open c1 ;
fetch c1 into v_MASTER_KPI_ID,v_UDF;
while v_UDF is not null
loop
select count(v_UDF) into v_count
from vw_ticket
where v_UDF is not null
and amsprogramid = v_programid;
if v_count is not null or v_count <> 0 then
delete from program_kpi where amsprogramid = v_programid;
INSERT INTO PROGRAM_KPI (AMSPROGRAMID,MASTER_KPI_ID,LASTUPDATEDBYDATALOAD)
VALUES(V_PROGRAMID,v_MASTER_KPI_ID,to_char(sysdate,'dd-mon-yy hh.mi.ss'));
dbms_output.put_line('xyz');
end if;
end loop;
close c1;
END KPI_AVAILABILITY;
Reverse engineering business rules from another developer's code is always tricky, especially without understanding the wider domain. However, at the centre of the loop is DELETE from program_kpi followed by an INSERT into the same table. If there are no records matching on amsprogramid = v_programid then you're inserting a record, if there are matches then effectively you're just updating lastupdatedbydataload with the current SYSDATE.
In others, it appears to be the logic of a MERGE. So perhaps your code could be entirely replaced with a single statement. If so, this is likely to be a lot more efficient than the row-by-agonizing-row process within a cursor loop.
merge into program_kpi pkpi
using (select kpim.master_kpi_id
, kpim.udf
, v_programid
from kpi_master kpim
where kpim.udf is not null
and kpim.isactive = 1
and exists ( select null
from vw_ticket tkt
where tkt.amsprogramid = v_programid)
) kpim
on (kpim.v_programid = pkpi.programid
and kpim.master_kpi_id = pkpi.master_kpi_id)
when not matched then
insert values (kpim.v_programid, kpim.master_kpi_id, sysdate)
when matched then
update
set pkpi.lastupdatedbydataload = sysdate;
Please check the results of this code with your expected outcome. As I said, reverse-engineering business logic is hard, and matching on master_kpi_id as well as programid is not the same as just deleting on programid.
You do not change v_UDF after first fetch. Then loop compare it with same first value... compare and compare... compare and compare.

Oracle 11g PL/SQL cursor,intersect query

I have the following query
select distinct name from table1
intersect
select distinct name from table2;
I load the resultset into a cursor in a PL/SQL procedure, like so:
cursor c1 is (select distinct name from table1
intersect
select distinct name from table2);
For some reason the last value in the resultset is duplicated in the cursor. This does not happen when running the query by itself. Any ideas why this is happening?
Code for the loop:
var table.col%type;
BEGIN
OPEN c1;
LOOP
BEGIN
exit when c1%NOTFOUND;
FETCH c1 into var;
INSERT INTO table values (col1, var);
commit;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
CONTINUE;
END;
END LOOP;
END;
EXIT WHEN .. clause should come after FETCH.
Let's say, your cursor had 10 records to return. Before the first fetch is fired, %NOTFOUND evaluated to NULL, and the processing moves to the next statement, which is FETCH in your case. Now, if we fast forward to the 10th iteration, FETCH will get the 10th record, and the same is inserted into your destination table. The loop will move ahead, and since your EXIT WHEN %NOTFOUND is before fetch, it still has the value from last iteration, and it lets the control move ahead, and there, fetch will not be able to get any record, but the code will anyhow insert the last row it retrieved in 10th iteration. Now in the next loop, c1%NOTFOUND will be evaluated to TRUE and the loop will terminate
var table.col%type;
BEGIN
OPEN c1;
LOOP
BEGIN
FETCH c1 into var;
exit when c1%NOTFOUND;
INSERT INTO table values (col1, var);
commit;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
CONTINUE;
END;
END LOOP;
END;
This is typical of the problems you see with explicit cursors.
Your first choice should be a single SQL statement, nothing more.
If you had to use a cursor, you should be using an implicit one wherever possible.

Simple oracle insert

I am trying to simply insert some information to a table in Oracle using Forms. Sometimes the insert statement works, sometimes it doesn't. I'm just not experienced enough with Oracle to understand what's not working. Here's the code:
PROCEDURE create_account IS
temp_name varchar2(30);
temp_street varchar2(30);
temp_zip number(5);
temp_phone varchar2(30);
temp_login passuse.login%type;
temp_pass varchar2(30);
temp_total number(4);
temp_lgn passuse.lgn%type;
cursor num_cursor is
select MAX(ano)
from accounts;
cursor lgn_cursor is
select MAX(lgn)
from passuse;
BEGIN
temp_name:= Get_Item_Property('ACCOUNTS.A_NAME', database_value);
temp_street:= Get_Item_Property('ACCOUNTS.STREET', database_value);
temp_zip:= Get_Item_Property('ACCOUNTS.ZIP', database_value);
temp_phone:= Get_Item_Property('ACCOUNTS.STREET', database_value);
temp_login:= Get_Item_Property('PASSUSE.LOGIN', database_value);
temp_pass:= Get_Item_Property('PASSUSE.PASS', database_value);
open num_cursor;
fetch num_cursor into temp_total;
open lgn_cursor;
fetch lgn_cursor into temp_lgn;
if(lgn_cursor%found) then
if(num_cursor%found) then
temp_lgn := temp_lgn + 20;
--the trouble maker..
INSERT INTO passuse (lgn, a_type, login, pass)
VALUES (temp_lgn, 1, temp_login, temp_pass);
temp_total := temp_total+1;
INSERT INTO accounts(ano,lgn,a_name,street,zip,phone)
VALUES (temp_total,temp_lgn,temp_name,temp_street,temp_zip,temp_phone);
end if;
end if;
close lgn_cursor;
close num_cursor;
commit;
END;
To expand on #Mikpa's comment - it certainly appears that getting the values for ACCOUNTS.ANO and PASSUSE.LGN from sequences would be a good idea. Populating these fields automatically by using a trigger would also be helpful. Something like the following:
-- Note that the following is intended as a demo. When executing these statements
-- you'll probably have to modify them for your particular circumstances.
SELECT MAX(ANO) INTO nMax_ano FROM ACCOUNTS;
SELECT MAX(LGN) INTO nMax_lgn FROM PASSUSE;
CREATE SEQUENCE ACCOUNTS_SEQ START WITH nMax_ano+1;
CREATE SEQUENCE PASSUSE_SEQ START WITH nMax_lgn+1;
CREATE TRIGGER ACCOUNTS_BI
BEFORE INSERT ON ACCOUNTS
FOR EACH ROW
BEGIN
SELECT ACCOUNTS_SEQ.NEXTVAL
INTO :NEW.ANO
FROM DUAL;
END ACCOUNTS_BI;
CREATE TRIGGER PASSUSE_BI
BEFORE INSERT ON PASSUSE
FOR EACH ROW
BEGIN
SELECT PASSUSE_SEQ.NEXTVAL
INTO :NEW.LGN
FROM DUAL;
END PASSUSE_BI;
Having done the above you can now write your inserts into these tables as
INSERT INTO passuse (a_type, login, pass)
VALUES (1, temp_login, temp_pass)
RETURNING LGN INTO temp_lgn;
and
INSERT INTO accounts(lgn, a_name, street, zip, phone)
VALUES (temp_lgn, temp_name, temp_street, temp_zip, temp_phone);
Note that in the both statements the key values (PASSUSE.LGN and ACCOUNTS.ANO) are not mentioned in the field list as the new triggers should take care of filling them in correctly. Also note that when inserting into PASSUSE the RETURNING clause is used to get back the new value for LGN so it can be used in the insert into the ACCOUNTS table.
Share and enjoy.

Resources