Getting Sum Using PL/SQL In Optimized Way - oracle

I have the below PL/SQL block which would be converted to a function, this would be used very heavily, is there a better and efficient way to re-write in the most optimized way?
Appreciate any suggestions or insights.
DECLARE
ln_grand_total NUMBER;
ln_total NUMBER;
ln_final_total NUMBER;
CURSOR E1
IS
SELECT SUM (qty * price * rate)
INTO ln_total
FROM maximo.po_lines pl
JOIN maximo.po_headers ph ON pl.HEADER_ID = ph.HEADER_ID
WHERE ph.HEADER_ID = 0123;
BEGIN
OPEN E1;
FETCH E1 INTO ln_grand_total;
ln_final_total := ROUND (ln_grand_total, 2);
DBMS_OUTPUT.put_line (ROUND (ln_grand_total, 2));
CLOSE E1;
DBMS_OUTPUT.put_line ('ln_final_total --> ' || ln_final_total);
END;
/

You don't need a cursor for this:
DECLARE
ln_total NUMBER;
BEGIN
SELECT ROUND( SUM (qty * price * rate), 2 )
INTO ln_total
FROM maximo.po_lines pl
JOIN maximo.po_headers ph
ON pl.HEADER_ID = ph.HEADER_ID
WHERE ph.HEADER_ID = 0123;
DBMS_OUTPUT.put_line ('ln_final_total --> ' || ln_total);
END;
/

Indeed there's no problem with the logic, but change the style such as
if HEADER_ID is column of numeric type, then remove the preceding
zero of 0123, otherwise quote that as '0123'
INTO clause cannot be used for a cursor definition. Eg. get rid of
INTO ln_total. So remove that local variable
Convert SUM (qty * price * rate) within the query of the cursor to
ROUND(SUM (qty * price * rate),2). So, get rid of ln_final_total
local variable
Therefore, you can use the below code block
DECLARE
ln_grand_total NUMBER(10,2);
CURSOR E1 IS
SELECT ROUND( SUM(qty * price * rate), 2)
FROM maximo.po_lines pl
JOIN maximo.po_headers ph
ON pl.HEADER_ID = ph.HEADER_ID
WHERE ph.HEADER_ID = '0123';
BEGIN
OPEN E1;
FETCH E1
INTO ln_grand_total;
CLOSE E1;
DBMS_OUTPUT.PUT_LINE('ln_grand_total --> ' || ln_grand_total);
END;
/

Related

Pass Query in Clob to Cursor (PL/SQL)

