how to fix this function in pl-sql - oracle

I wrote a function in pl-sql to check if all employees have their salary between the correct range of maximum and minimum salary. But it's giving me error like this :
Error(9,3): PL/SQL: Statement ignored.
Error(9,16): PLS-00306: wrong number or types of arguments in call to '>'
CREATE OR REPLACE FUNCTION MIN_MAX_SAL RETURN NUMBER AS
cursor emp_cur is select salary from employees ;
emp_sal emp_cur%rowtype;
min_sal jobs.min_salary%type;
max_sal jobs.max_salary%type;
BEGIN
select min_salary , max_salary into min_sal , max_sal from jobs;
for emp_sal in emp_cur loop
if ((emp_sal > max_sal) or (emp_sal < min_sal)) then
return 0;
end if;
end loop;
RETURN 1;
END MIN_MAX_SAL;
what is the wrong ??

emp_sal is a row type, even though it only contains one column. You need to specify the column within that which you're comparing:
if ((emp_sal.salary > max_sal) or (emp_sal.salary < min_sal)) then
You don't need to declare emp_sal with this form of cursor loop.
If there is more than one record in jobs, which seems likely, then you first select will throw ORA-02112, too many rows selected. You probably mean to compare each employee with their job's salary range. Assuming you're intentionally doing this in PL/SQL as an exercise and want to use cursors - since it could easily be done in a single SQL statement - you perhaps want to use nested loops, with one loop to find job information and then an inner loop checking all employees with that job, something like:
create or replace function min_max_sal return number as
cursor job_cur is
select job_id, min_salary, max_salary from jobs;
cursor emp_cur(p_job_id jobs.job_id%type) is
select emp_id, salary from employees where job_id = p_job_id;
begin
for job_rec in job_cur loop
for emp_rec in emp_cur(job_rec.job_id) loop
if (emp_rec.salary > job_rec.max_salary)
or (emp_rec.salary < job_rec.min_salary) then
return 0;
end if;
end loop;
end loop;
return 1;
end min_max_sal;
/
Of course you can just join to do this in one hit:
select count(*)
from employees e
join jobs j on j.job_id = e.job_id
where (e.salary > j.max_salary)
or (e.salary < j.min_salary);
or:
where not e.salary between j.min_salary and j.max_salary
You could still use that within a PL/SQL Block if you wanted to, selectng that value into a variable and then returning based on whether the count was zero or not.

Related

PL/SQL FETCH into data string

I'm new to oracle forms and i have a problem i hope you might help me to resolve.
My question is there a way to put data fetched into a data string?
For example here i use dbms_output, but is there a way to put all rows into data string separated by ';'?
So ideally result should look something like 'engineer;manager;database analyst;'
Thank you
DECLARE
Job_desc varchar(100);
CURSOR cur_job is
SELECT job_id
from job a where a.salary='10000';
BEGIN
OPEN cur_job;
LOOP
FETCH cur_job into job_desc;
EXIT WHEN cur_job%notfound;
dbms_output.put_line(job_desc || ';');
END LOOP;
As it is Forms, you'd do something like this (based on Scott's EMP table):
declare
job_desc varchar2(100);
begin
for cur_r in (select distinct job
from emp
where sal > 1000
)
loop
job_desc := job_desc ||';'|| cur_r.job;
end loop;
end;
Now it depends on what you want to do with job_desc:
display it as a message on the screen (two consecutive message calls; otherwise, it would be displayed in the status line)
end loop;
message(job_desc);
message(job_desc);
(alternatively, see how alerts work)
put it into block item:
end loop;
:block.job_description := job_desc;
But, for this option, you'd rather directly put jobs into the item, not into the variable and then into the item.
I don't know whether Forms 10 support listagg; if so, it would be even simpler:
select listagg(job, ';') within group (order by null) job_desc
from (select distinct job
from emp
where sal > 1000
);
If not, xmlagg works:
select rtrim (xmlagg (xmlelement (e, job || ', ') order by job).extract
('//text()'), ', ')
from (select distinct job
from emp
where sal > 1000
);
So, quite a few options; pick one.

PL/SQL cannot delete multiple rows

I have written simple code using PL/SQL to delete multiple rows from a table, but below code only deletes one row every i trigger it.
DECLARE
i number(2);
BEGIN
FOR i IN 1..4 LOOP
DELETE FROM table_name WHERE rownum = i;
dbms_output.put_line('i is: '|| i);
END LOOP;
END;
Can someone please suggest what is wrong with code?
ROWNUM is the nth row read.
select * from table_name where rownum = 1;
gets you the first row.
select * from table_name where rownum <= 2;
gets you the first two rows.
select * from table_name where rownum = 2;
gets you no rows, because you cannot read a second row without having read a first one.
This said, you'd have to replace
DELETE FROM table_name WHERE rownum = i;
with
DELETE FROM table_name WHERE rownum = 1;
But why would you do this anyway? Why delete arbitrarily picked records? Why use PL/SQL at all, rather than a mere DELETE FROM table_name WHERE rownum <= 4;?
What you need to understand is how Oracle processes ROWNUM. When assigning ROWNUM to a row, Oracle starts at 1 and only only increments the value when a row is selected; that is, when all conditions in the WHERE clause are met. Since our condition requires that ROWNUM is greater than 2 or equal to nth value, no rows are selected and ROWNUM is never incremented beyond 1.
If you really do wanna achieve it using PLSQL anot using SQL query as my friend Throsten has stated then please find a work around below.
I Created a dummy table test_c which holds 1 column (ID with number as its type).
set serveroutput on ;
DECLARE
i number(2);
j number(2);
counter number(10):=0;
BEGIN
FOR i IN 5..11 LOOP
if counter = 0 then
j:=i;
end if;
DELETE FROM test_c WHERE ID = (select id from (select id,rownum as ro from test_c order by id) where ro =j);
dbms_output.put_line('i is: '|| i);
counter:=counter+1;
END LOOP;
END;
Please note that this is not the right way to do it, but will work for your requirement.

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.

