Difficulty compiling an AFTER INSERT OR UPDATE trigger - oracle

I have an EMPLOYEE table with SALARY field. I'm using Oracle SQL developer. I want to write a trigger so that when someone update salary in EMPLOYEE table, it will update Salary field in EMPLOYEE_SALARIES table as low, medium, high. Here's the second table.
CREATE TABLE Employee_Salaries(
Ssn CHAR(9) NOT NULL,
Salary VARCHAR(10),
Log_Date DATE
);
Here's the trigger and procedure to update the Salary field to low, middle or high.
CREATE OR REPLACE PROCEDURE salaryType(x IN NUMBER, y OUT VARCHAR) IS
BEGIN
IF x >= 60000 THEN y := 'HIGH';
ELSIF (x >= 40000 AND x <= 60000) THEN y := 'MEDIUM';
ELSE y := 'LOW';
END IF;
END salaryType;
/
I get compiler error on this trigger. Please tell me what I did wrong or am I missing something.
CREATE OR REPLACE TRIGGER salary1
AFTER INSERT OR UPDATE ON Employee
FOR EACH ROW
BEGIN
DECLARE
salaryRank VARCHAR(10) := ' ';
salaryType(:new.Salary, salaryRank);
INSERT INTO Employee_Salaries(Ssn, Salary, Log_Date) VALUES (:new.Ssn, salaryRank, SYSDATE);
END;
/

