Creating a Cursor and Procedure - oracle

I created a Cursor for a procedure. I am trying to apply a flag to records in that cursor.
Create or Replace Procedure Pledges
(IDdonor In Int)
is
Cursor Cur_Pledges is
Select dd_pledge.iddonor, dd_status.idstatus from dd_donor
join dd_pledge on dd_donor.iddonor=dd_pledge.iddonor
join dd_status on dd_pledge.idstatus=dd_status.idstatus;
Type All_Pledges2 is record(iddonor dd_pledge.iddonor%type, idstatus dd_status.idstatus%type, flag Varchar2(10));
Begin
For Rec_Pledges in Cur_Pledges LOOP
if rec_pledges.idstatus = '10' THEN Flag := 'True';
elsif rec_pledges.idstatus= '20' THEN Flag := 'False';
End if;
Insert Into All_Pledges
Values(rec_pledges.idddonor, rec_pledges.idstatus, flag);
End Loop;
End;

You are wrongly using the type record variable, I have made the changes please check below, this will work:
Create or Replace Procedure Pledges
(IDdonor In Int)
is
Cursor Cur_Pledges is
Select dd_pledge.iddonor, dd_status.idstatus from dd_donor
join dd_pledge on dd_donor.iddonor=dd_pledge.iddonor
join dd_status on dd_pledge.idstatus=dd_status.idstatus;
Type All_Pledges2 is record(iddonor dd_pledge.iddonor%type, idstatus dd_status.idstatus%type, flag Varchar2(10));
-- new change below
allpledges2 All_Pledges2;
Begin
For Rec_Pledges in Cur_Pledges LOOP
if rec_pledges.idstatus = '10' THEN
allpledges2.Flag := 'True';
elsif rec_pledges.idstatus= '20' THEN
allpledges2.Flag := 'False';
End if;
Insert Into All_Pledges
Values(rec_pledges.iddonor, rec_pledges.idstatus, allpledges2.flag);
End Loop;
End;

While you can do this with a cursor loop, you shouldn't. Generally, PL/SQL performs best if you minimize the number of context changes and let the SQL optimizer do it's job. This procedure should consist of a single insert statement:
CREATE OR REPLACE PROCEDURE pledges (iddonor IN INT) IS
BEGIN
INSERT INTO all_pledges
SELECT dd_pledge.iddonor,
dd_status.idstatus,
CASE dd_status.idstatus
WHEN '10' THEN 'True'
WHEN '20' THEN 'False'
END
FROM dd_donor
JOIN dd_pledge ON dd_donor.iddonor = dd_pledge.iddonor
JOIN dd_status ON dd_pledge.idstatus = dd_status.idstatus;
END pledges;
It probably worth noting that, as written, the iddonor parameter is superfluous: since you aren't referencing it in the code, it serves no purpose.
If the goal is to simply return the results to another program, you don't want an insert at all (using a table to return a result set is not a good pattern in Oracle). The best way to handle that is typically to open a ref cursor and return that:
CREATE OR REPLACE PROCEDURE pledges (iddonor IN INT,
all_pledges OUT SYS_REFCURSOR) IS
BEGIN
OPEN all_pledges FOR
SELECT dd_pledge.iddonor,
dd_status.idstatus,
CASE dd_status.idstatus
WHEN '10' THEN 'True'
WHEN '20' THEN 'False'
END
FROM dd_donor
JOIN dd_pledge ON dd_donor.iddonor = dd_pledge.iddonor
JOIN dd_status ON dd_pledge.idstatus = dd_status.idstatus
WHERE dd_donor.iddonor = pledges.iddonor;
END pledges;
Alternately, you could return a user-defined type or write the results to a global temporary table.

Related

Getting error PLS-003036 wrong number or types of argument in call to =

