I am in the process of trying to create a trigger that when a record is inserted into the employee table(a new employee), the trigger fires and inserts a record into the employee_dept_history table as 'N/A' (since this employee is new and has no previous department). Also, if a current employee switches departments the trigger should insert a record in the employee_dept_history table and the employees depatment_id changes. I am having problems creating this trigger.I was wondering if someone could lead me to the right direction with my code. I can't get the below trigger to display 'N/A'. How could I go about making this trigger work? Do I need to create local variables for the new and old department?
CREATE OR REPLACE TRIGGER employee_dept_trigger
BEFORE INSERT OR UPDATE ON employee
FOR EACH ROW
BEGIN
INSERT INTO employee_dept_history
VALUES(:NEW.employee_id,
:NEW.employee_name,
from_department_name (NEEDS TO OUTPUT 'N/A'),
to_department_name,
sysdate);
END employee_dept_trigger;
The EMPLOYEE_DEPT_HISTORY table looks like:
CREATE TABLE empployee_dept_history
(
EMPLOYEE_ID NUMBER(4)
EMPLOYEE_NAME VARCHAR2(50)
FROM_DEPARTMENT_NAME VARCHAR2(50)
TO_DEPARTMENT_NAME VARCHAR2(50)
OPERATION_TIME DATE
);
The EMPLOYEE table:
(
EMPLOYEE_ID NUMBER(4)
EMPLOYEE_NAME VARCHAR2(20)
JOB VARCHAR2(50)
MANAGER_ID NUMBER(4)
HIRE_DATE DATE
SALARY NUMBER(9)
COMMISION NUMBER(9)
DEPARTMENT_ID NUMBER(4)
);
Note: This answer is similar to the other two, but I'll post it anyway because it defines the trigger differently in a way that may be useful. Please note that I've up-voted the other two answers as they'll do the job nicely.
First, if you're only interested in changes to the DEPARTMENT_ID column, you can specify that in your trigger definition:
CREATE OR REPLACE TRIGGER employee_dept_trigger
BEFORE INSERT OR UPDATE OF department_id ON employee
FOR EACH ROW
....
A trigger can use the "automatic" logical values INSERTING and UPDATING to determine if an insert or an update called it. You can use the INSERTING value to indicate that this is a new employee:
IF INSERTING THEN
-- set old department name = 'N/A'
ELSE
-- set old department name = whatever
END IF;
Your question doesn't describe where the department name comes from, so this example descends into pseudocode when it comes to the department names:
CREATE OR REPLACE TRIGGER employee_dept_trigger
BEFORE INSERT OR UPDATE OF department_id ON employee
FOR EACH ROW
BEGIN
IF INSERTING THEN
-- New employee
INSERT INTO employee_dept_history VALUES (
:NEW.employee_id,
:NEW.employee_name,
'N/A',
(SELECT department_name from ...), <-- based on :NEW.department_id
SYSDATE);
ELSE
-- Existing employee
INSERT INTO employee_dept_history VALUES (
:NEW.employee_id,
:NEW.employee_name,
(SELECT department_name from ...), <-- based on :OLD.department_id
(SELECT department_name from ...), <-- based on :NEW.department_id
SYSDATE);
END IF;
END employee_dept_trigger;
It's either
CREATE OR REPLACE TRIGGER employee_dept_trigger
BEFORE INSERT OR UPDATE ON employee
FOR EACH ROW
DECLARE
from_department_name varchar(255);
to_department_name varchar(255);
BEGIN
select from DEPARTMENT where DEPARTMENT_ID=:NEW.DEPARTMENT_ID into to_department_name;
IF INSERTING THEN
INSERT INTO employee_dept_history
VALUES(:NEW.employee_id,
:NEW.employee_name,
'N/A',
to_department_name,
sysdate);
end if;
IF UPDATING THEN
select from DEPARTMENT where DEPARTMENT_ID=:OLD.DEPARTMENT_ID into from_department_name;
INSERT INTO employee_dept_history
VALUES(:NEW.employee_id,
:NEW.employee_name,
from_department_name ,
to_department_name,
sysdate);
end if;
END employee_dept_trig;
or I don't understand your question.
You probably want something like this:
CREATE OR REPLACE TRIGGER employee_dept_trigger
BEFORE INSERT OR UPDATE ON employee
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO employee_dept_history
VALUES(:NEW.employee_id,
:NEW.employee_name,
'N/A',
:NEW.DEPARTMENT_ID,
sysdate);
ELSE
INSERT INTO employee_dept_history
VALUES(:NEW.employee_id,
:NEW.employee_name,
:OLD.DEPARTMENT_ID,
:NEW.DEPARTMENT_ID,
sysdate);
END IF;
END employee_dept_trigger;
It won't be quite right yet though as you haven't indicated where the department name comes from. I've used department id just to give you an idea..
Related
Evening, needing assistance regarding triggers.
Within my development environment I have two tables, one containing employee data (which contains various data errors that will be amended via ALTER TABLE) and the log table.
How do i go about designing a trigger that will update multiple rows contained within the log table such as 'issue_status','status_update_date' when ALTER TABLE sql is used to amend the data contained in the first data table?
-- employee table
CREATE TABLE emp(
emp_id INTEGER NOT NULL,
emp_name VARCHAR(30),
emp_postcode VARCHAR(20),
emp_registered DATE,
CONSTRAINT pk_emp PRIMARY KEY (emp_id));
-- SQL for the log table
CREATE TABLE data_log
(issue_id NUMBER(2) NOT NULL,
table_name VARCHAR2(20),
row_name VARCHAR2(20),
data_error_code NUMBER(2),
issue_desc VARCHAR2(50),
issue_date DATE,
issue_status VARCHAR2(20),
status_update_date DATE);
-- example log insert
INSERT INTO data_log( SELECT DI_SEQ.nextval, 'emp', emp.emp_id, '1', 'emp_name case size not UPPER', SYSDATE, 'un-fixed', '' FROM emp WHERE emp_name != UPPER(emp_name));
This is the example of the issue inserted into the log table. All i want to do is if I update the emp table to set 'emp_name' to Upper the trigger will register this update and change the rows 'issue_status' and 'status_update_date' to 'fixed' and the 'sysdate' of when the change was made
I've done some browsing however i'm still struggling to understand, any literature recommendations would be appreciated also.
Thanks in advance for the assistance.
Note that we don't use ALTER TABLE to update the rows of a table as you have mentioned.We use Update statement. Your trigger should be a BEFORE UPDATE TRIGGER like this.
CREATE OR REPLACE TRIGGER trig_emp_upd BEFORE
UPDATE ON emp
FOR EACH ROW
WHEN ( new.emp_name = upper(old.emp_name) )
BEGIN
UPDATE data_log
SET
issue_status = 'fixed',
status_update_date = SYSDATE
WHERE
row_name =:new.emp_id;
END;
/
Using oracle 12c, I have a table for employee and a table for managers, if the newly inserted employee salary >=5000 then he/she is considered manager. So I'd like to create trigger on table employee that checks if the salary of the newly inserted employee >=5000 this row should be duplicated in the manager table. IS this possible? If yes, could you simply give me the right syntax.
Some general words first: This could be considered bad database design. If you consider an employee beyond a certain salary a manager, this almost screams for a column in the same table, either physical or virtual. For example, it could look like this:
CREATE TABLE employees (
id NUMBER,
first_name VARCHAR2(10),
last_name VARCHAR2(10),
salary NUMBER(9,2),
is_manager as (case when salary >= 5000 then 1 else 0 end)
CONSTRAINT employees_pk PRIMARY KEY (id)
);
If you still want to use a trigger and a second managers table, it could work like this:
CREATE OR REPLACE TRIGGER trig_emp_insert
AFTER INSERT
ON employees
FOR EACH ROW
BEGIN
if (:new.salary >= 5000) then
insert into managers (...) values (...)
end if;
END;
Instructions:
Create two tables, named employees and departments. Preface the table names with your initials. Link the two tables (foreign key) by a column called dept. Make up a few column names for each table.
Employees Table:
create table bsemployees(
dept number primary key,
empName varchar2(20),
salary number
);
Departments Table:
create table bsdepartments(
dept number references bsemployees(dept),
deptName varchar2(20)
);
Write the following stored procedures:
• Insert a row into the employees table. If the department does not exist. Insert it into the departments table.
create or replace procedure sp_employees(
a_dept IN number,
a_empName IN varchar2,
a_salary IN number
)
as
vCount number;
BEGIN
sp_check_dept(a_dept,vCount);
insert into bsemployees values(a_dept, a_empName, a_salary);
if vCount = 0 then
dbms_output.put_line('**DEPT DOES NOT EXIST**');
insert into bsdepartments (dept, deptName) values(a_dept, NULL);
end if;
END;
/
create or replace procedure sp_check_dept(
a_dept IN number,
vCount OUT number
)
as
BEGIN
select count(*)
into vCount
from bsdepartments
where dept = a_dept;
end;
/
• Insert a row into the departments table.
create or replace procedure sp_departments(
a_dept IN number,
a_deptName IN varchar2
)
as
BEGIN
insert into bsdepartments values(a_dept, a_deptName);
END;
/
I've got it pretty much all down for this assignment except for the fact that when I try to insert a row into the departments table I am getting a integrity constraint - parent key not found error.
If I do execute sp_employees(5, 'John Doe', 90000); It will display ***DEPT DOES NOT EXIST*** and will go ahead and insert the data into bsemployees and insert the dept# into bsdepartments and the deptName will be left blank based on my if-then statement. Doing a select(*) shows me this.
However if I go ahead and do execute sp_departments(1, 'human resources'); to place a row into departments I get the parent key error. I understand that I am trying to insert something that has no parent key but I do not know how to fix it.
Your table design isn't quite correct - the dept primary key needs to be added as a foreign key to employee (not as the primary key), and employee should have its own primary key:
create table bsdepartments(
dept number primary key,
deptName varchar2(20)
);
create table bsemployees(
empName varchar2(20) primary key,
dept number references bsdepartments(dept),
salary number
);
You can then do the 'add if not exists' logic in the check_dept proc:
create or replace procedure sp_check_dept(
a_dept IN number
)
as
vCount number
BEGIN
select count(*)
into vCount
from bsdepartments
where dept = a_dept;
if (vCount = 0) then
dbms_output.put_line('**DEPT DOES NOT EXIST**');
insert into bsdepartments (dept, deptName) values(a_dept, NULL);
end if;
end;
Which then simplifies the employee insertion proc as it should be guaranteed of a department:
create or replace procedure sp_insertEmployee(
a_dept IN number,
a_empName IN varchar2,
a_salary IN number
)
as
BEGIN
sp_check_dept(a_dept);
insert into bsemployees values(a_dept, a_empName, a_salary);
END
Notes
Recommend that you name the procs in alignment with their purpose, e.g. insertEmployee vs just employees
As you've noted, the problem with the 'add if not exists' approach is that you do not have sufficient data to completely populate the department table, hence the null column (but this is what your lecturer asked for)
Your realation to table department is bad. It should linked as below
create table bsdepartments(
dept number primary key,
deptName varchar2(20)
);
and it should be linked to employee table
create table bsemployees(
dept number references bsdepartments(dept),
empName varchar2(20),
salary number
);
Then if you try inserting execute sp_departments(1, 'human resources'); it will execute
an then you have to insert the employee.
Here the employee is related to department not the department is related to employee.
CREATE TABLE "EMPLOYEE_BP"
( "EMP_ID" VARCHAR2(10) NOT NULL ENABLE,
"FNAME" VARCHAR2(20),
"LNAME" VARCHAR2(20),
"JOB_ROLE" VARCHAR2(20),
"AIRPORT_CODE" VARCHAR2(10) NOT NULL ENABLE,
"SALARY" NUMBER(9,0),
"MOBILE" NUMBER(10,0)
);
CREATE or REPLACE TRIGGER emp_after_insert AFTER INSERT ON EMPLOYEE
FOR EACH ROW
DECLARE
BEGIN
INSERT INTO EMPLOYEE_BP values (:NEW.EMP_ID, :NEW.FNAME, :NEW.LNAME, :NEW.JOB_ROLE, : NEW.AIRPORT_CODE, : NEW.SALARY, : NEW.MOBILE);
DBMS_OUTPUT.PUT_LINE('Record successfully inserted into emp_backup table');
END;
--> Apparently constraints are not inserted into backup tables
The table gets created, but it gives me an error for the trigger on line 4,where the begin statement is. Ther error is error at line 4 statement ignored. The synthax seems ok and I'm confident it's a small error but I can't figure it out. I am using Oracle.
Thanks in advance.
Running the trigger as you have it, I actually get an internal Oracle error, which is not good. But I think the problem is with the spaces you have between the : and the NEW.
This works for me:
SQL> CREATE or REPLACE TRIGGER emp_after_insert AFTER INSERT ON EMPLOYEE
2 FOR EACH ROW
3 DECLARE
4 BEGIN
5 INSERT INTO EMPLOYEE_BP values (:NEW.EMP_ID, :NEW.FNAME, :NEW.LNAME, :NEW.JOB_ROLE, :NEW.AIRPORT_CODE, :NEW.SALARY, :NEW.MOBILE);
6 DBMS_OUTPUT.PUT_LINE('Record successfully inserted into emp_backup table');
7 END;
8 /
Trigger created.
I'm not an Oracle expert but you would either need to strike your DECLARE line or actually declare something based on this example
CREATE or REPLACE TRIGGER emp_after_insert AFTER INSERT ON EMPLOYEE
FOR EACH ROW
DECLARE
unused varchar2(10);
BEGIN
INSERT INTO EMPLOYEE_BP values (:NEW.EMP_ID, :NEW.FNAME, :NEW.LNAME, :NEW.JOB_ROLE, :NEW.AIRPORT_CODE, :NEW.SALARY, :NEW.MOBILE);
DBMS_OUTPUT.PUT_LINE('Record successfully inserted into emp_backup table');
END;
First and foremost, this is for an assignment, so most of the really cool pre-written functions you'll want to suggest, I will not be allowed to use.
I have a couple tables that all have fields
creation_date
created_by
last_update_date
last_updated_by
I need to write a trigger that fills these in for creation or updating. The problem is, these tables have null values that are problematic to me. Example:
CREATE TABLE parts
(
pno NUMBER,
pname VARCHAR2(50) NOT NULL,
qoh NUMBER NOT NULL,
price NUMBER(5,2),
reorder_level NUMBER(2),
creation_date DATE NOT NULL,
created_by VARCHAR2(10) NOT NULL,
last_update_date DATE NOT NULL,
last_updated_by VARCHAR2(10) NOT NULL,
CONSTRAINT parts_PK PRIMARY KEY (pno))
These NOT NULL's are given to me and I'm not allowed to change them. So I'm having trouble conceptualizing this.
If my trigger adds these values before the field is created, I can't do an INSERT INTO with those fields blank because they're NOT NULL.
If my trigger adds these values after the field is created, I get compilation errors ORA-00903 and 00922. Invalid table name and invalid option.
I was thinking my trigger would look like
CREATE OR REPLACE TRIGGER pcreate
BEFORE UPDATE on parts
FOR EACH ROW
BEGIN
UPDATE
SET creation_date = SYSDATE;
SET created_by = USER;
SET last_update_date = SYSDATE;
SET last_updated_by = USER;
END;
/
CREATE OR REPLACE TRIGGER pchange
BEFORE UPDATE on parts
FOR EACH ROW
BEGIN
UPDATE
SET last_update_date = SYSDATE;
SET last_updated_by = USER;
END;
/
repeat for the other tables
I might be allowed to use UPSERT but I don't really know how that works.. Any suggestions are welcome. I'm really in this to learn so any other advice is appreciated.
EDIT:
My package that will not acknowledge the trigger is as follows. Do I need to call the trigger inside the package?
CREATE OR REPLACE PACKAGE process_orders
AS
PROCEDURE add_order (p_cno IN NUMBER, p_eno IN NUMBER, p_received IN DATE);
PROCEDURE add_order_details (p_ono IN NUMBER, p_pno IN NUMBER, p_qty IN NUMBER);
PROCEDURE ship_order (p_ono IN NUMBER, p_sdate IN DATE);
PROCEDURE delete_order (p_ono IN NUMBER);
FUNCTION total_emp_sales (f_eno IN NUMBER) RETURN NUMBER;
END process_orders;
/
CREATE OR REPLACE PACKAGE BODY process_orders
AS
PROCEDURE add_order (p_cno IN NUMBER, p_eno IN NUMBER, p_received IN DATE)
IS
ao_emsg VARCHAR2(100);
p_rec_today DATE;
BEGIN
SELECT SYSDATE INTO p_rec_today FROM dual;
IF p_received is null THEN
INSERT INTO orders (ono, cno, eno, received)
VALUES(order_number_seq.NEXTVAL,p_cno,p_eno,p_rec_today);
ELSE
INSERT INTO orders (ono, cno, eno, received)
VALUES(order_number_seq.NEXTVAL,p_cno,p_eno,p_received);
END IF;
EXCEPTION
WHEN OTHERS THEN
ao_emsg := substr(SQLERRM,1,100);
INSERT INTO orders_errors (ono,transaction_date,message)
VALUES(order_number_seq.CURRVAL,SYSDATE,ao_emsg);
END;
--Etc. Procedures
END;
/
In your trigger, you simply want to modify the :new record. Your triggers would look something like
CREATE OR REPLACE TRIGGER parts_before_insert
BEFORE INSERT on parts
FOR EACH ROW
BEGIN
:new.creation_date := SYSDATE;
:new.created_by := USER;
:new.last_update_date := SYSDATE;
:new.last_updated_by := USER;
END;
and
CREATE OR REPLACE TRIGGER parts_before_update
BEFORE UPDATE on parts
FOR EACH ROW
BEGIN
:new.last_update_date := SYSDATE;
:new.last_updated_by := USER;
END;
In your INSERT statement, you would omit these four columns and let the trigger fill in the values. For example (obviously, your primary keys would be coming from something like a sequence rather than being hard-coded)
SQL> insert into parts( pno, pname, qoh, price, reorder_level )
2 values( 1, 'Widget', 10, 100, 75 );
1 row created.
SQL> select *
2 from parts;
PNO PNAME QOH
---------- -------------------------------------------------- ----------
PRICE REORDER_LEVEL CREATION_ CREATED_BY LAST_UPDA LAST_UPDAT
---------- ------------- --------- ---------- --------- ----------
1 Widget 10
100 75 26-SEP-12 SCOTT 26-SEP-12 SCOTT
There is no need of executing UPDATE statement in triggers in your case. Just assign new values to the columns using :new.
CREATE OR REPLACE TRIGGER pchange
BEFORE UPDATE on parts
FOR EACH ROW
BEGIN
:new.last_update_date = SYSDATE;
:new.last_updated_by = USER;
END;