Declaration Part is at wrong place(should be before BEGIN and just after FOR EACH ROW statement of TRIGGER's header), Make it as the following :
CREATE OR REPLACE TRIGGER salary1
AFTER INSERT OR UPDATE ON Employee
FOR EACH ROW
DECLARE
salaryRank VARCHAR(10) := ' ';
BEGIN
salaryType(:new.Salary, salaryRank);
INSERT INTO Employee_Salaries(Ssn, Salary, Log_Date) VALUES (:new.Ssn, salaryRank, SYSDATE);
END;

The keyword BEGIN in the trigger is in the wrong place. It should come after the DEFINE block; that is, after you declare salaryrank and before you invoke the procedure.

Related

Using Procedure in Trigger in PL/SQL

Well I have this Procedure:
SET SERVEROUTPUT ON;
CREATE OR REPLACE PROCEDURE check_salary (job_id employees.job_id%TYPE, salary employees.salary%TYPE)
IS
maxSal NUMBER;
minSal NUMBER;
BEGIN
SELECT MAX(salary) INTO maxSal FROM employees WHERE job_id = job_id;
SELECT MIN(salary) INTO minSal FROM employees WHERE job_id = job_id;
IF maxSal >= salary OR minSal <= salary THEN
RAISE_APPLICATION_ERROR(-20001,'Invalid slary '||salary||'. Salaries for job '||job_id||' must be between '||minSal||' and '||maxSal);
END IF;
END;
This PROCEDURE has two parameters. It checks if the salary of a job_id is between the max and the min of the job.
Now I want to create a TRIGGER.
If INSERT or UPDATE is called on employees I want to execute the prodcedure. But I don't know what I should write as a parameter in the procedure
CREATE OR REPLACE TRIGGER check_salary_trg
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
DECLARE
BEGIN
check_salary(); --Don't know which parameter I should use
END;
You should use :NEW as New Row and :OLD as Old Row.
But I think it is not useful to create a trigger as BEFORE INSERT.
You may create a trigger as AFTER INSERT OR UPDATE.
Here is an example :
CREATE OR REPLACE TRIGGER check_salary_trg
AFTER INSERT OR UPDATE ON EMPLOYEE
REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
DECLARE
BEGIN
check_salary(:NEW.job_id);
EXCEPTION
WHEN OTHERS THEN
-- Consider logging the error and then re-raise
RAISE;
END check_salary_trg;
You just need to add the job_id and the salary
CREATE OR REPLACE TRIGGER check_salary_trg
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
DECLARE
BEGIN
check_salary(:new.job_id, :new.salary);
END;

Why doesn't this PL/SQL trigger show any result?

I need to write a trigger that outputs the first name and last name of employees whose salary have been modified. This trigger will work when the old salary of the employee was less than 10,000 and with the modification, it's bigger than 10,000. This trigger has to be a row trigger
I have written this:
CREATE OR REPLACE TRIGGER print_info
AFTER UPDATE OF salary ON employees
FOR EACH ROW
DECLARE fname employees.first_name%type; lname employees.last_name%type;
BEGIN
IF(:OLD.salary < 10000 AND :NEW.salary > 10000) THEN
dbms_output.put('fname, lname');
END IF;
END;
/
This code compiles and the trigger is created successfully, but when I update an employee's salary, within the trigger requirements, the trigger doesn't execute or doesn't show me anything.
Am I doing something wrong here?
Thanks for your help.
Just add dbms_output.new_line; after the dbms_output.put or use dbms_output.put_line
CREATE OR REPLACE TRIGGER print_info
AFTER UPDATE OF salary ON employees
FOR EACH ROW
DECLARE fname employees.first_name%type; lname employees.last_name%type;
BEGIN
IF(:OLD.salary < 10000 AND :NEW.salary > 10000) THEN
dbms_output.enable;
dbms_output.put('fname, lname '||:NEW.first_name||','||:NEW.last_name);
dbms_output.new_line;
END IF;
END;

PL/SQL Trigger to calculate total attendance

I want to create a trigger to calculate total attendance for each distinct record from the given table.
DAILY_ATT
The second table should generate output like this:
TOTAL_ATT
Here's what I have done:
CREATE TABLE daily_att
(
roll_no NUMBER(5),
subject VARCHAR2(10),
attendance NUMBER(5),
date_att DATE
);
CREATE TABLE total_att
(
roll_no NUMBER(5) NOT NULL PRIMARY KEY,
total_attendance NUMBER(5)
);
INSERT INTO DAILY_ATT VALUES(1, 'MATHS', 0, '04-MAY-18');
INSERT INTO DAILY_ATT VALUES(1, 'ENG', 1, '04-MAY-18');
INSERT INTO DAILY_ATT VALUES(2, 'MATHS', 1, '04-MAY-18');
INSERT INTO DAILY_ATT VALUES(2, 'ENG', 1, '04-MAY-18');
INSERT INTO DAILY_ATT VALUES(1, 'MATHS', 1, '05-MAY-18');
INSERT INTO DAILY_ATT VALUES(1, 'ENG', 1, '05-MAY-18');
INSERT INTO DAILY_ATT VALUES(2, 'MATHS', 0, '05-MAY-18');
INSERT INTO DAILY_ATT VALUES(2, 'ENG', 0, '05-MAY-18');
SELECT * FROM DAILY_ATT;
CREATE OR replace TRIGGER att
AFTER INSERT OR UPDATE ON daily_att
FOR EACH ROW
BEGIN
SELECT SUM(attendance)
INTO Total_att(total_attendance)
FROM daily_att
WHERE roll_no = :NEW.roll_no;
END;
Hope this will serve your purpose
CREATE OR REPLACE TRIGGER create_subtotal
AFTER INSERT OR UPDATE OR DELETE ON DAILY_ATTENDANCE
FOR EACH ROW
DECLARE
V_ROLL_NO NUMBER(8);
V_TOT_ATTENDANCE NUMBER(4);
BEGIN
SELECT ROLL_NO,SUM(ATTENDANCE)
INTO V_ROLL_NO,V_TOT_ATTENDANCE
FROM DAILY_ATTENDANCE
WHERE ROLL_NO = :new.ROLL_NO AND DATE_ATT=TRUNC(SYSDATE);
BEGIN
update TOTAL_ATTENDANCE
set TOTAL_ATTENDANCE= TOT_ATTENDANCE
where ROLL_NO = V_ROLL_NO;
if sql%rowcount = 0 then
-- no rows were updated, so the record does not exist
insert into TOTAL_ATTENDANCE (ROLL_NO,TOTAL_ATTENDANCE )
values ( V_ROLL_NO,V_TOT_ATTENDANCE );
END IF;
END;
END;
I would recommend you to not abuse a Trigger for this purpose. Simply create a view.
CREATE
OR replace VIEW total_att AS
SELECT roll_no
,SUM(attendance) as total_attendance
FROM daily_att
GROUP BY roll_no;
select * FROM total_attendance;
ROLL_NO TOTAL_ATTENDANCE
1 3
2 2
My suggestion would be to use this set of 2 triggers:
The core one:
CREATE OR REPLACE TRIGGER create_subtotal
AFTER UPDATE OF roll_no, attendance OR INSERT OR DELETE ON daily_att
FOR EACH ROW
DECLARE
v_roll_no daily_att.roll_no%TYPE;
v_before daily_att.attendance%TYPE;
v_after daily_att.attendance%TYPE;
v_diff daily_att.attendance%TYPE;
BEGIN
IF UPDATING AND (:NEW.roll_no <> :OLD.roll_no) THEN
RAISE_APPLICATION_ERROR( -20001, 'Altering ROLL_NO is not allowed!' );
END IF;
IF NOT INSERTING THEN
v_before := :OLD.attendance;
v_roll_no := :OLD.roll_no;
ELSE
v_before := 0;
v_roll_no := :NEW.roll_no;
END IF;
IF NOT DELETING THEN
v_after := :NEW.attendance;
ELSE
v_after := 0;
END IF;
v_diff := v_after - v_before;
IF INSERTING OR (v_diff <> 0) THEN
UPDATE total_att
SET total_attendance = total_attendance + v_diff
WHERE roll_no = v_roll_no;
IF SQL%ROWCOUNT = 0 THEN
INSERT INTO total_att (roll_no, total_attendance)
VALUES (v_roll_no, v_diff);
END IF;
END IF;
END;
An auxiliary one:
CREATE OR REPLACE TRIGGER delete_subtotal
AFTER DELETE ON daily_att
BEGIN
DELETE FROM total_att
WHERE NOT EXISTS (SELECT 1
FROM daily_att d
WHERE total_att.roll_no = d.roll_no);
END;
I've assumed that ROLL_NO is never NULL, nor should ever be changed, and that ATTENDANCE is also never NULL. I basically ignored the SUBJECT and DATE_ATT columns, as from the question, they do not seem to impact the goal.
The auxiliary trigger can be dropped, if you do not need to handle deletes from the DAILY_ATT table, or can stand zero entries in TOTAL_ATT table, for things removed from DAILY_ATT completely.
For better performance, the check for ROLL_NO change should be moved to a separate BEFORE UPDATE trigger. The auxiliary trigger will usually benefit much from an index on ROLL_NO in the DAILY_ATT table.
Here's an SQL Fiddle for this.

Mutating table error thrown from stored procedure fired by an after insert trigger

I have a requirement to call a stored procedure via 'after insert' trigger whenever data is inserted into table but I run into "error ORA-04091: table TEST.EMP is mutating, trigger/function may not see it". I understand the reason behind this error but how can I overcome this through Compound Triggers without disturbing the procedure?
create TABLE emp(
id NUMBER(4),
emp_name VARCHAR2(30),
dept_name VARCHAR2(10));
create or replace PROCEDURE emp_count(dept_name_v emp.dept_name%TYPE) as
DECLARE
dept_emp_count NUMBER(4) := 0;
BEGIN
SELECT count(*) INTO dept_emp_count FROM emp WHERE dept_name = dept_name_v;
UPDATE dept_stat SET d_emp_count = dept_emp_count WHERE dept_name = dept_name_v;
END;
create or replace TRIGGER dept
AFTER INSERT ON emp
FOR EACH ROW
BEGIN
emp_count(:NEW.dept_name);
END;
There is an example in documentation how to create a compound trigger: http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/triggers.htm#CHDFEBFJ
Just change a few identifiers and declarations in this example, and you will get a trigger for your case:
CREATE OR REPLACE TRIGGER some_trigger
FOR INSERT ON emp
COMPOUND TRIGGER
TYPE dept_names_t IS TABLE OF emp.dept_name%TYPE INDEX BY SIMPLE_INTEGER;
dept_names dept_names_t;
idx SIMPLE_INTEGER := 0;
-- AFTER EACH ROW Section:
AFTER EACH ROW IS
BEGIN
idx := idx + 1;
dept_names(idx) := :NEW.dept_name;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR j IN 1..idx
LOOP
emp_count(dept_names(j));
END LOOP;
END AFTER STATEMENT;
END;
/

can any solve what is wrong with this please?

create table account_type
(
acct_type number(3) primary key,
acct_desc Varchar2(30) not null CHECK (acct_desc IN('savings','salary','current','credit')),
acct_wd_limit number(10)
);
create sequence acct_seq;
CREATE OR REPLACE TRIGGER acct_pk
BEFORE INSERT ON account_type
FOR EACH ROW
WHEN (new.acct_type IS NULL)
BEGIN
SELECT acct_seq.NEXTVAL
INTO :new.acct_type
FROM account_type;
END;
i am getting an error on the line before insert on account_type. no idea why
I also tried doing
CREATE OR REPLACE TRIGGER acct_pk
BEFORE INSERT ON account_type
FOR EACH ROW
WHEN (new.id IS NULL)
BEGIN
SELECT acct_seq.NEXTVAL
INTO :new.id
FROM account_type;
END;
Even doing this is giving me an error
create sequence acct_pk
start with 1
increment by 1
max value 999
min value 1
no cycle;
Thanks
Since the author tagged the question with oracle10g, she can't use sequence_name.nextVal in PL/SQL. Solution:
CREATE OR REPLACE TRIGGER acct_pk
BEFORE INSERT ON account_type
FOR EACH ROW
DECLARE
v_acct_type NUMBER;
BEGIN
IF :new.acct_type IS NULL THEN
SELECT acct_seq.NEXTVAL
INTO v_acct_type
FROM dual;
:new.acct_type := v_acct_type;
END IF;
END;

Resources