Procedure to update column from function - oracle

I have a problem.
I need to create a procedure that fetches or_id loop from table1.
This id from table1 is a parameter calling function that returns two values d1 and d2.
The values d1 and d2 must be updated in table1.
How to do it? The function works correctly but I do not know how to loop it.
CURSOR rec_cur IS
SELECT s.or_id from table1 s;
id number;
a_BASKET_ID varchar(20);
a_ORDER_ID varchar (20);
BEGIN open rec_cur; loop
fetch rec_cur into number;
EXIT WHEN rec_cur%NOTFOUND;
SELECT cut(v_param,1,'#') a_BASKET_ID,
cut(v_param,2,'#') a_ORDER_ID
FROM (SELECT function1(or_id) v_param FROM dual);
UPDATE table1 b
SET BASKET_ID = a_BASKETID,
ORDER_ID = a_ORDERTYPE
WHERE b.or_id = s.or_id;
END LOOP;

This is one of the ugliest codes I've recently seen. Please, for your own sake (as well as ours), learn how to properly format & indent code and make it easier to read and follow.
Furthermore, it is invalid - lacks in DECLARE, there's no END, you declared some variables (a_basket_id, a_order_id) but used another ones (a_basketid, a_ordertype) ... quite a mess.
As of your question: as far as I understood, everything can be done in a single UPDATE statement, no PL/SQL is needed:
update table1 set
basket_id = cut(function1(or_id), 1, '#'),
order_id = cut(function1(or_id), 2, '#');
If you insist on PL/SQL, have a look at this: I've used cursor FOR loop as it is simpler to maintain than explicit cursor (as you don't have to create cursor variable(s), open the cursor, worry about exiting the loop, close the cursor - Oracle does it for you). Although you don't need local variables at all (nor PL/SQL, as I've already said), I let them be.
declare
a_basket_id table1.basket_id%type;
a_order_id table1.order_id%type;
begin
for cur_r in (select or_id from table1) loop
a_basket_id := cut(function1(cur_r.or_id), 1, '#');
a_order_id := cut(function1(cur_r.or_id), 2, '#');
update table1 set
basket_id = a_basket_id
order_id = a_order_id
where or_id = cur_r.or_id;
end loop;
end;

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;

Insert into not working on plsql in oracle

declare
vquery long;
cursor c1 is
select * from temp_name;
begin
for i in c1
loop
vquery :='INSERT INTO ot.temp_new(id)
select '''||i.id||''' from ot.customers';
dbms_output.put_line(i.id);
end loop;
end;
/
Output of select * from temp_name is :
ID
--------------------------------------------------------------------------------
customer_id
1 row selected.
I have customers table which has customer_id column.I want to insert all the customer_id into temp_new table but it is not being inserted. The PLSQL block executes successfully but the temp_new table is empty.
The output of dbms_output.put_line(i.id); is
customer_id
What is wrong there?
The main problem is that you generate a dynamic statement that you never execute; at some point you need to do:
execute immediate vquery;
But there are other problems. If you output the generated vquery string you'll see it contains:
INSERT INTO ot.temp_new(id)
select 'customer_id' from ot.customers
which means that for every row in customers you'll get one row in temp_new with ID set to the same fixed literal 'customer_id'. It's unlikely that's what you want; if customer_id is a column name from customers then it shouldn't be in single quotes.
As #mathguy suggested, long is not a sensible data type to use; you could use a CLOB but only really need a varchar2 here. So something more like this, where I've also switched to use an implicit cursor:
declare
l_stmt varchar2(4000);
begin
for i in (select id from temp_name)
loop
l_stmt := 'INSERT INTO temp_new(id) select '||i.id||' from customers';
dbms_output.put_line(i.id);
dbms_output.put_line(l_stmt);
execute immediate l_stmt;
end loop;
end;
/
db<>fiddle
The loop doesn't really make sense though; if your temp_name table had multiple rows with different column names, you'd try to insert the corresponding values from those columns in the customers table into multiple rows in temp_new, all in the same id column, as shown in this db<>fiddle.
I guess this is the starting point for something more complicated, but still seems a little odd.

PL/SQL %rowtype variable declaration represents inner join