I had written the below trigger
create or replace trigger my_trigger
Before insert or update on table1
referencing new as new old as old
for each row
declare id number;
cursor id_cnt is
select count(*) from table2 where my_id=:new.my_id;
begin
if :new.my_id is null
then RAISE_APPLICATION_ERROR(-001,"MY_ID should nit be null");
elsif id_cnt=0 then
RAISE_APPLICATION_ERROR(-002,"not a valid id ");
else
select new_id from table2 where my_id=:new.my_id;
if lenght(new_id) <5
then
RAISE_APPLICATION_ERROR(-003,"length is very small ");
END IF;
END IF;
END my_trigger;
At if :new.my_id is null i am getting the below error
error PLS-003036 wrong number or types of argument in call to =
There are 2 conditions needs to be checked first condition i need to check my_id is null or not and second condition need to check the length of new_id before that i am checking if that my_id is already existed in table 2 before inserting into table1
You:
want to SELECT ... INTO rather than using a CURSOR
misspelt LENGTH
need to use ' for string literals and not "; and
need to use -20000 to -20999 for user-defined error numbers.
Like this:
create or replace trigger my_trigger
Before insert or update on table1
referencing new as new old as old
for each row
declare
id number;
id_cnt PLS_INTEGER;
v_new_id table2.new_id%TYPE;
begin
IF :new.my_id is null THEN
RAISE_APPLICATION_ERROR(-20001,'MY_ID should nit be null');
END IF;
select count(*)
INTO id_cnt
from table2
where my_id=:new.my_id;
if id_cnt=0 then
RAISE_APPLICATION_ERROR(-20002,'not a valid id');
else
select new_id
INTO v_new_id
from table2
where my_id=:new.my_id;
if length(v_new_id) < 5 then
RAISE_APPLICATION_ERROR(-20003,'length is very small');
END IF;
END IF;
END my_trigger;
/
db<>fiddle here

How to do conditional processing in a bulk collect loop?

