Refactor code without cursor PL/SQL - oracle

How can I refactor these lines of code without using CURSOR?
I am beginner in PL/SQL.
Any help would be appreciated. Thank you
DECLARE
CURSOR c_emps IS
SELECT employee_id
FROM bonus;
v_region HR.REGIONS.region_name%TYPE;
v_salary hr.employees.salary%TYPE;
BEGIN
FOR r_emps IN c_emps LOOP
SELECT reg.region_name, emp.salary
INTO v_region, v_salary
FROM hr.employees emp,
hr.departments dep,
hr.Locations loc,
hr.countries cot,
hr.regions reg
WHERE emp.department_id = dep.department_id AND
dep.location_id = loc.location_id AND
loc.country_id = cot.country_id AND
cot.region_id = reg.region_id AND
employee_id = r_emps.employee_id;
IF v_region = 'Europe' THEN
UPDATE bonus
SET bonus = bonus + (v_salary * .01)
WHERE employee_id = r_emps.employee_id;
ELSE
UPDATE bonus
SET bonus = v_salary * .01
WHERE employee_id = r_emps.employee_id;
END IF;
END LOOP;
COMMIT;
END;
/

CURSOR c_emps IS
SELECT employee_id
FROM bonus;
You don't need to explicitly declare the CURSOR. You could do it in the CURSOR FOR LOOP itself:
FOR r_emps IN (SELECT employee_id FROM bonus)
LOOP
If PL/SQL is not mandatory, then you could do it in plain SQL using CASE expression in the UPDATE statement.
Something like,
UPDATE bonus
SET bonus =
CASE
WHEN region = 'Europe'
THEN bonus + (v_salary * .01)
ELSE v_salary * .01
...
and so on
Yes, you need to rewrite the entire PL/SQL code into a SQL update statement. But, it would be much better and faster. For loop is row-by-row processing, thus it is slow-by-slow. Avoid PL/SQL if you could do the same in SQL.

When working with SQL Server, an awful lot of effort is taken to avoid cursors because they are handled very poorly. Using a cursor in SQL Server is like slogging waist-deep through molasses. Oracle handles cursors much better so you see a lot more row-by-row work in Oracle. Too much, really. Even in Oracle, if something can be done with a single SQL statement, it is far superior than using PL/SQL cursors and looping.
Unfortunately, Oracle doesn't allow joins in UPDATE statements. But not to worry, the more recent innovation, the MERGE statement does.
MERGE INTO BONUS B
USING(
SELECT EMP.EMPLOYEE_ID, EMP.SALARY, REG.REGION_NAME
FROM HR.EMPLOYEES EMP
JOIN HR.DEPARTMENTS DEP
ON DEP.DEPARTMENT_ID = EMP.DEPARTMENT_ID
JOIN HR.LOCATIONS LOC
ON LOC.LOCATION_ID = DEP.LOCATION_ID
JOIN HR.COUNTRIES COT
ON COT.COUNTRY_ID = LOC.COUNTRY_ID
JOIN HR.REGIONS REG
ON REG.REGION_ID = COT.REGION_ID ) U
ON( U.EMPLOYEE_ID = b.EMPLOYEE_ID )
WHEN MATCHED THEN
UPDATE SET B.BONUS =( u.SALARY * 0.01 ) +
CASE U.REGION_NAME WHEN 'Europe' THEN B.BONUS ELSE 0 END;
How nice that the when not matched clause is not required, effectively turning the merge into a very flexible update.

Related

PLSQL Loop taking lot of time to execute

