Can somebody help me with these trigger errors in PL/SQL? - sql

I have these tables:
Person (id_pers, name, phone, address, birthdate)
Book (book_id, title, nr_of_pages, copies, genre)
Loan(book_id, loan_id, dateL, dateR, nr_of_days)
birthdate is of datatype DATE;
loan_id takes value from the id_pers;
dateL - loan date;
dateR - return date;
nr_of_days represents the return term (how many days the loan may take);
Define a trigger for:
Ensure that when adding a loan, the nr_of_days is proportionate to
nr_of_pages taking into account the age of the person making the loan. Between 18-25 years,
30 pages/day; between 26-65 years, 20 pages/day; over 65 years, 40 pages/day.
I tried using this trigger but it compiles with errors
create or replace TRIGGER Update_Days
BEFORE INSERT ON Loan
FOR EACH ROW
BEGIN
DECLARE age INT;
SELECT DATEDIFF(YEAR, birthdate, CURRENT_DATE) INTO age
FROM Person
WHERE id_pers = NEW.loan_id;
IF age BETWEEN 18 AND 25 THEN
SET NEW.nr_of_days = NEW.nr_of_pages / 30;
ELSEIF age BETWEEN 26 AND 65 THEN
SET NEW.nr_of_days = NEW.nr_of_pages / 20;
ELSE
SET NEW.nr_of_days = NEW.nr_of_pages / 40;
END IF;
END;
errors i get

SET is not used in PL/SQL
In PL/SQL, the assignment operator is := and not =
The NEW record is referenced using bind variable syntax of :NEW
DATEDIFF is not a valid function in Oracle.
The DECLARE section goes before BEGIN.
CREATE OR REPLACE TRIGGER Update_Days
BEFORE INSERT ON loan
FOR EACH ROW
DECLARE
age INT;
BEGIN
SELECT TRUNC(MONTHS_BETWEEN(CURRENT_DATE, birthdate)/12)
INTO age
FROM Person
WHERE id_pers = :NEW.loan_id;
:NEW.nr_of_days := :NEW.nr_of_pages / CASE
WHEN age BETWEEN 18 AND 25 THEN 30
WHEN age BETWEEN 26 AND 65 THEN 20
ELSE 40
END;
END;
/
fiddle

Related

Trigger to update age when inserted age < 0

I am trying to set age = 0 if the age is inserted as negative. I don't know what is wrong with the code:
CREATE TRIGGER verify_age
BEFORE INSERT ON customers
FOR EACH ROW
WHEN (NEW.age < 0)
BEGIN
UPDATE customers
SET age = 0
END;
The syntactical error in your code is that it is missing a ; before END, but even if you correct this it would not solve your problem.
You need a WHERE clause in the UPDATE statement so that you update only the new row and not the whole table.
The condition in the WHERE clause will check for the rowid of the new row, but this rowid exists only after the row is inserted.
So, you must change the trigger to an AFTER INSERT trigger:
CREATE TRIGGER verify_age AFTER INSERT ON customers
WHEN NEW.age < 0
BEGIN
UPDATE customers
SET age = 0
WHERE rowid = NEW.rowid;
END;
See the demo.
Try this:
CREATE OR REPLACE TRIGGER validate_age
BEFORE INSERT
ON customers
FOR EACH ROW
BEGIN
IF :new.age < 0
THEN
:new.age := 0;
END IF;
END;
or :
CREATE TRIGGER validate_age
BEFORE INSERT ON customers
BEGIN
SELECT
CASE
WHEN NEW.age < 0 THEN
NEW.age = 0
END;
END;

WITH clause with FUNCTION AND PROCEDURE, where is the mistake?

