Encountered the symbol "SELECT" when expecting one of the following: - oracle

i need help from you guys.
I am using oracle apex for university, and my task is to create triggers. I'm struggling a lot creating them, I don't even know what is wrong with this trigger. Can someone help me?
I get error
Encountered the symbol "SELECT" when expecting one of the following: ( - + case mod new not null <an identifier> <a double-quoted delimited-identifier> <a bind variable> continue avg count current exists max min prior sql stddev sum variance execute forall merge time timestamp interval date <a string literal with character set specification> <a number> <a single-quoted SQL string> pipe <an alternatively-quoted string literal with character set specification> <an alternat
Error at line 3: PLS-00049: bad bind variable 'NEW'
Error at line 3: PLS-00103: Encountered the symbol "" when expecting one of the following: . ( * # % & = - + ; < / > at for in is mod remainder not rem <an exponent (**)> <> or != or ~= >= <= <> and or like like2 like4 likec between group having intersect minus order start union where connect || indicator multiset member submultiset
Error at line 3: PLS-00049: bad bind variable 'CONTRACT' Error at line 3: PLS-00049: bad bind variable 'CONTRACT' -
CREATE OR REPLACE TRIGGER restrict_orders
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
DECLARE countOrders number;
set countOrders := SELECT count(contract) FROM orders WHERE :new:contract := old:contract AND (:old.status := 'PREPARING' OR :old.status := 'IN PROGRESS');
IF (countOrders :> 3)
THEN
RAISE_APPLICATION_ERROR (-20202, 'You have too much active orders.');
END IF;
END;

Quite a few errors; see if this helps.
Sample table:
SQL> select * From orders;
CONTRACT STATUS
---------- ---------
1 PREPARING
Trigger:
SQL> CREATE OR REPLACE TRIGGER restrict_orders
2 BEFORE INSERT ON orders
3 FOR EACH ROW
4 DECLARE
5 countOrders number;
6 BEGIN
7 SELECT count(contract)
8 INTO countOrders
9 FROM orders
10 WHERE contract = :new.contract
11 AND status IN ('PREPARING', 'IN PROGRESS');
12
13 IF countOrders > 3 THEN
14 RAISE_APPLICATION_ERROR (-20202, 'You have too many active orders.');
15 END IF;
16 END;
17 /
Trigger created.
Testing:
SQL> insert into orders (contract, status) values (1, 'PREPARING');
1 row created.
SQL> insert into orders (contract, status) values (1, 'PREPARING');
1 row created.
SQL> insert into orders (contract, status) values (1, 'PREPARING');
1 row created.
SQL> insert into orders (contract, status) values (1, 'PREPARING');
insert into orders (contract, status) values (1, 'PREPARING')
*
ERROR at line 1:
ORA-20202: You have too many active orders.
ORA-06512: at "SCOTT.RESTRICT_ORDERS", line 11
ORA-04088: error during execution of trigger 'SCOTT.RESTRICT_ORDERS'
Kind of works. However, note that it'll fail if you attempt to insert more than a single row at a time because table will be mutating (so you'd have to take another, more complex approach), but - if you'll insert just one row at a time, you're good:
SQL> insert into orders (contract, status)
2 select 1, 'PREPARING' from dual union all
3 select 2, 'IN PROGRESS' from dual;
insert into orders (contract, status)
*
ERROR at line 1:
ORA-04091: table SCOTT.ORDERS is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.RESTRICT_ORDERS", line 4
ORA-04088: error during execution of trigger 'SCOTT.RESTRICT_ORDERS'
SQL>

You need to correct your syntax which should be -
CREATE OR REPLACE TRIGGER restrict_orders
BEFORE INSERT ON orders
FOR EACH ROW
DECLARE countOrders number;
BEGIN
SELECT count(contract)
INTO countOrders
FROM orders
WHERE :new.contract = :old.contract
AND :old.status IN ('PREPARING', 'IN PROGRESS');
IF (countOrders >= 3) THEN
RAISE_APPLICATION_ERROR (-20202, 'You have too much active orders.');
END IF;
END;

Related

Create trigger that does the same as unique static constraint PL/SQL

As a disclaimer, this question is only for my curiosity and for practicing triggers and compound triggers particularly.
I've been trying to replace the UNIQUE constraint with a trigger in order to understand triggers more, but I haven't been successful so far, mainly because of the global variables that I'm not so comfortable with inside the compound triggers.
what I'm trying to do with a trigger :
ALTER TABLE Employee
ADD CONSTRAINT emp_UQ
UNIQUE (id_emp, id_office);
here's what I tried so far (t for type, g for global):
CREATE OR REPLACE TRIGGER BIUUniqueConstraint
FOR INSERT OR UPDATE ON Employee
COMPOUND TRIGGER
TYPE tIdEmpOffice IS TABLE OF Employee.id_emp%TYPE
INDEX BY VARCHAR2(80);
gIdEmpOffice tIdEmpOffice;
TYPE tId_emp IS TABLE OF Employee.id_emp%TYPE;
gId_emp tId_emp;
TYPE tId_office IS TABLE OF Employee.id_office%TYPE;
gId_office tId_office;
BEFORE STATEMENT IS
BEGIN
SELECT e.id_emp, e.id_office
BULK COLLECT INTO gId_emp, gId_office
FROM Employee e
ORDER BY e.id_emp;
FOR j IN 1..gId_emp.COUNT() LOOP
gIdEmpOffice(gId_emp(j)) := gId_office(j);
END LOOP;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF INSERTING THEN
DBMS_OUTPUT.PUT_LINE(gIdEmpOffice(:NEW.id_emp);
END IF;
END BEFORE EACH ROW;
END BIUCompteParti;
/
I have no clue how to move forward with this trigger and would like suggestions and explanations if possible about how to use globale variables to store data and how to use them on a row level.
Here's one option.
Sample table:
SQL> create table employee (id_emp number, id_office number);
Table created.
Trigger:
SQL> create or replace trigger trg_emp_unique
2 for insert or update on employee
3 compound trigger
4 type t_row is record (id_emp number, id_office number);
5 type t_tab is table of t_row;
6 l_tab t_tab := t_tab();
7 l_cnt number;
8
9 before each row is
10 begin
11 l_tab.extend;
12 l_tab(l_tab.last).id_emp := :new.id_emp;
13 l_tab(l_tab.last).id_office := :new.id_office;
14 end before each row;
15
16 after statement is
17 begin
18 for i in l_tab.first .. l_tab.last loop
19 select count(*) into l_cnt
20 from employee
21 where id_emp = l_tab(i).id_emp
22 and id_office = l_tab(i).id_office;
23 if l_cnt > 1 then
24 raise_application_error(-20000, 'Unique constraint violated');
25 end if;
26 end loop;
27 l_tab.delete;
28 end after statement;
29 end trg_emp_unique;
30 /
Trigger created.
SQL>
As you can see, it doesn't contain before statement nor after each row parts; if they aren't used, you don't have to put them into the trigger.
Let's try it:
SQL> insert into employee(id_emp, id_office) values (1, 1);
1 row created.
SQL> insert into employee(id_emp, id_office) values (1, 2);
1 row created.
Re-insert the first combination (1, 1):
SQL> insert into employee(id_emp, id_office) values (1, 1);
insert into employee(id_emp, id_office) values (1, 1)
*
ERROR at line 1:
ORA-20000: Unique constraint violated
ORA-06512: at "SCOTT.TRG_EMP_UNIQUE", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_EMP_UNIQUE'
That failed (as it should). Let's update existing rows:
SQL> select * from employee;
ID_EMP ID_OFFICE
---------- ----------
1 1
1 2
SQL> update employee set id_office = 5;
update employee set id_office = 5
*
ERROR at line 1:
ORA-20000: Unique constraint violated
ORA-06512: at "SCOTT.TRG_EMP_UNIQUE", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_EMP_UNIQUE'
Kind of works.
You can count whether a paired value for those columns already exists with a statement level trigger such as
CREATE OR REPLACE TRIGGER trg_chk_unique_emp_dept_id
AFTER INSERT ON employee
DECLARE
val INT;
BEGIN
SELECT NVL(MAX(COUNT(*)),0)
INTO val
FROM employee
GROUP BY id_emp, id_office;
IF val > 1 THEN
RAISE_APPLICATION_ERROR(-20304,
'Each employee may be assigned to a department once at most !');
END IF;
END;
/
which will check if an attempt made to insert the same value pairs more than once, and if so, it will hurl with an error message.
Demo

Can cursor be use in UPDATE and DELETE in Function

I have one doupt while I try to use UPDATE and DELETE method.
So starting from basic function something like
FUNCTION GET_ANSWER(p_questionId IN INT)
RETURN SYS_REFCURSOR IS
rc SYS_REFCURSOR;
/*getAnswer*/
BEGIN
OPEN rc FOR
SELECT * FROM answers WHERE AnswerID = p_questionId;
RETURN rc;
END GET_ANSWER;
While I try to use DELETE or UPDATE method I get error message
Error(3529,1): PLS-00103: Encountered the symbol "DELETE" when expecting one of the following: ( - + case mod new not null select with <an identifier> <a double-quoted delimited-identifier> <a bind variable> continue avg count current exists max min prior sql stddev sum variance execute forall merge time timestamp interval date <a string literal with character set specification> <a number> <a single-quoted SQL string> pipe <an alternatively-quoted string literal with character set specification>
Error(3530,1): PLS-00103: Encountered the symbol "RETURN"
Error(3536,1): PLS-00103: Encountered the symbol "END"
FUNCTION DELETE_ACTIVITY(p_activityId IN INT)
RETURN SYS_REFCURSOR IS
rc SYS_REFCURSOR;
BEGIN
OPEN rc FOR
DELETE FROM activities WHERE id = p_activityId;
RETURN rc;
END DELETE_ACTIVITY;
What is wrong here ? Wher did I made mistake ?
Don't do it via cursor, but simply
DELETE FROM answers WHERE AnswerID = p_questionId;
Also, make it a procedure, not a function.
As of UPDATE, well - it depends on what you want to update and how, but - generally:
update answers set
some_column = some_value
where ansewrID = p_questionId
Why a procedure and not a function:
SQL> create or replace function f_test return number is
2 begin
3 delete from test;
4 return 1;
5 end;
6 /
Function created.
SQL> select f_test from dual;
select f_test from dual
*
ERROR at line 1:
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "SCOTT.F_TEST", line 3
SQL>
However, if a function is an autonomous transaction, you can do that, but - not recommended:
SQL> create or replace function f_test return number is
2 pragma autonomous_transaction;
3 begin
4 delete from test;
5 commit;
6 return 1;
7 end;
8 /
Function created.
SQL> select f_test from dual;
F_TEST
----------
1
SQL>
Cursors are only used for SELECT statements. You cannot open a cursor for an INSERT, UPDATE or DELETE statement.
See the documentation
An explicit cursor definition has this syntax:
CURSOR cursor_name [ parameter_list ] [ RETURN return_type ] IS
select_statement;

PLSQL trigger not working 0 rows inserted

Whatever I do to the trigger always returns "0 rows inserted".
It is like it can't find the new values after inserting them.
After adding the exception it return no_data_found and i don't know why.
before insert or update of rent_date, return_date on rent
for each row
declare
pragma AUTONOMOUS_TRANSACTION;
v_rentDate date;
v_returnDate date;
begin
select rent_date
into v_rentDate
from rent
where rent_date = :new.rent_date;
select return_date
into v_returnDate
from rent
where return_date = :new.return_date;
if v_returnDate < v_rentDate then
raise_application_error(-20158, 'Return date must be after the rent date');
else
dbms_output.put_line('TEST');
end if;
exception when no_data_found then raise_application_error(-20157, 'No data found');
end;
/
insert into rent values (82,sysdate-5,101,sysdate,sysdate+5,100);
--0 rows inserted
It appears that you're doing it the wrong way. Here's why:
you are trying to select values from a table you're currently inserting into (or updating existing values)
Oracle complains that it can't do that because the table is mutating
in order to "fix" it, you used pragma autonomous_transaction which isolates trigger code from the main transaction
You shouldn't use that pragma for such a purpose. Lucky you, trigger can be rewritten in a simpler manner, the one that doesn't cause the mutating table error. As you want to compare rent_date and return_date, do it directly. Here's an example (see line #5):
SQL> create table rent
2 (id number,
3 rent_date date,
4 return_date date
5 );
Table created.
SQL> create or replace trigger trg_biu_rent
2 before insert or update on rent
3 for each row
4 begin
5 if :new.return_date < :new.rent_date then
6 raise_application_error (-20158, 'Return date must be after the rent date');
7 end if;
8 end;
9 /
Trigger created.
Testing:
SQL> -- This will fail
SQL> insert into rent (id, rent_date, return_date) values
2 (1, date '2019-05-25', date '2019-04-10');
insert into rent (id, rent_date, return_date) values
*
ERROR at line 1:
ORA-20158: Return date must be after the rent date
ORA-06512: at "SCOTT.TRG_BIU_RENT", line 3
ORA-04088: error during execution of trigger 'SCOTT.TRG_BIU_RENT'
SQL> -- This is OK
SQL> insert into rent (id, rent_date, return_date) values
2 (1, date '2019-03-28', date '2019-10-20');
1 row created.
SQL>

Invalid reference to variable PL/SQL

I'm having trouble in getting started with PL/SQL
Here is my code:
SET SERVEROUTPUT ON;
DECLARE
v_cname customers.customer_name%type := '&customer_name';
v_cardno customers.card_number%type := '&card_number';
v_lastcid customers.customer_id%type;
BEGIN
SELECT customer_id INTO v_lastcid from customers
where customer_id = (select max(customer_id) from customers);
dbms_output.put_line(v_lastcid);
INSERT INTO customers(customer_id, customer_name, card_number)
VALUES(v_lastcid.NEXTVAL, v_cname, v_cardno);
COMMIT;
END;
This returns an error:
ORA-06550: line 12, column 20:
PLS-00487: Invalid reference to variable 'V_LASTCID'
ORA-06550: line 12, column 20:
PL/SQL: ORA-02289: sequence does not exist
ORA-06550: line 11, column 13:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
Any help would me much appreciated!
You probably meant to say MAX + 1 (see lines 7 and 12), i.e.
SQL> create table customers
2 (customer_id number,
3 customer_name varchar2(20),
4 card_number varchar2(20));
Table created.
SQL> DECLARE
2 v_cname customers.customer_name%type := '&customer_name';
3 v_cardno customers.card_number%type := '&card_number';
4 v_lastcid customers.customer_id%type;
5
6 BEGIN
7 SELECT nvl(max(customer_id), 0) INTO v_lastcid from customers
8 where customer_id = (select max(customer_id) from customers);
9 dbms_output.put_line(v_lastcid);
10
11 INSERT INTO customers(customer_id, customer_name, card_number)
12 VALUES(v_lastcid + 1, v_cname, v_cardno);
13 COMMIT;
14 END;
15 /
Enter value for customer_name: Little
Enter value for card_number: Foot
0
PL/SQL procedure successfully completed.
SQL> select * from customers;
CUSTOMER_ID CUSTOMER_NAME CARD_NUMBER
----------- -------------------- --------------------
1 Little Foot
SQL>
Although it works, it is doomed to fail in a multi-user environment if two (or more) users fetch the same MAX value; insert would fail with a DUP-VAL-ON-INDEX error (if the ID is supposed to be unique).
Therefore, use a sequence (which is what NEXTVAL in your code suggests):
SQL> create sequence seq_cust;
Sequence created.
SQL> DECLARE
2 v_cname customers.customer_name%type := '&customer_name';
3 v_cardno customers.card_number%type := '&card_number';
4 v_lastcid customers.customer_id%type;
5 BEGIN
6 INSERT INTO customers(customer_id, customer_name, card_number)
7 VALUES(seq_cust.nextval, v_cname, v_cardno);
8 COMMIT;
9 END;
10 /
Enter value for customer_name: Big
Enter value for card_number: Foot
PL/SQL procedure successfully completed.
nextval is used to get the next value from a sequence. It's usually used to create a pseudo primary key. You can think of it like a special function that can only be called on sequences.
v_lastcid is a variable that has the same type as the customer_id column in the customers table. So if you had a table like this...
CREATE TABLE CUSTOMERS ( CUSTOMER_ID INTEGER );
...then v_lastcid is an integer;
If you're trying to make a dummy customer with the next highest number maybe you mean something like...
INSERT INTO customers(customer_id, customer_name, card_number)
VALUES(v_lastcid + 1, v_cname, v_cardno);
Hope this helps.

ORA-04091 mutating table error when calling a function from a procedure

I have a task:
Write a procedure to update salary (salary * % of increment) in emp table based on grade. Use function to get increment
This is my procedure:
CREATE OR REPLACE
PROCEDURE sal_incre
IS
CURSOR c_cur
IS
SELECT * FROM emp_task;
BEGIN
UPDATE emp_task SET sal = sal + sal_incr(grade_id);
FOR rec IN c_cur
LOOP
dbms_output.put_line(rec.empno||','||rec.ename||','||rec.sal);
END LOOP;
END;
This is my function code:
CREATE OR REPLACE
FUNCTION sal_incr(
p_grade NUMBER)
RETURN
IS
v_inc NUMBER;
BEGIN
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id IN
(SELECT grade_id FROM emp_task WHERE grade_id = p_grade
);
RETURN v_inc;
COMMIT;
END;
When I call the procedure I'm getting:
ORA-04091: table SCOTT.EMP_TASK is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.SAL_INCR", line 8
ORA-06512: at "SCOTT.SAL_INCRE", line 6
ORA-06512: at line 2
What am I doing wrong?
Your function is referring to the same table you're using in the procedure at the point you call that function, which is what causes this error. You're updating and querying it at the same time, in a way that could cause indeterminate (or confusing) results, even though you aren't querying the column you're updating. Oracle is protecting you from yourself here.
In your function you're doing:
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id IN
(SELECT grade_id FROM emp_task WHERE grade_id = p_grade
);
There is no need to look at the emp_task table here. Unless you've been passed an nonexistent value (which can't happen from your procedure) the subquery can only return the original p_grade argument value, so this is the same as:
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id = p_grade;
If you do that the function no longer refers to emp_task, so it won't throw the mutating trigger/function error when it's called as part of an update.
And your function should not be issuing a COMMIT - let the calling procedure, or preferably the session that calls the procedure, decide whether the who transaction should be committed or rolled back.
Also, from the title and column name it looks like raise_percent is a percentage, so you need to use that to find the value to multiply by - you shouldn't add that percentage figure. If that gives you a value of 2 for a 2% raise, for example, you need to either do this in your procedure:
UPDATE emp_task SET sal = sal * (1 + (sal_incr(grade_id)/100));
Or more neatly have your function return 1 + (raise_percent/100) and do:
UPDATE emp_task SET sal = sal * sal_incr(grade_id);
Change the procedure like this:
create or replace
procedure sal_incre
is
cursor c_cur is
select distinct e.grade_id,e.ename,e.sal from sal_inc s
join emp_task e on e.grade_id = s.grade_id order by e.ename ;
v_incr number;
begin
for f_cur in c_cur
loop
v_incr := sal_incr(f_cur.grade_id);
update emp_task set sal = sal + v_incr;
dbms_output.put_line('Emp Name : '||f_cur.ename||','
||' Sal:'||f_cur.sal||','||' Grade: '||f_cur.grade_id);
end loop;
end;

Resources