PLSQL Encountered the symbol "IF" in forall - oracle

Is it posssible use if statments in forall, when try get error PLS-00103: Encountered the symbol "IF" when expecting one of the following: . ( * # % & - + / at mod remainder rem select update with
FORALL i IN 1 .. P_DAYS_IDS.COUNT
if (P_DAYS_IDS(i) = 1) then
Update test set col_1 = 'Y' where id = 1;
elsif (P_DAYS_IDS(i) = 2) then
Update test set col_2 = 'Y' where id = 2;
end if;

No. forall exists to eliminate context shifts between SQL and PL/SQL so it only works if you are doing a single SQL operation using each element of the collection.
You can use a regular for loop if you want to execute PL/SQL code in a loop.
FOR i IN 1 .. P_DAYS_IDS.COUNT
LOOP
if (P_DAYS_IDS(i) = 1) then
Update test set col_1 = 'Y' where id = 1;
elsif (P_DAYS_IDS(i) = 2) then
Update test set col_2 = 'Y' where id = 2;
end if;
end loop;

You cannot use IF within FORALL as it requires exactly 1 DML, and there is no IF in SQL. However the conditions tested migrate to a CASE expression or the WHERE clause. For this you can combine the IF predicate and the WHERE clauses into a CASE expression for each column that may be updated.
forall i in 1 .. p_days_ids.count
update test
set col_1 = case when p_days_ids(i) = 1 and id = 1 then 'Y' else col_1 end
, col_2 = case when p_days_ids(i) = 2 and id = 2 then 'Y' else col_2 end;
Notice when a column is reference in SET assigning a value is required. Thus if the when condition is not meet the column is just set to its current value.

Related

How to stop repeating values/ Null values from being output in a PL/SQL loop

I'm having an issue where null values and repeating columns are being output in my stored procedure.
Currently, an array is being passed into my procedure as TYPE. I need to output all part numbers/pdc/src from the array. Currently if the records in TYPE (array) match what's in inventory there are no issues. If they don't exist then only nulls will be output for those respective columns in the cursor.
I've tried inserting the current record in a variable and using a NVL operator but then the records just end up repeating. Does anyone have any suggestions?
PROCEDURE iws_update (i_Tbulk_type IN update_bulk_type,
o_Cresultset OUT SYS_REFCURSOR)
AS
partcount VARCHAR2 (20) := NULL;
sumcount VARCHAR2 (4) := NULL;
miscount VARCHAR2 (1) := NULL;
BEGIN
FOR i IN i_Tbulk_type.FIRST .. i_Tbulk_type.LAST
LOOP
partcount := i_Tbulk_type (i).part_no; --
sumcount := i_Tbulk_type (i).pdc;
miscount := i_Tbulk_type (i).part_no;
UPDATE inventory i -- update logic
SET i.diff_qty = i_Tbulk_type (i).mismatch_qty,
i.aval_qty = ( (i.aval_qty + i.diff_qty) - i_Tbulk_type (i).mismatch_qty)
WHERE i.dept_cd = i_Tbulk_type (i).pdc --First PK validation
AND i.src = i_Tbulk_type (i).src --second PK validation
AND TRIM (i.part_no) = TRIM (i_Tbulk_type (i).part_no) -- third PK validation
AND i.diff_qty >= 0 -- second step in validation: diff qty must be greater than zero
AND ( (i.diff_qty + i.aval_qty) - i_Tbulk_type (i).mismatch_qty) >=
0 ; -- Final condition that must be met: the sum of diff and aval quantity minus the qty passed in by the array must be greater than or equal to zero. If all conditions are met update proceeds.
COMMIT;
OPEN o_Cresultset FOR --cursor to send back records and flags to Back End
SELECT t.pdc, -- array dept code
t.src, -- array source
t.part_no, -- array part no
CASE WHEN i.part_no IS NULL THEN 'FAIL' ELSE 'PASS' END AS part_no_flag, -- if part no is null then it doesn't exist in inventory table and will print 'FAIL' as a flag. else pass.
( (i.diff_qty + i.aval_qty) - t.mismatch_qty) AS avail_qty,-- sends the quantity based on second validation method. line 440. If output is negative BE will mark as fail
i.diff_qty AS unmatch_qty -- prints qty. if negative BE marks as fail.
FROM TABLE (CAST (i_Tbulk_type AS update_bulk_type)) t
LEFT JOIN inventory i
ON i.dept_cd = t.pdc
AND i.src = t.src
AND TRIM (i.part_no) = TRIM (t.part_no);
END LOOP;
END iws_update;

Why I receive "NO DATA FOUND" exception when I use RETURNING clause after update statement?

I'm coding a simple LOOP FOR to update a column from a table using information populated in an associative array. All seems right when I only use UPDATE statement but when I add a RETURNING clause I receive "NO DATA FOUND" error. Thanks!
DECLARE
TYPE emps_info IS TABLE OF employees23%ROWTYPE
INDEX BY PLS_INTEGER;
t_emps_current_info emps_info;
t_emps_new_info emps_info;
BEGIN
SELECT *
BULK COLLECT INTO t_emps_current_info
FROM employees;
FOR emps_index IN t_emps_current_info.FIRST .. t_emps_current_info.LAST
LOOP
IF
NVL(t_emps_current_info(emps_index).commission_pct, 0) = 0 THEN
UPDATE employees23
SET commission_pct = 0.3
WHERE employee_id = t_emps_current_info(emps_index).employee_id;
ELSIF
t_emps_current_info(emps_index).commission_pct BETWEEN 0.1 AND 0.3 THEN
UPDATE employees23
SET commission_pct = 0.5
WHERE employee_id = t_emps_current_info(emps_index).employee_id;
END IF;
END LOOP;
END;
Now When I add RETURNING clause I receive following error:
DECLARE
TYPE emps_info IS TABLE OF employees23%ROWTYPE
INDEX BY PLS_INTEGER;
t_emps_current_info emps_info;
t_emps_new_info emps_info;
BEGIN
SELECT *
BULK COLLECT INTO t_emps_current_info
FROM employees;
FOR emps_index IN t_emps_current_info.FIRST .. t_emps_current_info.LAST
LOOP
IF
NVL(t_emps_current_info(emps_index).commission_pct, 0) = 0 THEN
UPDATE employees23
SET commission_pct = 0.3
WHERE employee_id = t_emps_current_info(emps_index).employee_id
RETURNING commission_pct
INTO t_emps_new_info(emps_index).commission_pct;
ELSIF
t_emps_current_info(emps_index).commission_pct BETWEEN 0.1 AND 0.3 THEN
UPDATE employees23
SET commission_pct = 0.5
WHERE employee_id = t_emps_current_info(emps_index).employee_id
RETURNING commission_pct
INTO t_emps_new_info(emps_index).commission_pct;
END IF;
DBMS_OUTPUT.PUT_LINE('EMPLOYEE_ID: ' ||
t_emps_current_info(emps_index).employee_id ||
' OLD COMMISSION: ' ||
NVL(t_emps_current_info(emps_index).commission_pct, 0)
|| ' NEW COMMISSION: ' ||
t_emps_new_info(emps_index).commission_pct);
END LOOP;
END;
Informe de error -
ORA-01403: no data found
ORA-06512: at line 22
01403. 00000 - "no data found"
*Cause: No data was found from the objects.
*Action: There was no data from the objects which may be due to end of fetch.
This part in your code
DBMS_OUTPUT.PUT_LINE('EMPLOYEE_ID: ' ||
t_emps_current_info(emps_index).employee_id ||
' OLD COMMISSION: ' ||
NVL(t_emps_current_info(emps_index).commission_pct, 0)
|| ' NEW COMMISSION: ' ||
t_emps_new_info(emps_index).commission_pct);
prints both the original array as well as the returning array .
At some point of time during your update it may happen that your update statement doesnt update any row , in that case your original array will have a value at that index , but your returning array wont
So check for the existence of index to avoid this error
case when t_emps_new_info.exists(emps_index) then
dbms_output.put_line('print something') ;
else dbms_output.put_line('print something else') ;
end case;
You are getting NO_DATA_FOUND because nowhere in your code are you initialising (creating) the t_emps_new_info(emps_index) records - whenever your code tries to assign t_emps_new_info(emps_index).commission_pct it will fail.
I'm not sure why it should be necessary, but I would change each UPDATE statement return the value into a simple local variable. Then I'd set the table record field to the simple variable.
Probably unrelated, but I noticed that your ELSIF test isn't doing an NVL() on the existing t_emps_current_info(emps_index).commission_pct, as your IF test does.
If it was me, I'd log the updates of both sides of the IF-ELSEIF to see if one update is working while the other fails. Maybe it's just failing on the ELSIF, because of the missing NVL()?

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.

PL/SQL If statement, cursors, and a record set. How to use an IF statement after I use the Cursor?

I am wondering if I am going about this the right way.
My main issue that compiler gives is for this line
IF SELECT 1 FROM works WHERE an_employee.employee_name IN works.manager_name THEN
Error(17,8): PLS-00103: Encountered the symbol "SELECT" when expecting one of the following: ( - + case mod new not null continue avg count current exists max min prior sql stddev sum variance execute forall merge time timestamp interval date pipe
What I am trying to do is use a cursor that detects the first condition if they work in a company that is in that city. In this example in my table it will give 2 companies, and about 7 or 8 employees. Then I put that into my variable an_employee. What I am trying to do is use that employee then to see if they are a manager from my manages table which has multiple rows/tuples. Not everyone is a manager. How can I see if my employee_name is in the list of manager_name?
Do I declare another cursor for a manager and then do a nested loop?
Can I use my existing cursor and do a select query to get the list of managers from the table manages.manager_name? If I do that, how can I use it in the IF statement as my condition?
-- Give all employees that work in a company located in city X
-- a Y percent raise if they are managers and
-- a Z percent raise if they are not a manager
-- X, Y, and Z will be the three parameters for the stored procedure.
-- Build / compile a stored procedure
CREATE OR REPLACE PROCEDURE give_raises(X company.city%TYPE, Y NUMBER, Z NUMBER) IS
an_employee works.employee_name%TYPE;
-- cursor declaration
cursor Cursor1 IS
select works.employee_name
from works
where works.company_name IN (select company_name from company where city = 'London');
BEGIN
SELECT manager_name INTO managers FROM manages;
OPEN Cursor1;
LOOP
FETCH Cursor1 INTO an_employee;
EXIT WHEN Cursor1%NOTFOUND;
-- is a manager give Y percent raise
IF SELECT 1 FROM works WHERE an_employee.employee_name IN works.manager_name THEN
update works
set works.salary = works.salary + (works.salary * Y)
where works.employee_name = an_employee.employee_name;
ELSE -- is not a manager give Z percent raise
update works
set works.salary = works.salary + (works.salary * Z)
where works.employee_name = an_employee.employee_name;
END IF;
END LOOP;
CLOSE Cursor1;
END;
Also you should know. I am using Oracle, Oracle Sql Developer and the IDE.
If works on a logical condition and does not support Select statements in the condition.
You need to rework your code like this
SELECT count(*)
INTO v_value
FROM works
WHERE an_employee.employee_name = works.manager_name;
IF v_value = 1 THEN
--do some stuff
ELSE
--do some other stuff
END IF;
If i understand your problem correctly you are trying to identify whether an employee is manager or not based on thatsalary is computed. Hope below snippet helps.
CREATE OR REPLACE PROCEDURE give_raises(
X company.city%TYPE,
Y NUMBER,
Z NUMBER)
AS
managers PLS_INTEGER;
BEGIN
FOR an_employee IN
(SELECT works.employee_name
FROM works
WHERE works.company_name IN
(SELECT company_name FROM company WHERE city = 'London'
)
)
LOOP
SELECT COUNT(1)
INTO managers
FROM manages m
WHERE m.manager_name = an_employee.employee_name;
-- is a manager give Y percent raise
IF managers <> 0 THEN
UPDATE works
SET works.salary = works.salary + (works.salary * Y)
WHERE works.employee_name = an_employee.employee_name;
ELSE -- is not a manager give Z percent raise
UPDATE works
SET works.salary = works.salary + (works.salary * Z)
WHERE works.employee_name = an_employee.employee_name;
END IF;
END LOOP;
END;
Your problem is essentially the same than e.g. in a question posted yesterday. PL/SQL if statement expects a boolean expression not a query result set.
A good practice is to encapsulate the query to a function returning a suitable value.
Example:
SQL> !cat so53.sql
declare
function is_foo_1(p_foo in varchar2) return boolean is
v_exists number;
begin
select count(*)
into v_exists
from dual
where dummy = p_foo
and rownum = 1
;
return
case v_exists
when 1 then true
else false
end;
end;
function is_foo_2(p_foo in varchar2) return number is
v_exists number;
begin
select count(*)
into v_exists
from dual
where dummy = p_foo
and rownum = 1
;
return v_exists;
end;
begin
-- is_foo_1 returns a boolean value than is a valid boolean expression
if is_foo_1('X')
then
dbms_output.put_line('1:X');
end if;
if not is_foo_1('Y')
then
dbms_output.put_line('1:not Y');
end if;
-- is_foo_2 returns a number that is not a valid boolean expression (PL/SQL
-- doesn't have implicit type conversions) so one have to use an operator
-- (in this example `=`-operator) to construct an explicit boolean
-- expression
if is_foo_2('X') = 1
then
dbms_output.put_line('2:X');
end if;
if is_foo_2('Y') = 0
then
dbms_output.put_line('2:not Y');
end if;
end;
/
Example run:
SQL> #so53.sql
1:X
1:not Y
2:X
2:not Y
PL/SQL procedure successfully completed.
SQL>

PL/SQL how to check if exists and return error code

Disregard if using MERGE INTO is better in this case or not. I just wonder if I can check if the row exists or not. If not, then set the return code to 1 and return immediately. If yes, then continue to execute the rest of code and set return code to 0 in the end. Below code is not working as it always executes to the end. How should I fix it?
BEGIN
-- check
SELECT CASE
WHEN NOT EXISTS (SELECT 1 FROM s WHERE s.col1 = 1 AND s.col2 = 2)
THEN 1
END
INTO ret FROM dual;
-- update
UPDATE s
SET s.col3 = 3
WHERE s.col1 = 1 AND s.col2 = 2;
COMMIT;
SELECT 0 INTO ret FROM dual;
RETURN ret;
END foo;
What if I want be able to distinguish if it's s.col1 = 1 not exist or s.col2 = 2 not exist. And have 2 different return codes for them. How should I do in this case?
I'm wondering if thre is any point checking if the row exists in the first place and why you just don't issue the UPDATE straightaway?
Could you not do something like this?
BEGIN
-- update
UPDATE s
SET s.col3 = 3
WHERE s.col1 = 1 AND s.col2 = 2;
COMMIT;
IF SQL%ROWCOUNT = 0 THEN
RETURN 0;
ELSE
RETURN 1;
END IF;
END foo;
You are not doing anything with the value stored ret.
There is no IF around the UPDATE statement that checks if ret is 1 or null (the other alternative). And because there is no IF the rest of the procedure is always executed.
Something like this is needed:
SELECT sum(case when col1 = 1 then 1 else 0 end) as col1_count,
sum(case when col2 = 2 then 1 else 0 end) as col2_count
into ret1, ret2
FROM s
WHERE s.col1 = 1
or s.col2 = 2;
if ret1 > 0 and ret2 > 0 then
update ...;
commit;
ret := 0;
elsif (ret1 > 0 and ret2 = 0) then
ret := 1;
elsif (ret1 = 0 and ret2 > 0) then
ret := 2;
end if;
return ret;
A much more efficient approach is to simply do the update and check if any rows where modified. Running the select before doing the update simply doubles the work if the row exists. Running only the update when the row does not exist is the same work as doing the select.
What if I want be able to distinguish if it's s.col1 = 1 not exist or
s.col2 = 2 not exist. And have 2 different return codes for them. How
should I do in this case?
I've probably over-complicated it with a BULK COLLECT - especially if the update is for only one row at any one time - but you can of course modify the code accordingly - the principle remains the same.
The code aims to return 1,2 or 3 depending on which UPDATE condition is met regarding col1 and col2.
DECLARE
TYPE test_rec is record ( .... );
TYPE result_tab IS TABLE OF test_rt%ROWTYPE;
lt_results result_tab;
lv_ret NUMBER(1) := 0;
BEGIN
SELECT x.* FROM (
SELECT s.*, 1 as ret
FROM s
WHERE s.col1 = 1 and s.col2 != 2
UNION ALL
(SELECT s.*, 2
FROM s
WHERE s.col1 =! 1 and s.col2 = 2)
UNION ALL
(SELECT s.*, 3
FROM s
WHERE s.col1 = 1 and s.col2 = 2))
BULK COLLECT INTO lt_results;
FOR i in lt_results.first .. lt_results.last LOOP
<<DO YOUR UPDATE>>
lv_ret := lt_results(i).ret;
END LOOP;
COMMIT;
RETURN lv_ret;
END;

Resources