I have table 'Studies' (with columns: student_id, name, surname, course_name, mark) and I have a task to write a PL/SQL program, where will be WITH word + FUNCTION word + PROCEDURE word.I decided to make such a program: the function will calculate the average mark for some course (input parameter) and the procedure will display information about students whose mark in this course is higher than the average. I managed to create a function that returns the average score,
create or replace FUNCTION average_mark(co_name IN VARCHAR2) RETURN REAL IS
iter NUMBER := 0;
aver NUMBER := 0;
CURSOR c1
IS
SELECT mark
FROM studies
WHERE course_name = co_name;
BEGIN
FOR student IN c1
LOOP
iter := iter + 1;
aver := aver + student.mark;
END LOOP;
RETURN ROUND((aver/iter),2);
END above_average_mark;
and procedure which displays information about a student whose mark in the course is more than a certain one, how now to connect the procedure and the function and the WITH word?
CREATE OR REPLACE PROCEDURE above(co_name IN VARCHAR2) IS
CURSOR c2
IS
SELECT *
FROM studies
WHERE course_name = co_name;
BEGIN
FOR student IN c2
LOOP
IF (student.mark > 4) THEN
DBMS_OUTPUT.PUT_LINE('name: ' || student.student_name || ', mark: ' || student.mark);
END IF;
END LOOP;
END;
i need something like this:
WITH
PROCEDURE
FUNCTION
I don't have your tables to illustrate it, so I'll use Scott's sample schema to calculate average salaries for departments.
As you said that you need something like WITH PROCEDURE FUNCTION, the only thing you have to do is to follow syntax.
Therefore, here you are: with factoring clause in this example contains a procedure which displays department name and average salary; function calls the procedure (and passes department number and average salary it calculated). Also, as any other function it actually returns a value.
SQL> set serveroutput on;
SQL> with
2 procedure p_deptno (par_deptno in dept.deptno%type,
3 par_avgsal in number)
4 is
5 l_dname dept.dname%type;
6 begin
7 select dname into l_dname
8 from dept
9 where deptno = par_deptno;
10 dbms_output.put_line('Average salary for department ' || l_dname ||
11 ' = ' || par_avgsal);
12 end p_deptno;
13
14 function f_avgsal (par_deptno in dept.deptno%type)
15 return number
16 is
17 l_avgsal number;
18 begin
19 select round(avg(e.sal)) into l_avgsal
20 from emp e
21 where e.deptno = par_deptno;
22
23 p_deptno (par_deptno, l_avgsal);
24
25 return l_avgsal;
26 end f_avgsal;
27 select f_avgsal (a.deptno) avg_sal
28 from dept a;
29 /
Result:
AVG_SAL
----------
2917
2175
1567
Average salary for department ACCOUNTING = 2917
Average salary for department RESEARCH = 2175
Average salary for department SALES = 1567
Average salary for department OPERATIONS =
SQL>
Now, adjust it to your tables & data.

ORACLE SQL PROCEDURE WITH FOR LOOP AND IF ELSE ERROR