In my PL/SQL script, how do I declare JUSTIFIC_REC when it represents a join?
SELECT *
INTO JUSTIFIC_REC
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
All I want is to insert into a TABLE3 a concatenated row:
INSERT INTO MOF_OUTACCDTL_REQ VALUES(
JUSTIFIC_rec.ENTRY_COMMENTS || ' ' || JUSTIFIC_rec.DESCRIPTION );
How should the declaration of JUSTIFIC_REC be like in the beginning of my script?
If it wasn't for the INNER JOIN , I would write something like:
JUSTIFIC_rec TABLE1%ROWTYPE;
If I understood correctly you can try with cursor rowtype like this (not sure if that is what you meant by declaring your variable type for the select with joins):
set serveroutput on;
declare
cursor cur is
SELECT ENTRY_COMMENTS, DESCRIPTION
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
justific_rec cur%ROWTYPE;
begin
open cur;
loop
fetch cur into justific_rec;
exit when cur%notfound;
dbms_output.put_line(justific_rec.entry_comments || ' ' || justific_rec.description);
end loop;
close cur;
end;
Answer to your question is itself in your question. You have to use the %row type
tow type attribute can be any of the below type:
rowtype_attribute :=
{cursor_name | cursor_variable_name | table_name} % ROWTYPE
cursor_name:-
An explicit cursor previously declared within the current scope.
cursor_variable_name:-
A PL/SQL strongly typed cursor variable, previously declared within the current scope.
table_name:-
A database table or view that must be accessible when the declaration is elaborated.
So code will looks like
DECLARE
CURSOR c1 IS
SELECT * FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
justific_rec c1%ROWTYPE;
BEGIN
open c1;
loop
fetch c1 into justific_rec;
exit when c1%notfound;
INSERT INTO MOF_OUTACCDTL_REQ VALUES(
JUSTIFIC_rec.ENTRY_COMMENTS || ' ' || JUSTIFIC_rec.DESCRIPTION );
end loop;
close c1;
END;
/
You need to use BULK COLLECT INTO
SELECT *
BULK COLLECT INTO JUSTIFIC_REC
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
The JUSTIFIC_REC is needed to be TABLE type with appropriate columns
If all you're wanting to do is insert into a table based on a select statement, then there's no need for PL/SQL (by which I mean there's no need to open a cursor, fetch a row, process the row and then move on to the next row) - that's row-by-row aka slow-by-slow processing.
Instead, you can do this all in a single insert statement, e.g.:
INSERT INTO mof_outaccdtl_rec (<column being inserted into>) -- please always list the columns being inserted into; this avoids issues with your code when someone adds a column to the table.
SELECT entry_comments || ' ' || description -- please alias your columns! How do we know which table each column came from?
FROM table1 a
inner join table2 b on a.id_justific = b.id_justific;
If you wanted to embed this insert statement in PL/SQL, all you need to do is add a begin/end around it, like so:
begin
INSERT INTO mof_outaccdtl_rec (<column being inserted into>) -- please always list the columns being inserted into; this avoids issues with your code when someone adds a column to the table.
SELECT entry_comments || ' ' || description -- please alias your columns! How do we know which table each column came from?
FROM table1 a
inner join table2 b on a.id_justific = b.id_justific;
end;
/
This is the preferred solution when working with databases - think in sets (where possible) not procedurally (aka row-by-row processing). It is easier to maintain, the code is simpler to read and write, and it'll be more performant since you're not having to switch between PL/SQL and SQL several times with each row.
Context switching is bad for performance - think in terms of a bath full of water - is it quicker to empty the bath with a spoon (row-by-row processing), with a jug (batched rows - by - batched rows) or by pulling the plug (set-based)?

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: update non-unique field

I need to update a non-unique field. I have a table tbl:
create table tbl (A number(5));
Values in tbl: 1, 2, 2, 2 .. 2.
I need to replace all 2 with new non-unique values
New values: 1, 100, 101, 102, 103 ..
I wrote:
DECLARE
sql_stmt VARCHAR2(500);
cursor curs is
select A from tbl group by A having count(*)>1;
l_row curs%ROWTYPE;
i number(5);
new_mail VARCHAR2(20);
BEGIN
i:=100;
open curs;
loop
fetch curs into l_row;
exit when curs%notfound;
SQL_STMT := 'update tbl set a='||i||' where a='||l_row.A;
i:=i+1;
EXECUTE IMMEDIATE sql_stmt;
end loop;
close curs;
END;
/
But I got:
A
----------
1
100
...
100
What can be wrong? Why doesn't the loop work?
what about
update tbl
set a = 100 + rownum
where a in (
select a
from tbl
group by a
having count(*) > 1 )
the subquery finds duplicated A fields and the update gives them the unique identifier starting from 100. (you got other problems here like , what if id 100, 101.... already exists ).
first rule of PLSQL says that what ever you can do with SQL always do with SQL. writing straight up for loop cause allot of context switches between the sql and pl/sql engine. even if oracle automatically converts this to a bulk statement (10g<) it will still be faster with pure SQL.
Your cursor gets one row per unique value of A:
select A from tbl group by A having count(*)>1;
You need to get all the distinct rows that match those values. One way is to do this:
select a, r from (
select a, rowid as r, count(*) over (partition by a) as c
from tbl
) where c > 1;
... and then use the rowid values to do the update. I'm not sure why you're using dynamic SQL as it is not at all necessary, and you can simplify (IMO) the loop:
declare
i number(5);
begin
i:=100;
for l_row in (
select a, r from (
select a, rowid as r, count(*) over (partition by a) as c
from tbl
) where c > 1) loop
update tbl set a=i where rowid = l_row.r;
i:=i+1;
end loop;
end;
/
I've kept this as PL/SQL to show what was wrong with what you were attempting, but #haki is quite correct, you should (and can) do this in plain SQL if at all possible. Even if you need it to be PL/SQL because you're doing other work in the loop (as the new_mail field might suggest) then you might be able to still do a single update within the procedure, rather than one update per iteration around the loop.

Resources