I have a Oracle procedure which is updating below 10,000 records. if I run the normal SQL statement, it is returning the result immediately with in seconds(30).
same statment in procedure loop it is going to endlessly.
My loop statment below.
Note: data FIELD Data type is a clob not varchar2.
statment:
select
'LB_COPY_CHANGE-'||8 LAST_MODIFIED_BY,
rec.COR_ID_old,
rec.COR_ID_NEW,
replace(replace(replace(a.data,'''id'':'||rec.COR_ID_OLD||',','''id'':'||rec.COR_ID_NEW||','),''id':'||rec.COR_ID_OLD||',',''id':'||rec.COR_ID_NEW||','),'''id'':'||rec.COR_ID_OLD||',','''id'':'||rec.COR_ID_NEW||',') as data
from KPI_MET_FIELD_DATA a, CUSTOM_TEMP_TABLE_SESSION_1 rec
where A.cmf_fk_id in (145,146,147)
and TYPE_LB in (14,15,16)
and a.KDB_FK_ID in (
select distinct km.KDB_FK_ID
from KPI_MET_FIELD_DATA km , KPI_DET_BASE kp, KPI_REL_KPI_SCORECARD ksc, STR_DET_EMP_SCORECARD sc
where ksc.SDE_FK_ID=sc.SDE_PK_ID
and km.KDB_FK_ID = ksc.KDB_KPI_FK_ID
and km.is_deleted=0
and kp.kdb_pk_id = km.KDB_FK_ID
and kp.is_deleted=0
and km.cmf_fk_id in (145,146,147)
and sc.sdp_fk_id = 8)
and a.is_deleted=0
and (a.data like '%'||rec.COR_ID_OLD||'%');
FOR rec in (SELECT * FROM CUSTOM_TEMP_TABLE_SESSION where TYPE_LB in (14,15,16)) LOOP
update KPI_MET_FIELD_DATA
set LAST_MODIFIED_BY='LB_COPY_CHANGE-'||p2 ,
data = replace(replace(replace(data,'''id'':'||rec.COR_ID_OLD||',','''id'':'||rec.COR_ID_NEW||','),''id':'||rec.COR_ID_OLD||',',''id':'||rec.COR_ID_NEW||','),'''id'':'||rec.COR_ID_OLD||',','''id'':'||rec.COR_ID_NEW||',')
where cmf_fk_id in (145,146,147)
and KDB_FK_ID in (
select distinct km.KDB_FK_ID
from KPI_MET_FIELD_DATA km , KPI_DET_BASE kp, KPI_REL_KPI_SCORECARD ksc, STR_DET_EMP_SCORECARD sc
where ksc.SDE_FK_ID=sc.SDE_PK_ID
and km.KDB_FK_ID = ksc.KDB_KPI_FK_ID
and km.is_deleted=0
and kp.kdb_pk_id = km.KDB_FK_ID
and kp.is_deleted=0
and km.cmf_fk_id in (145,146,147)
and sc.sdp_fk_id = p2)
and is_deleted=0 ;
There are several weaknesses in your code.
WHERE KDB_FK_ID in (select distinct ... does not make any sense. There is no need to make DISTINCT for an IN () clause.
Use ANSI join syntax instead of old Oracle join syntax, it is less error-prone
But the main difference is, your loop does not contain join condition (a.data like '%'||rec.COR_ID_OLD||'%'), i.e. you update entire table KPI_MET_FIELD_DATA again and again for each row in CUSTOM_TEMP_TABLE_SESSION where TYPE_LB in (14,15,16)

Iterate through cursor and storing the output of the cursor in another table

I am trying to iterate through a cursor which stores the value of the table. I use a FOR Loop to iterate and IF one of the conditions is met, I store the output in another table. I am not sure of the approach I am following and also getting error(ORA-00933: SQL command not ended properly). Stats_Queries is my reference table where I iterate my cursor through. STATS_RESULT_CARD is my output table where I have to store the results. Please help.
DECLARE
CURSOR c1 IS
select Stats_Queries.OBJECTTYPE, Stats_Queries.CATEGORY, Stats_Queries.QUERY
from Stats_Queries;
r1 c1%ROWTYPE;
BEGIN
FOR r1 IN c1 LOOP
If (r1.OBJECTTYPE = 'CARD') THEN
INSERT INTO STATS_RESULTS_CARD (NODETYPENAME, NODEDEFNAME , CARDTYPENAME, PROVISIONSTATUSNAME, STATDATE, CARDCOUNT)
select nt.name, nd.name, ct.name, ps.name, sysdate, count(c.cardid)
from cardtype ct, card c, node n, nodetype nt, nodedef nd, provisionstatus ps
where ct.name in ('SRA AMP', 'XLA AMP', 'SAM', 'ESAM')
and ct.cardtypeid = c.card2cardtype
and c.card2node = n.nodeid
and n.node2nodetype = nt.nodetypeid
and n.node2nodedef = nd.nodedefid
and c.card2provisionstatus = ps.provisionstatusid
group by nt.name, nd.name, ct.name, ps.name
END If;
END LOOP;
END;
As an aside from the answer that Finbarr has provided (which is perfectly correct; add in the missing semi-colon and your procedure should work), why do you need to loop through the cursor at all? That's the slow way of doing it.
You could just do a single insert statement instead, such as:
insert into stats_results_card (nodetypename,
nodedefname,
cardtypename,
provisionstatusname,
statdate,
cardcount)
select x.nt_name,
x.nd_name,
x.ct_name,
x.ps_name,
x.statdate,
x.cnt_cardid
from (select nt.name nt_name,
nd.name nd_name,
ct.name ct_name,
ps.name ps_name,
sysdate statdate,
count (c.cardid) cnt_cardid
from cardtype ct,
card c,
node n,
nodetype nt,
nodedef nd,
provisionstatus ps
where ct.name in ('SRA AMP',
'XLA AMP',
'SAM',
'ESAM')
and ct.cardtypeid = c.card2cardtype
and c.card2node = n.nodeid
and n.node2nodetype = nt.nodetypeid
and n.node2nodedef = nd.nodedefid
and c.card2provisionstatus = ps.provisionstatusid
group by nt.name,
nd.name,
ct.name,
ps.name) x
cross join (select stats_queries.objecttype,
stats_queries.category,
stats_queries.query
from stats_queries
where objecttype = 'CARD');
N.B. This assumes that there really isn't any link between the original cursor and the select statement that was inside the loop; we do a cross join to replicate the rows the required number of times.
If there was an actual join between the two queries, you would put that in place of the cross join.
ORA-00933: SQL command not ended properly
Probably occurring because you missed a semicolon after
group by nt.name, nd.name, ct.name, ps.name

An INTO clause is expected in this SELECT statement

any help for this please ?
I want to return all those who live in france, then compare them with those who live in BELguim if they have the same name or name_done show the result
declare
cursor c is select namer from poeple where city = 'France';
c_res c%rowtype;
begin
open c;
loop
fetch c into c_res;
exit when c%notfound;
select * from poeple tw where city = 'BELguim' AND (c_res.namer = tw.namer OR c_res.namer = tw.namer || ' _done' );
end loop;
close c;
end;
Why use a cursor? You can write this with a single query:
select pb.*
from poeple pf join
poeple pb
on pf.city = 'France' and pb.city = 'BELgium' and
(pf.namer = pb.namer or pf.namer = pb.namer || ' _done');
The issue with your code is that PL/SQL does not allow queries to return anything from code blocks. You can print out the results, insert values into variables, or put them in another table. But, you cannot just have the results from the query.
The issue is with :
select * from poeple tw where city='BELguim' AND (c_res.namer
=tw.namer OR c_res.namer =tw.namer || ' _done' );
You cannot write the SQL directly in PL/SQL. The PL/SQL engine expects an INTO clause.
Looking at your code, you don't need the cursor c. it could be written in a single SQL. Which brings it back to Gordon's query :
select pb.*
from poeple pf join
poeple pb
on pf.city = 'France' and pb.city = 'BELgium' and
(pf.namer = pb.namer or pf.namer = pb.namer || ' _done');
If you really need PL/SQL for some stuff which you have not explained, for example return the result set as REF CURSOR, then have an OUT parameter as REF CURSOR. But, you need to explain the requirement.
If you can do something in SQL, then don't use PL/SQL.
EDIT If your final goal is to update, then use the above query as USING clause in the MERGE statement.

Oracle cursor with variables help needed

I am trying to do a cursor which does something like below, struggling with different approaches with no results. Seems, I won't be able to do it by myself, and decided to ask you for help.
Below code shows what I want to achieve rather than ready approach. Please help.
I dont know it it matters but note, that I need to update CUSTOMERS in loop. I also need to select some data from another table referencing customer in this loop, then insert something to third table and update customer table.
DECLARE
CURSOR MY_CURSOR
IS
SELECT CUSTOMERID FROM CUSTOMERS WHERE ACTIVE = 1 ;
MY_RECORD MY_CURSOR%ROWTYPE;
BEGIN
FOR MY_RECORD IN MY_CURSOR
LOOP
DECLARE TEMPORARY_TABLE TABLE (A DATE, B NUMBER, C VARCHAR)
INSERT INTO #TEMPORARY_TABLE(A,B,C) (SELECT CREATEDDATE, ID, NAME FROM ACCOUNT WHERE CUSTOMER = MY_RECORD.CUSTOMERID)
INSERT INTO SOME_EVENT_TABLE(ID, NAME, DATE, ACCOUNT_ID) VALUE (some_seq.NEXTVAL, #TEMPORARY_TABLE[C], #TEMPORARY_TABLE[A], #TEMPORARY_TABLE[B])
UPDATE CUSTOMERS SET LAST_ACCOUNT_CHECK_NAME=#TEMPORARY_TABLE(C), LAST_INSERTED_EVENT_ID = some_seq.CURRVAL WHERE ID = MY_RECORD.CUSTOMERID
END LOOP;
COMMIT;
END;
First, you can't declare a temporary table in Oracle like you do in SQL Server. However, you really don't need it here anyway.
Something like this should work:
FOR MY_RECORD IN MY_CURSOR LOOP
FOR R IN (SELECT CREATEDDATE, ID, NAME
FROM ACCOUNT WHERE CUSTOMER = MY_RECORD.CUSTOMERID) LOOP
INSERT INTO some_event_table(ID, NAME, DATE, ACCOUNT_ID)
VALUES (some_seq.NEXTVAL, R.NAME, R.CREATEDATE, R.ID);
UPDATE customers
SET last_account_check_name = R.name
, last_inserted_event_id = some_seq.CURRVAL
WHERE id = MY_RECORD.CUSTOMER_ID;
END LOOP;
END LOOP;
COMMIT;
Row by row actions in SQL are terribly inefficient. You will get vastly better performance if you do this in a set-based way.
INSERT INTO some_event_table(ID, NAME, DATE, ACCOUNT_ID)
SELECT some_seq.NEXTVAL, a.name, a.createdate, a.id
FROM ACCOUNT a
INNER JOIN customers c ON c.customerid = a.customerid
WHERE c.active = 1;
UPDATE customers
SET last_account_check_name =
( SELECT a.name FROM account a WHERE a.customerid = c.customerid ),
last_inserted_event_id = some_seq.CURRVAL
WHERE c.active = 1;
There may be concurrency issues with that (what happens if customers is updated between the two statements?), but that might be good enough for your needs.

How to loop in sql?

I dont want to use the "loop" related keyword, how can I implement loop with basic sql command in oracle ?
I have two table :
A:
ID, Color
B,
ID, AID, Type
I want to loop all records in B, and if ID = AID, then set the A.Color = B.Type
Thanks in advance !
Looping is, by definition, a procedural construct.
SQL is declarative: tell the database what you want done, not how to do it.
If you're absolutely convinced that you need to program such a thing, then write it in PL/SQL, Oracle's procedural language.
Bu I'm sure that it's possible to do what you want in SQL using an UPDATE with a WHERE clause.
Something like this (corrected per NullUserException):
UPDATE A SET A.Color = (SELECT B.Type FROM B WHERE A.ID = B.AID)
An alternate method:
MERGE INTO a
USING b
ON (b.aid = a.id)
WHEN MATCHED THEN UPDATE SET a.color = b.type;
You could just do:
UPDATE tablea a
SET a.color = (SELECT b.type
FROM tableb b
WHERE b.aid = a.id)
See this SQL script.
To do that you will have to write a stored procedure using PL/SQL. Here is the oracle page with some info and papers on the topic.
As others pointed out, you can probably solve your problem with a normal DML statement, without any looping involved. But to give you some basics on how to accomplish what you asked for in PL/SQL, here's an example...
DECLARE
CURSOR c IS
SELECT id, aid, type FROM b;
statement VARCHAR2(200);
BEGIN
FOR iterator IN c LOOP
IF iterator.id = iterator.aid THEN
statement := 'UPDATE a SET color = ' || iterator.type || 'WHERE id = ' || iterator.id;
EXECUTE IMMEDIATE statement;
END IF;
END LOOP;
END;
This anonymous PL/SQL block will iterate through each record in table b, and if b.id = b.aid, it will update table a and set a.color = b.type where a.id = b.id.
This seems to be what you were asking for. It's not exactly an efficient way to go about doing things, since you're firing off one DML statement per row in table b that has b.id=b.aid. But I wanted more to give this as a syntax example. This is just one way to iterate through a cursor by the way; you can also explicitly open cursors and fetch records, but it's easier this way if you don't need to do anything but iterate over the entire result set.

Resources