I need to replace employee id in shift table for a particular day with another employee id who hasn't any shift for a particular day.
This my tables,
CREATE TABLE EMPLOYEE
(EMPLOYEE_ID NUMBER(4) NOT NULL,
NAME VARCHAR(50),
GENDER VARCHAR(10),
BIRTH_DAY DATE,
PRIMARY KEY(EMPLOYEE_ID)
);
CREATE TABLE SHIFT
(SHIFT_ID NUMBER(4),
SHIFT_DATE DATE,
CUSTOMER_ID NUMBER(4),
SERVICE_ID NUMBER(4),
EMPLOYEE_ID NUMBER(4),
SHIFT_CHARGE FLOAT(10),
PRIMARY KEY(SHIFT_ID),
FOREIGN KEY (EMPLOYEE_ID) REFERENCES EMPLOYEE(EMPLOYEE_ID)
);
For the above purpose, I want to create a procedure. I created the procedure is below. But there is a compilation error.
CREATE OR REPLACE PROCEDURE CHANGE_SHIFT_SHEDULED
(
EMPLOYEE_ID IN SHIFT.EMPLOYEE_ID%TYPE,
SHIFT_DATE IN SHIFT.SHIFT_DATE%TYPE
)
AS
CHECK_CON BOOLEAN:=TRUE;
BEGIN
FOR SICK_EMP_SHIFT_REC IN (SELECT * FROM SHIFT WHERE SHIFT.EMPLOYEE_ID=EMPLOYEE_ID AND SHIFT.SHIFT_DATE=SHIFT_DATE)
LOOP
FOR EMP_REC IN (
SELECT e.EMPLOYEE_ID,COUNT(e.EMPLOYEE_ID)AS SHIFT_COUNT,
CASE
WHEN SHIFT_COUNT>0 THEN FALSE
ELSE TRUE
END AS SHIFT_COUNT
FROM EMPLOYEE e LEFT OUTER JOIN SHIFT s ON e.EMPLOYEE_ID=s.EMPLOYEE_ID
WHERE s.SHIFT_DATE=SHIFT_DATE
GROUP BY e.EMPLOYEE_ID)
LOOP
CHECK_CON:=EMP_REC.SHIFT_COUNT;
IF CHECK_CON THEN
UPDATE SHIFT s SET s.EMPLOYEE_ID=EMP_REC.EMPLOYEE_ID WHERE s.SHIFT_ID=SICK_EMP_SHIFT_REC.SHIFT_ID;
EXIT;
END IF;
END LOOP;
END LOOP;
END;
/
Please anyone help me to solve this issue.
I'm not looking at logic you applied; I hope you did it right. I just fixed errors which prevented code you wrote to compile; for example:
don't name parameters as column names; use some prefix, e.g. par_ or p_
in cursor FOR loop, you can't have two columns with same name (shift_count); one has to be changed
also, you can't use an alias in the same statement's CASE (which is what you did with shift_count)
Boolean support is somewhat strange in Oracle. For example, there's no such datatype at SQL level. Therefore, you'll have to work with strings or numbers, and then "convert" them to Boolean (e.g. line #26)
The rest is OK, I suppose.
SQL> CREATE OR REPLACE PROCEDURE change_shift_sheduled (
2 par_employee_id IN shift.employee_id%TYPE,
3 par_shift_date IN shift.shift_date%TYPE)
4 AS
5 check_con BOOLEAN := TRUE;
6 BEGIN
7 FOR sick_emp_shift_rec IN (SELECT *
8 FROM shift
9 WHERE shift.employee_id = par_employee_id
10 AND shift.shift_date = par_shift_date)
11 LOOP
12 FOR emp_rec
13 IN ( SELECT e.employee_id,
14 COUNT (e.employee_id) AS shift_count,
15 CASE
16 WHEN COUNT (e.employee_id) > 0 THEN 'FALSE'
17 ELSE 'TRUE'
18 END
19 AS shift_count_case
20 FROM employee e
21 LEFT OUTER JOIN shift s ON e.employee_id = s.employee_id
22 WHERE s.shift_date = par_shift_date
23 GROUP BY e.employee_id)
24 LOOP
25 check_con :=
26 CASE
27 WHEN emp_rec.shift_count_case = 'FALSE' THEN FALSE
28 ELSE TRUE
29 END;
30
31 IF check_con
32 THEN
33 UPDATE shift s
34 SET s.employee_id = emp_rec.employee_id
35 WHERE s.shift_id = sick_emp_shift_rec.shift_id;
36
37 EXIT;
38 END IF;
39 END LOOP;
40 END LOOP;
41 END;
42 /
Procedure created.
SQL>

SQL Trigger Update is ALWAYS null

We are trying to make a trigger to calculate the TOTAL_COST of a Car Rental based on the number of days it has been rented. The total cost for the rental is calculated based on the number of days and the cost of the vehicle. An additional tax of 12% is added to the total. If the rental is longer than 10 days a discount of 15% is subtracted from the total cost.
This Is Our Trigger:
create or replace trigger L5_Q8
After Update on E2_RESERVATIONS
for each row
Declare
TOTALDAYS NUMBER(4);
TotalCostBeforeTax Number(8);
BEGIN
TOTALDAYS := (trunc(:NEW.END_DATE) - TRUNC(:NEW.START_DATE)) + 1;
IF(TOTALDAYS > 10) THEN
SELECT V.COST_PER_DAY * TOTALDAYS * 0.85
INTO TotalCostBeforeTax
from E2_Reservations R
join E2_Vehicle V on R.V_ID = V.V_ID;
END IF;
IF(TOTALDAYS <= 10) THEN
SELECT V.COST_PER_DAY * TOTALDAYS
INTO TotalCostBeforeTax
from E2_Reservations R
join E2_Vehicle V on R.V_ID = V.V_ID;
END IF;
TotalCostBeforeTax := TotalCostBeforeTax * 1.12;
UPDATE E2_RESERVATIONS SET TOTAL_COST = 10.1 WHERE ROWID IN (SELECT MAX(ROWID) FROM E2_RESERVATIONS);
END;
These are Our Tables:
E2_RESERVATIONS:
E2_VEHICLE:
You're trying to calculate the total cost for the current record, right? So do the calculation before the change gets applied, not in an after update trigger. Also, there's way too much SQL in that trigger.
Try this:
create or replace trigger l5_q8
before update on e2_reservations
for each row
declare
totaldays number(4);
totalcostbeforetax number(8,2);
begin
-- length of reservation
totaldays := (trunc(:new.end_date) - trunc(:new.start_date)) + 1;
-- base cost of hire
select v.cost_per_day * totaldays
into totalcostbeforetax
from e2_vehicle v
where v.v_id = :new.v_id;
-- apply discount for long reservation
if(totaldays > 10) then
totalcostbeforetax := totalcostbeforetax * 0.85;
end if;
-- apply tax to total cost
:new.total_cost := totalcostbeforetax * 1.12;
end;
Note that is a BEFORE UPDATE trigger, not AFTER UPDATE.
Also, the variable totalcostbeforetax needs to handle decimals, because you're multiplying by 0.85, and anyway should match the declaration of the column total_cost.
"Wouldn't you need to join the vehicle table with reservations?"
A trigger has access to all the columns of the trigger it is built on. So you're already joined to the RESERVATIONS table, by dint of where v.v_id = :new.v_id, which is the vehicle ID of the current reservations record.

TRIGGER AND COUNT to limit the Count value

i want to create a trigger that will
select staffid where dateread = this months date
to count the number of a staffID
if the count for that staff during that month is more than 5
a stop will be issued.
here is what i did that screws me up
I would like to know is this logically correct?
and here are my compiler log errors
This is my required outcome:
Meter readers can only read a maximum of 5 meters in any given calendar month
My Reading Table has
StaffID
MeterID
DateRead
ReadinID (PK)
Here is the error text:
Error(5,7): PL/SQL: SQL Statement ignored Error(5,27):
PL/SQL:ORA-00923: FROM keyword not found where expected
C:\Users\ChrisPin\AppData\Roaming\SQL Developer\assgn2 sat4.sql Error(5,7):
PL/SQL: SQL Statement ignored Error(5,27):
PL/SQL: ORA-00923: FROM keyword not found where expected
Here is the trigger code:
CREATE OR REPLACE TRIGGER LIMIT_5_REDINGS
BEFORE UPDATE OR INSERT ON reading
FOR EACH ROW
DECLARE
ReadingCount INTEGER; --# of depts for this employee
max_read INTEGER := 5; --max number of depts per employee.
BEGIN
select Reading COUNT(*) into ReadingCount
from (select *
from Reading
where to_char(DateRead, 'YYYY-MM') = to_char(sysdate, 'YYYY-MM'))
WHERE STAFFID = :NEW.STAFFID;
IF :OLD.STAFFID = :NEW.STAFFID THEN
RETURN;
ELSE
IF ReadingCount >= max_read THEN
RAISE_APPLICATION_ERROR (-20000,'Employees are limited to a max of two departments.');
END IF;
END IF;
END;
It's in this line
select Reading COUNT(*) into ReadingCount
should be
select COUNT(*) into ReadingCount