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.
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;
/
This is the code I found on a course
create type employee_type AS OBJECT
( empno number(4),
ename varchar2(40),
dept_ref REF department_type)
/
create table employee of employee_type
( empno PRIMARY KEY )
/
create type department_type AS OBJECT
( deptno number(2),
dname varchar2(20),
loc varchar(20))
/
create table employee of employee_type
( empno PRIMARY KEY )
/
create table department of department_type
( deptno PRIMARY KEY )
/
alter table employee
add (scope for (dept_ref) is department)
/
I don't understand why we need to alter employee table to add scope for to department table, I mean dept_ref has been referred to department_type already and department table is consisted of department_type object, what is the add scope statment do?
As described in the documentation, the purpose is to say that dept_ref in employee has to refer, specifically, to a department, and not to any other table that may be of department_type type.
See REF Columns: Examples:
The dept column can store references to objects of dept_t stored in any table. If you would like to restrict the references to point only to objects stored in the departments table, then you could do so by adding a scope constraint on the dept column as follows ...
(People, apparently, love drawing examples from the domain of employees and departments)
I am attempting to write a trigger which prevents the insertion into my TA (teaching assistant) table if the student is already taking the class for which they're attempting to become a TA. From what I understand the best way to do this (other than a unique constraint, this has to be a trigger) is through the use of rollback, and I'm also using raiserror.
create or replace trigger CheckTA
after insert
on TA for each row
begin
if exists (select * from Enrolls where Student_ID = :new.Student_ID and Course_ID = :new.Course_ID) then
rollback;
raiserror('TA enrolled in course as student');
end if;
end;
Upon creating the trigger, I'm met with the following errors:
Error(2,2): PL/SQL: Statement ignored
Error(2,5): PLS-00204: function or pseudo-column 'EXISTS' may be used inside a SQL statement only
The trigger still gets created, but it doesn't trigger when improper values are inserted into TA.
Tables:
TA
Create table TA
(
Student_ID int references Student(Student_ID),
Course_ID int references Course(Course_ID),
Courses_taught varchar2(250),
Office_hours varchar2(25)
);
Student
create table Student
(
Student_ID int primary key,
Name varchar2(50),
Start_began int,
Semester_began varchar2(50),
GPA int,
Degree_status varchar2(25),
Degree_type varchar2(50),
Courses_Taken varchar2(250),
JI_list varchar2(250),
Profile_status varchar2(50)
);
Course
create table Course
(
Course_ID int primary key,
Course_Name varchar2(25)
);
Enrolls
create table Enrolls
(
Student_ID int references Student(Student_ID),
Course_ID int references Course(Course_ID)
);
Stored Procedure
This is the SP I use to insert TA values that should trigger the trigger. It doesn't raise errors but I'll put it here in case it's relevant.
create or replace procedure addTA (s_id int, c_id int, course varchar2)
as
begin
insert into TA (Student_ID,Course_ID,Courses_taught)
values (s_id,c_id,course);
end;
Honestly I'm not quite sure what these errors mean, let alone what's causing them, but still I've tried messing with the syntax of raiserror as well as replacing it with raise_application_error with no luck. If anyone could help me resolve or at least explain these errors it would be greatly appreciated. And of course if there is anything obviously wrong with my trigger please let me know.
The error says "function or pseudo-column 'EXISTS' may be used inside a SQL statement only", meaning that you only can use EXISTS inside a SQL statment, while you are using in in a PL/SQL statement.
If you need to check for the existence of some records, you can try something like this:
vNum number;
...
select count(1)
into vNumber
from Enrolls
where Student_ID = ...
if vNumber > 0 then
...
else
...
end if;
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;
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..