I have a function that returns A clob which is actually a query. I want to call that function in another function where I want to create a cursor that will have the results of that query as it's data.
So I have this function
performance_9chartsDay(p_display_resolution,p_technology_group)
And If I call it
SELECT performance_9chartsDay(1,'OTHER') FROM DUAL;
I get the following query (clob)
'Select "Category" as "Category", less_than_5m as "<5 min", less_than_15m as "<15 min", less_than_60m as "<60 min", more_than_60m as ">60 min" ,95 as KPI from ( Select "Category",ROUND((less_than_5m / (less_than_5m+less_than_15m+less_than_60m+more_than_60m))*100,2) AS less_than_5m,
ROUND((less_than_15m / (less_than_5m+less_than_15m+less_than_60m+more_than_60m))*100,2) AS less_than_15m,
ROUND((less_than_60m / (less_than_5m+less_than_15m+less_than_60m+more_than_60m))*100,2) AS less_than_60m,
ROUND((more_than_60m / (less_than_5m+less_than_15m+less_than_60m+more_than_60m))*100,2) AS more_than_60m from (WITH statistics AS
(SELECT /*+ materialize */
*
FROM
(SELECT trim(TO_CHAR(TRUNC(last_upd), 'Month')) || ' ' || trim(TO_CHAR(TRUNC(last_upd), 'YYYY')) AS month ,
'Week ' || TO_CHAR(TRUNC(last_upd), 'WW') || ' (' || trim(TO_CHAR(TRUNC(last_upd), 'YYYY')) || ')' AS week ,
TRUNC(last_upd) AS datum,
CASE
WHEN (completed_sec/60) <= 5
THEN '<5M'
WHEN (completed_sec/60) > 5
AND (completed_sec /60) <= 15
THEN '<15M'
WHEN (completed_sec/60) > 15
AND (completed_sec /60) <= 60
THEN '<60M'
WHEN (completed_sec/60) > 60
THEN '>60M'
END AS completed_in_less_than
FROM ORD_LOAD
WHERE 1=1 AND trunc(last_upd) >= to_date('29.01.2023', 'dd.mm.yyyy') and technology in ('Mobile', 'Homebox') ) pivot (COUNT(completed_in_less_than) FOR completed_in_less_than
IN ('<5M' less_than_5m,'<15M' less_than_15m ,'<60M' less_than_60m,'>60M' more_than_60m)))
SELECT to_char(date,'dd.mm.yyyy') as "Category",SUM (less_than_5m) AS less_than_5m,
SUM (less_than_15m) AS less_than_15m,
SUM (less_than_60m) AS less_than_60m,
SUM (more_than_60m) AS more_than_60m
from statistics
group by to_char(date,'dd.mm.yyyy'))) order by to_date("Category",'dd.mm.yyyy')'
Now I have this function
FUNCTION fixMobOtherDayWeekMonth_pipe(
p_display_resolution VARCHAR2,
p_technology_group VARCHAR2
)return fixMobOtherDayWeekMonth_table PIPELINED
AS
output fixMobOtherDayWeekMonth;
v1 VARCHAR2 (200);
v2 NUMBER;
v3 NUMBER;
v4 NUMBER;
v5 NUMBER;
v6 NUMBER;
CURSOR cursor_k IS --SELECT THAT QUERY FROM THAT FUNCTION
....
```
How do I get the data from the query from that function into this cursor?
Look into REF CURSORs, that's what they are for. Modify performance_9chartsDay so that instead of returning a string, it opens a ref cursor with that string and returns the ref cursor.
CREATE OR REPLACE FUNCTION performance_9chartsDay (...)
RETURN sys_refcursor
AS
cur sys_refcursor;
BEGIN
OPEN cur for 'SQL string here';
RETURN cur;
END;
Then pass the result of that function into fixMobOtherDayWeekMonth_pipe through an IN variable of type sys_refcursor, and fetch from it.
CREATE OR REPLACE FUNCTION fixMobOtherDayWeekMonth_pipe(in_cursor IN sys_refcursor,....)
RETURN fixMobOtherDayWeekMonth_table
AS
output fixMobOtherDayWeekMonth;
BEGIN
FETCH in_cursor INTO output;
WHILE in_cursor%FOUND
LOOP
FETCH in_cursor INTO output;
PIPE ROW....
END LOOP;
END;

06533. 00000 - "Subscript beyond count"

I have this plsql block that populates a table with information of two other tables and I have to use a variable array:
DECLARE nombre_grupo VARCHAR2(15);
direccion_tipo direccion;
persona_tipo persona;
personas_array personas := personas();
CURSOR departamento IS
SELECT * FROM departamentos;
CURSOR empleado IS
SELECT * FROM empleados, departamentos
WHERE empleados.dept_no = departamentos.dept_no;
i INTEGER;
BEGIN
FOR departamento IN (SELECT * FROM departamentos) LOOP
nombre_grupo := departamento.dnombre;
i := 1;
personas_array := personas();
FOR empleado IN (SELECT * FROM empleados WHERE dept_no = departamento.dept_no) LOOP
direccion_tipo := DIRECCION(departamento.loc, 'NULL', empleado.dir);
personas_array(i) := PERSONA(empleado.emp_no, empleado.apellido,
direccion_tipo, empleado.fecha_alt);
i := i + 1;
END LOOP;
INSERT INTO grupos VALUES (nombre_grupo, personas_array);
END LOOP;
END;
Here's the type personas:
CREATE OR REPLACE TYPE personas AS VARRAY(5) OF PERSONA
So when I execute that block and it reaches the personas_array(i) bit, it exits the execution with "subscript beyond count" error, no matter what value of i. What am I missing?
I've already deleted and created the type personas again, I've also tried creating the type inside the procedure, but it can't insert into the table
A few tips for a SQL beginner:
Don't learn 30 years old Oracle join syntax. Use modern ANSI join syntax, i.e.
SELECT *
FROM empleados
JOIN departamentos ON empleados.dept_no = departamentos.dept_no;
Your cursors are redundant. Either use
DECLARE
CURSOR cur_departamento IS
SELECT *
FROM departamentos;
BEGIN
FOR departamento IN cur_departamento LOOP
...
END LOOP;
END;
or
DECLARE
BEGIN
FOR departamento IN (SELECT * FROM departamentos) LOOP
...
END LOOP;
END;
You can also use this:
DECLARE
CURSOR cur_empleados(d IN EMPLEADOS.DEPT_NO%TYPE) IS
SELECT *
FROM EMPLEADOS
WHERE dept_no = d;
/*
-- Do not use this!
CURSOR cur_empleados(dept_no IN EMPLEADOS.DEPT_NO%TYPE) IS
SELECT *
FROM EMPLEADOS
WHERE EMPLEADOS.dept_no = dept_no; -> will return all rows
*/
BEGIN
FOR departamento IN (SELECT * FROM departamentos) LOOP
FOR empleado IN cur_empleados(departamento.dept_no) LOOP
...
END LOOP;
END LOOP;
END;
According to my feelings, VARRAYs are often part of student material but hardly used in real life.
Using string 'NULL' is most likely not want you want. Use literal NULL, i.e.
DIRECCION(departamento.loc, NULL, empleado.dir)
Type VARRAY(5) OF PERSONA defines a varray with maximum size of 5 elements. When you initialize it with personas_array := personas(); then the actual size is 0. You need to extend the varray.
You code may look like this:
DECLARE
nombre_grupo VARCHAR2(15);
direccion_tipo direccion;
persona_tipo persona;
personas_array personas;
i INTEGER;
BEGIN
FOR departamento IN (SELECT * FROM departamentos) LOOP
nombre_grupo := departamento.dnombre;
i := 1;
personas_array := personas();
FOR empleado IN (SELECT * FROM empleados WHERE dept_no = departamento.dept_no AND ROWNUM <= 5) LOOP
direccion_tipo := DIRECCION(departamento.loc, NULL, empleado.dir);
personas_array.extend();
personas_array(i) := PERSONA(empleado.emp_no, empleado.apellido, direccion_tipo, empleado.fecha_alt);
i := i + 1;
END LOOP;
INSERT INTO grupos VALUES (nombre_grupo, personas_array);
END LOOP;
END;
Just a note, such procedure would have rather low performance. The professional way of doing it would be a Nested Table and then insert the data with a single command:
CREATE OR REPLACE TYPE personas_NT AS TABLE OF PERSONA;
INSERT INTO grupos VALUES (nombre_grupo, personas_array)
SELECT dnombre,
CAST(MULTISET(
SELECT
emp_no,
apellido,
DIRECCION(dept.loc, NULL, dir),
fecha_alt
FROM EMPLEADOS
WHERE dept_no = dept.dept_no
) AS personas_NT) AS personas_array
FROM DEPARTAMENTOS dept;
But maybe, that would be a chapter in the "advanced" SQL course.

Oracle PL/SQL Stored Procedure Cursor for update

I am trying to update the 'damage_amt' of cars by increasing the value by 'inc', but only for accidents that occurred in year 'year'.
CREATE or REPLACE PROCEDURE updateDMG(xyear VARCHAR2, inc NUMBER) AS
CURSOR cur1 IS
select a.DAMAGE_AMT
from participated a join accident b
on a.report_nr = b.report_nr
for update;
p_dmg PARTICPATED.damage_amt%TYPE;
p_year NUMBER;
inc_dmg NUMBER;
BEGIN
p_year:=xyear;
inc_dmg:=inc;
OPEN cur1;
LOOP
FETCH cur1 bulk collect INTO p_dmg;
EXIT WHEN cur1%NOTFOUND;
UPDATE PARTICIPATED
SET damage_amt = damage_amt * inc_dmg
WHERE p_dmg like xyear;
END LOOP;
CLOSE cur1;
END updateDMG;
/
EXEC updateDMG('08', 0.10);
But I'm getting the error:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following: begin function pragma procedure subtype <an identifier> <a double-quoted delimited-identifier> current cursor delete exists prior
Obviously I am lacking knowledge on the syntax of this type of answer. Can anyone point out my error? I can't seem to find anything from searching.
EDIT: Was missing a / after END. Now it doesn't actually update the rows.
FINAL EDIT: Figured it out. I think some of my variables are unnecessary, but I could be wrong.
CREATE or REPLACE PROCEDURE updateDMG(xyear NUMBER, inc DECIMAL) AS
CURSOR cur1 IS
select a.DAMAGE_AMT, extract(year from b.ACCIDENT_DATE)
from participated a join accident b
on a.report_nr = b.report_nr
for update;
p_dmg PARTICIPATED.damage_amt%TYPE;
p_year NUMBER;
input_year NUMBER;
inc_dmg DECIMAL;
BEGIN
input_year:=xyear;
inc_dmg:=inc;
OPEN cur1;
FETCH cur1 INTO p_dmg, p_year;
MERGE INTO PARTICIPATED x
USING ACCIDENT y
ON (x.report_nr = y.report_nr)
WHEN MATCHED THEN
UPDATE SET x.damage_amt = x.damage_amt * (1 + inc_dmg/100)
WHERE extract(year from y.accident_date) = input_year;
CLOSE cur1;
END updateDMG;
/
EXEC updateDMG(2008, 10);
I don't have a database right now to fully check syntax, but I would suggest something like this:
CREATE OR REPLACE PROCEDURE updatedmg (p_year NUMBER, p_inc DECIMAL)
AS
l_next_year NUMBER;
l_from_date DATE;
l_to_date DATE;
BEGIN
l_next_year := p_year + 1;
l_from_date := TO_DATE (p_year || '-01-01', 'RRRR-MM-DD');
l_to_date := TO_DATE (l_next_year || '-01-01', 'RRRR-MM-DD');
UPDATE participated pt
SET pt.damage_amt = pt.damage_amt * (1 + p_inc / 100)
WHERE pd.report_nr IN (SELECT ad.report_nr
FROM accident ad
WHERE ad.accident_date >= l_from_date
AND ad.accident_date < l_to_date);
END updatedmg;
Its simple and that way you probably can make better use of indexes on tables.
I hope it helps.
Good luck!

The procedure below(when i uncomment the commented parts) shows 'no data found'..?

Hi I'm trying the code the below logic..i need help..
When i run the following procedure i get all the sids along with the corresponding pid for a particular month. but when i uncomment the parts that i have commented here i get the month and year displayed and then a message saying 'no data found'. Where am i going wrong?
create or replace PROCEDURE mas(V_MONTH NUMBER DEFAULT NULL,V_YEAR NUMBER DEFAULT NULL,V_AID VARCHAR2) AS
V_MID VARCHAR2(50);
V_SID VARCHAR2(50);
v_v_month number := nvl(V_MONTH,to_number(to_char(sysdate,'mm')));
v_v_year number := nvl(V_YEAR,to_number(to_char(sysdate,'yyyy')));
v_is_sub PM.IS_SUB%TYPE;
V_DIST_s NUMBER;
V_DIST_t NUMBER;
cursor c1
is
select distinct a.mid,
b.sid
from pmt a
inner join smt b
on (a.mid = b.mid)
where a.AID = V_AID
AND A.AID = B.AID
AND EXTRACT(MONTH FROM A.RDATE)= v_v_month AND
EXTRACT(YEAR FROM A.RDATE)= v_v_year
order by mid;
BEGIN
dbms_output.put_line('month : ' || v_v_month);
dbms_output.put_line('year : ' || v_v_year);
/*
select IS_SUB into v_is_sub from program_master where pid = 'V_AID';
IF v_is_sub = 1 then
select count(*) into V_DIST_S from (select distinct sid from smt where aid = 'v_aid');
select count(*) into V_DIST_T from (select distinct sid from tm where aid = 'v_aid');
if(V_DIST_S = V_DIST_T) then
*/
for rec1 in c1
loop
dbms_output.put_line('MID : ' || rec1.MID);
dbms_output.put_line('SID : ' || rec1.SID);
end loop;
-- else
-- dbms_output.put_line('count of sids do not match');
--end if;
--else
--dbms_output.put_line('No sids available for the mentioned pid.');
--end if;
END MAS;
where pid = 'V_AID';
V_AID is a variable, however, you have enclosed it within single-quotation marks, which makes it a string. So, you are actually looking for the value 'V_AID' and not the value of the variable.
Modify it to:
select IS_SUB into v_is_sub from program_master where pid = V_AID;
And do the same wherever you have enclosed the variable in single-quotation marks.

bind array in cursor using plsql

declare
cursor lc is
select *
from (select a.lin, a.pr,
b.name, sum(a.up) as u,
sum (a.d) as d
from li_dy_4 a,
p_list b
where a.pr=b.id
and b.parent_id != 0
and a.partitionid <= 308
and a.partitionid >= 302
and a.pr in (91,80)
GROUP BY a.pr, b.name, a.lin
order by d desc) ;
rec lc%ROWTYPE;
BEGIN
open lc;
loop
FETCH lc into rec;
dbms_output.put_line(rec.pr);
exit when lc%NOTFOUND;
end loop;
close lc;
END;
the above statement works fine for me. What I am not capable of finding anything hopeful is changing the value after the "in" statement which is a.pr in (91,80)
I have listed the values here manually, but I want to pass it to the cursor as an array of numbers for a.pr column. In short I want to do a.pr = idlist wher idlist is an array. Please anyone tell me if my idea is possible.
Just want to remind you, the IN clause supports 1000 items only. And that could be the primary reason ,there's nothing called BULK BINDING for SELECT Queries. We have FORALL INSERT/UPDATE, which is like BULK BINDING. Unfortunately select has none.
But still you can achieve your requirement in a different fashion.
You can try a global temporary table(GTT) which a temporary table with "scope of the data inserted" is only to that session.
You can FORALL INSERT all your data for IN clause into that table, and join the TABLE to your Query.
Else you can have a nested table (if oracle 10g) or a simple pl/sql type itself (if oracle 11g), with all your IN class items as records and join it to your Query.
Example: Using NESTED TABLE , effective for less number(<10000) of items
CREATE TYPE pr AS OBJECT
(pr NUMBER);
/
CREATE TYPE prList AS TABLE OF pr;
/
declare
myPrList prList := prList ();
cursor lc is
select *
from (select a.lin, a.pr,
b.name, sum(a.up) as u,
sum (a.d) as d
from li_dy_4 a,
p_list b,
TABLE(CAST(myPrList as prList)) my_list
where a.pr=b.id
and b.parent_id != 0
and a.partitionid <= 308
and a.partitionid >= 302
and a.pr = my_list.pr
GROUP BY a.pr, b.name, a.lin
order by d desc) ;
rec lc%ROWTYPE;
BEGIN
/*Populate the Nested Table, with whatever collection you have */
myPrList := prList ( pr(91),
pr(80));
/*
Sample code: for populating from your TABLE OF NUMBER type
FOR I IN 1..your_input_array.COUNT
LOOP
myPrList.EXTEND;
myPrList(I) := pr(your_input_array(I));
END LOOP;
*/
open lc;
loop
FETCH lc into rec;
exit when lc%NOTFOUND; -- Your Exit WHEN condition should be checked afte FETCH iyself!
dbms_output.put_line(rec.pr);
end loop;
close lc;
END;
/
I don't know the exact structure of your global table but you can use the collection in cursor like this
declare
cursor c1 is
select last_name ls from empc;
type x is table of employees.last_name%type;
x1 x := x();
cnt integer :=0;
begin
for z in c1 loop
cnt := cnt +1;
x1.extend;
x1(cnt) := z.ls;
if x1(cnt) is NULL then-----------
DBMS_OUTPUT.PUT_LINE('ASHISH');
end if;
dbms_output.put_line(cnt || ' '|| x1(cnt));
end loop;
end;

Resources