we have Oracle 11G and i'm trying to move data from one table to another using bulk collect. Problem is when I tried to evaluate if one field from origin is empty my package got invalidated. What I have:
Declaration:
CREATE OR REPLACE PACKAGE MYSCHEMA.MYPKG AS
CURSOR CUR_MYDATA IS
SELECT
o.name,
o.last_name,
o.id,
o.socnum
FROM
origin o
WHERE
1=1
AND o.name like upper ('a%');
TYPE t_name IS TABLE OF origin.name%TYPE;
TYPE t_lastname IS TABLE OF origin.last_name%TYPE;
TYPE t_id IS TABLE OF origin.id%TYPE;
TYPE t_socnum IS TABLE OF origin.socnum%TYPE;
l_name t_name;
l_lastname t_lastname;
l_id t_id;
l_socnum t_socnum;
PROCEDURE MYPROCEDURE;
END MYPKG;
Body:
CREATE OR REPLACE PACKAGE BODY MYSCHEMA.MYPKG AS
PROCEDURE MYPROCEDURE IS
BEGIN
OPEN CUR_MYDATA;
LOOP
FETCH CUR_MYDATA BULK COLLECT INTO l_name,l_lastname,l_id,l_socnum;
forall i IN 1 .. l_name.COUNT
IF ( l_socnum(i) IS NULL)
THEN (select oo.socnum from other_origin where oo.id=l_id(i))
END IF;
INSERT INTO destiny (
d_name,
d_lastname,
d_id,
d_socnum)
VALUES (
l_name(i),
l_lastname(i),
l_id(i),
l_socnum(i),
EXIT WHEN l_name.count = 0;
END LOOP;
END MYPROCEDURE;
END MYPKG;
but when I check body status it is INVALID
any thoughs?
FORALL is not a loop construct: it cannot be split from its DML statement.
when I tried to evaluate if one field from origin is empty
You need to loop round the populated collection and fix that before executing the FORALL ... INSERT.
CREATE OR REPLACE PACKAGE BODY MYSCHEMA.MYPKG AS
PROCEDURE MYPROCEDURE IS
BEGIN
OPEN CUR_MYDATA;
LOOP
FETCH CUR_MYDATA BULK COLLECT INTO l_name,l_lastname,l_id,l_socnum;
EXIT WHEN l_name.count = 0;
for idx in 1 .. l_socnum.count() loop
IF l_socnum(idx) IS NULL THEN
select oo.socnum
into l_socnum(idx)
from other_origin
where oo.id = l_id(idx);
END IF;
end loop;
forall i IN 1 .. l_name.COUNT
INSERT INTO destiny (
d_name,
d_lastname,
d_id,
d_socnum)
VALUES (
l_name(i),
l_lastname(i),
l_id(i),
l_socnum(i));
END LOOP;
END MYPROCEDURE;
END MYPKG;
Other notes.
Check whether the fetch returns any records immediately after executing the fetch. Otherwise your code will attempt to execute code over an empty collection, which will fail.
You should define a collection based on the target table %rowtype: this is simpler than defining and handling multiple collections based on columns.
Also, your real code may be way more complicated than what you posted here, but if you have a large amount of data to shift there is a lot of performance gain in using pure SQL rather than a procedure:
INSERT INTO DESTINY (
D_NAME,
D_LASTNAME,
D_ID,
D_SOCNUM
)
SELECT
o.name,
o.last_name,
o.id,
coalesce(o.socnum, oo.socnum)
FROM
origin o
left outer join other_origin oo
on oo.id = o.id
WHERE
1=1
AND o.name like upper ('a%');
IF condition is not allowed inside FOR ALL.
FOR ALL can execute a single DML: INSERT, UPDATE, or DELETE statement which is written following it. It is not normal for loop.
You can try the following code:
Package:
CREATE OR REPLACE PACKAGE MYSCHEMA.MYPKG AS
CURSOR CUR_MYDATA IS
SELECT
O.NAME,
O.LAST_NAME,
O.ID,
-- ADDED THIS CASE STATEMENT
CASE
WHEN O.SOCNUM IS NOT NULL THEN O.SOCNUM
ELSE OO.SOCNUM
END AS SOCNUM
FROM
-- ADDED THIS LEF JOIN
ORIGIN O
LEFT JOIN OTHER_ORIGIN OO ON ( OO.ID = O.ID )
WHERE
1 = 1
AND O.NAME LIKE UPPER('a%');
TYPE T_NAME IS
TABLE OF ORIGIN.NAME%TYPE;
TYPE T_LASTNAME IS
TABLE OF ORIGIN.LAST_NAME%TYPE;
TYPE T_ID IS
TABLE OF ORIGIN.ID%TYPE;
TYPE T_SOCNUM IS
TABLE OF ORIGIN.SOCNUM%TYPE;
L_NAME T_NAME;
L_LASTNAME T_LASTNAME;
L_ID T_ID;
L_SOCNUM T_SOCNUM;
PROCEDURE MYPROCEDURE;
END MYPKG;
Package Body
CREATE OR REPLACE PACKAGE BODY MYSCHEMA.MYPKG AS
PROCEDURE MYPROCEDURE IS
BEGIN
OPEN CUR_MYDATA;
FETCH CUR_MYDATA BULK COLLECT INTO
L_NAME,
L_LASTNAME,
L_ID,
L_SOCNUM
LIMIT 1000;
FORALL I IN 1..L_NAME.COUNT
--
-- REMOVED THIS CONDITION
--
-- IF ( l_socnum(i) IS NULL)
-- THEN (select oo.socnum from other_origin where oo.id=l_id(i))
-- END IF;
INSERT INTO DESTINY (
D_NAME,
D_LASTNAME,
D_ID,
D_SOCNUM
) VALUES (
L_NAME(I),
L_LASTNAME(I),
L_ID(I),
L_SOCNUM(I)
);
CLOSE CUR_MYDATA;
END MYPROCEDURE;
END MYPKG;

Oracle - PLS-00222

I have the following piece of code which is returning PLS-00222. So I want to compare the dates of an "old" id with a "new" id retrieved from cur_c1.
Here is the cursor where cur_table records come from:
CURSOR table_cur IS
SELECT
NEW_ID,
OLD_ID
FROM
TABLE_C
WHERE
C_ID = in_parameter_id; --This is input for the procedure
CURSOR cur_c1 (c_in_id NUMBER) IS
SELECT
FIELD_DATE
FROM
TABLE_D
WHERE
FIELD_ID = c_in_id;
FOR cur_table IN table_cur LOOP
...stuff...;
FOR c_cur IN cur_c1(cur_table.NEW_ID) LOOP
IF c_cur.field_date > cur_c1(cur_table.OLD_ID).field_date
THEN
v_exist := 'Y';
END IF;
END LOOP;
END LOOP;
How can I achieve my desired result?
Error:
2593/56 PLS-00222: no function with name 'cur_c1' exists in this scope
2593/17 PL/SQL: Statement ignored
I suggest that using cursors here is inefficient. Instead I suggest the following:
FOR cur_table IN table_cur LOOP
...stuff...;
SELECT d_old.FIELD_DATE,
d_new.FIELD_DATE
INTO dtOld_field_date,
dtNew_field_date
FROM DUAL
LEFT OUTER JOIN TABLE_D d_old
ON d_old.FIELD_ID = cur_table.OLD_ID
LEFT OUTER JOIN TABLE_D d_new
ON d_new.FIELD_ID = cur_table.NEW_ID;
IF dtNew_field_date > dtOld_field_date THEN
v_exist := 'Y';
END IF;
END LOOP;
Best of luck.

Are there any impact of update statement on for loop statement in oracle?

I have nested for loop which iterates same table. In inner loop I update a column in same table. But in for loop condition I check that updated column and I need to check this column not in the beginning but dynamically, so my for loop iterations will maybe greatly decrease.
Am I doing this correct or is for statement will not see updated column?
declare
control number(1);
dup number(10);
res varchar2(5);--TRUE or FALSE
BEGIN
dup :=0;
control :=0;
FOR aRow IN (SELECT MI_PRINX, geoloc,durum, ROWID FROM ORAHAN where durum=0)
LOOP
FOR bRow IN (SELECT MI_PRINX, geoloc, ROWID FROM ORAHAN WHERE ROWID>aRow.ROWID AND durum=0)
LOOP
BEGIN
--dbms_output.put_line('aRow' || aRow.Mi_Prinx || ' bRow' || bRow.Mi_Prinx);
select SDO_GEOM.RELATE(aRow.geoloc,'anyinteract', bRow.Geoloc,0.02) into res from dual;
if (res='TRUE')
THEN
Insert INTO ORAHANCROSSES values (aRow.MI_PRINX,bRow.MI_PRINX);
UPDATE ORAHAN SET DURUM=1 where rowid=bRow.Rowid;
control :=1;
--dbms_output.put_line(' added');
END IF;
EXCEPTION
WHEN DUP_VAL_ON_INDEX
THEN
dup := dup+1;
--dbms_output.put_line('duplicate');
--continue;
END;
END LOOP;
IF(control =1)
THEN
UPDATE ORAHAN SET DURUM=1 WHERE rowid=aRow.Rowid;
END IF;
control :=0;
END LOOP;
dbms_output.put_line('duplicate: '||dup);
END ;
Note: I use oracle 11g and pl/sql developer
Sorry my english.
Yes, the FOR statement will not see the updated DURUM column because the FOR statement will see all data as they were when the query was started! This is called read consistency and Oracle accomplishes this by using the generated UNDO data. That means it'll have more and more work to do (==run slower) as your FOR loop advances and the base table is updated!
It also means that your implementation will eventually run into a ORA-01555: snapshot too old error when the UNDO tablespace is exhausted.
You'll be probably better off using a SQL MERGE statement which should also run much faster.
e.g.:
Merge Into ORAHANCROSSES C
Using (Select aROW.MI_PRINX aROW_MI_PRIX,
aROW.GEOLOC aROW_GEOLOC,
bROW.MI_PRINX bROW_MI_PRIX,
bROW.GEOLOC bROW_GEOLOC,
SDO_GEOM.RELATE(aRow.geoloc,'anyinteract', bRow.Geoloc,0.02) RES
From ORAHAN aROW,
ORAHAN bROW
Where aROW.ROWID < bROW.ROWID
) Q
On (C.MI_PRIX1 = Q.aROW_MI_PRIX
and C.MI_PRIX2 = Q.bROW_MI_PRIX)
When Matched Then
Delete Where Q.RES = 'FALSE'
When Not Matched Then
Insert Values (Q.aROW_MI_PRIX, Q.bROW_MI_PRIX)
Where Q.RES = 'TRUE'
;
I'm not sure what you're trying to accomplish by ROWID>aRow.ROWID though
To use a certain order (in this case MI_PRINX) use the following technique:
Merge Into ORAHANCROSSES C
Using (With D as (select T.*, ROWNUM RN from (select MI_PRINX, GEOLOC from ORAHAN order by MI_PRINX) T)
Select aROW.MI_PRINX aROW_MI_PRIX,
aROW.GEOLOC aROW_GEOLOC,
bROW.MI_PRINX bROW_MI_PRIX,
bROW.GEOLOC bROW_GEOLOC,
SDO_GEOM.RELATE(aRow.geoloc,'anyinteract', bRow.Geoloc,0.02) RES
From D aROW,
D bROW
Where aROW.RN < bROW.RN
) Q
On (C.MI_PRIX1 = Q.aROW_MI_PRIX
and C.MI_PRIX2 = Q.bROW_MI_PRIX)
When Matched Then
Delete Where Q.RES = 'FALSE'
When Not Matched Then
Insert Values (Q.aROW_MI_PRIX, Q.bROW_MI_PRIX)
Where Q.RES = 'TRUE'
;
In case the query is taking too long, you might select * from v$session_longops where seconds_remaining >0 to find out when it'll be finished.

Why is my cursor only returning the top row?

Hello I'm a bit baffled that the cursor I have in this function is only returning the top row.
I've been comparing it to a few different examples I've seen and I just don't see what's wrong. Any guidance is greatly appreciated.
FUNCTION FS_A_FUNCTION
(
inDate DATE
)
RETURN VARCHAR2 IS
tAnswer VARCHAR2(1) := 'N';
tDates DATE;
CURSOR c1 IS
SELECT S.Dates FROM A_TABLE S;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO tDates;
EXIT WHEN c1%NOTFOUND;
END LOOP;
CLOSE c1;
IF inDate IN (tDates) THEN
tAnswer := 'Y';
END IF;
RETURN (tAnswer);
END FS_A_FUNCTION
Thanks in advance.
Not quite sure what you're expecting to happen. After your loop tDates will have a single value from whichever row the cursor saw last. As your select doesn't have an order by, that could be any value from your table. I think you may mean to be checking the value against inDate inside the loop.
I'm not sure why you're using a cursor at all, unless the real logic is more complicated. If you're doing what I think, you could just do something like:
FUNCTION FS_A_FUNCTION
(
inDate DATE
)
RETURN VARCHAR2 IS
tAnswer VARCHAR2(1);
BEGIN
select decode(max(dates), null, 'N', 'Y')
into tAnswer
from a_table
where dates = inDate;
RETURN tAnswer;
END FS_A_FUNCTION;
... or maybe a bit clearer bu the same logic:
FUNCTION FS_A_FUNCTION
(
inDate DATE
)
RETURN VARCHAR2 IS
tDates DATE;
BEGIN
select max(dates)
into tDates
from a_table
where dates = inDate;
IF tDates IS NULL THEN
RETURN 'N';
ELSE
RETURN 'Y';
END IF;
END FS_A_FUNCTION;
In both cases I'm using max() in case there are multiple rows with the same date.
You are testing if the inDate exists in the dates field of your table, but only after the loop ends, so the reference to tDates will be the last record of the query.
You can do this by simply checking if exists a record with the date:
cursor c1 is
select *
from A_TABLE
where Dates = inDate;
As you can see, there's no need to do a loop.

Resources