ORA-01422: exact fetch returns more than requested number of rows ORA-06512: at line 34

for this particular block, i am continuously getting
ora 0142 : exact fetch returns more than requested number of rows
, i have tried distinct, rank and rownum, but nothing seems to work.
begin
for i in c1 loop
if i.estacao_a2 is not null then
begin
select migrationidentifier
into v_master_location
from (
select com.migrationidentifier
, row_number () over (partition by com.migrationidentifier order by com.migrationidentifier asc) RK
from com_location com
where round (i.latitude_a_decimal, 1) =
round (com.LATITUDEWGS84, 1)
and round (i.longitude_a_decimal, 1) =
round (com.longitudewgs84, 1)
and com.sourcedomain = 'FENIX'
)
where rk=1
;
exception
when no_data_found
then dbms_output.put_line ( 'NO RECORDS FOUND IN COM_LOCATION FOR LATITUDE:' || i.latitude_a_decimal);
end;
end if;
end loop;
end;
OK, so you have more than one migrationidentifier in com_location for that set of latitude_a_decimal and longitude_a_decimal. Is this bad data? Or possible? Or there is some ordering criteria that will determine which one are you supposed to take?
If there is an ordering criteria - add that to your query then put the query into a cursor and do an OPEN...FETCH INTO ... CLOSE to grab that row first and populate your variable. You won't need your EXCEPTION block doing it this way, you instead after the fetch do IF cursorname%NOTFOUND THEN dbms_output() ENDIF;
declare
cursor get_migrationidentifier (in_lat_decimal number, in_long_decimal number)
IS
select migrationidentifier from
(select com.migrationidentifier
,row_number () over (partition by com.migrationidentifier order by com.migrationidentifier asc) RK
from com_location com
where round (i.latitude_a_decimal, 1) =
round (com.LATITUDEWGS84, 1)
and round (i.longitude_a_decimal, 1) =
round (com.longitudewgs84, 1)
and com.sourcedomain = 'FENIX') where rk=1
order by migrationidentifier ;
begin
for i in c1
loop
if i.estacao_a2 is not null then
OPEN get_migrationidentifier (i.latitude_a_decimal. i.longitude_a_decimal);
FETCH get_migrationidentifier INTO v_master_location;
IF get_migrationidentifier%NOTFOUND
THEN v_master_location := null;
dbms_output.
put_line (
'NO RECORDS FOUND IN COM_LOCATION FOR LATITUDE:'
|| i.latitude_a_decimal);
end IF;
CLOSE get_migrationidentifier;
end if;
end loop;

Using Rownum in Cursor Bulk Collect Oracle

I'm trying to use the rownum to simulate a column autonumbered as I need to use it as an ID. Since it is an ID, I look at the final table if no record with MAX (ID).
The problem I have is when I want to do arithmetic operations within the cursor or when you invoke, or when you want to use a function. The ROWNUM (v_id) field is empty me when I want to print with DBMS_OUTPUT . Anyone have any idea how to solve it without using sequences ?
Here put the sample code.
declare
max_id number;
CURSOR INSRT(w_max number) IS
SELECT f_max_fact_sap(to_number(V_ID),w_max) AS V_ID,Seriei,serief
FROM (SELECT To_Char(ROWNUM) AS V_ID, A.*
FROM (SELECT DISTINCT a.matnr, a.seriei, a.serief,a.xblnr,a.fecha_sap, ((SERIEF-SERIEI)+1) AS rango
FROM SOPFUN.TT_ZMOVIMI_FACTURADAS a
WHERE 0 =(SELECT COUNT(1)
FROM PA_ZMOVIMI_FACTURADAS B
WHERE A.SERIEI = B.SERIEI
AND A.SERIEF = B.SERIEF
AND A.MATNR = B.MATNR
AND A.FECHA_SAP=B.FECHA_SAP)
AND A.FECHA_SAP IS NOT NULL) A);
TYPE T_INSRT IS TABLE OF INSRT%ROWTYPE INDEX BY PLS_INTEGER;
V_INSRT T_INSRT;
begin
SELECT Max(Nvl(ID,10000)) INTO MAX_ID-- To Proof because the table is empty
FROM PA_ZMOVIMI_FACTURADAS;
OPEN INSRT(MAX_ID);
LOOP
FETCH INSRT BULK COLLECT INTO V_INSRT LIMIT 1000;
FOR I IN 1 .. V_INSRT.Count loop
DBMS_OUTPUT.PUT_LINE('ID: ' ||V_INSRT(I).V_ID||' SI: '||V_INSRT(I).SERIEI||' SI: '||V_INSRT(I).SERIEF||' OPERACION: '||to_char(f_max_fact_sap(V_INSRT(I).V_ID,MAX_ID)));
end loop;
EXIT WHEN INSRT%NOTFOUND;
END LOOP;
end;

Resources