Should I have separate tables or integrate the data? - sql

I am designing an SQL database which has 2 tables that require a 'manager_id'. The 'Employees' table and the 'Facilities' table. Because Managers are considered to be employees, I'm not sure if I should have a separate 'Managers' table or to just integrate it into the 'employees' table. I'm new to SQL and not sure about cases like this. This is the code I have so far:
CREATE TABLE Employees (
emp_id NUMBER(5) NOT NULL,
emp_name VARCHAR2(20) NOT NULL,
emp_add1 VARCHAR2(30) NOT NULL,
emp_add2 VARCHAR2(30) NOT NULL,
emp_add3 VARCHAR2(30),
emp_town VARCHAR2(30),
emp_county NUMBER(2) NOT NULL,
emp_telno NUMBER(10),
emp_position NUMBER(3) NOT NULL,
emp_manager NUMBER(4),
CONSTRAINT pk_empid PRIMARY KEY (emp_id),
CONSTRAINT fk_empcounty FOREIGN KEY (emp_county) REFERENCES County(county_id),
CONSTRAINT fk_empposition FOREIGN KEY (emp_position) REFERENCES Positions(position_id),
CONSTRAINT fk_empmanager FOREIGN KEY (emp_manager) REFERENCES Manager(manager_id)
);
CREATE TABLE Facilities (
facility_id NUMBER(2) NOT NULL,
facility_name VARCHAR(15) NOT NULL,
facility_manager NUMBER(4) NOT NULL,
CONSTRAINT pk_facilityid PRIMARY KEY (facility_id);
CONSTRAINT fk_facilitymanager FOREIGN KEY (facility_manager) REFERENCES Manager(manager_id)
);

This is question about relational normalisation (the organisation of data in a relational database).
So how to organise:
Well though there are many steps in normalisation with the aim to produce the most efficient structure. In your case you should first try the approach of putting the common bits in the same table and the non common bits should be in another.
So since the manger is an employee (lets say with the attributes employee title, name, department) this should be in the employee table. But lets say managers have clipboards (e.g. attributes colour and size) that non managers don't. In which case the you'd add a Manager table to handle those.
-- employeee table
id Name Title Dept
1 adohertyd Coder IT
2 Preet Ninja SillyWalks
3 Skeety Secretary Cleaning
-- manager table
manager_id employee_id clipboard_size clipboard_colour
1 2 Big Black
The you'd find a manager like this
select Name, Dept, clipboard_size
from employee e
inner join manager m on e.id = m.employee_id
As you go further you may find it more efficient to bring some of those attributes into the employee table and have a 'Is_manager' column. This is essentially denormalisation and something that you should avoid until you need it.

Related

How to enforce two foreign key columns to be either both null or both pointing to some row in other table in Oracle?

I have two tables, Employees and Tasks (this is not an actual SQL code of course, listed just important stuff):
CREATE TABLE Employees (
employee_id NUMBER(6) NOT NULL,
is_boss NUMBER(1) DEFAULT 0 NOT NULL,
name VARCHAR2(32) NOT NULL,
CHECK (is_boss IN (0,1)),
UNIQUE (is_boss, employee_id)
);
CREATE TABLE Tasks (
task_id NUMBER(6) NOT NULL,
name VARCHAR2(32) NOT NULL,
is_boss NUMBER(1),
employee_id NUMBER(6),
finish_date DATE,
CHECK (is_boss IN (1)),
FOREIGN KEY (employee_id) REFERENCES Employees (employee_id),
FOREIGN KEY (is_boss) REFERENCES Employees (is_boss)
);
So the Tasks table contains some tasks. When they are added to the table we only need the name and id, that's why other fields are nullable. At some point in time every task has to be confirmed by a "boss", so an employee which has is_boss == 1 and only then finish_date is put, quite simple.
Those check constraints and foreign/unique keys work well if a task is updated with both is_boss and employee_id - if an employee is not a boss, it throws an error, if there isn't such employee also. But if one of those is null everything goes wrong. So what I want is to somehow enforce the database to check to have those two fields be either both not null or both null. Actually, I want 3 fields (finish_date too) to be either all null or all not null.
A trigger is probably an option, but my database teacher is very much against using them if there is a different, simpler possibility. So my question is - is there a way to enforce it without a trigger? DBMS is Oracle 11g.
Thanks in advance.
You need to combine your two foreign keys into a single foreign key - otherwise I think you'll find they're not doing quite what you think they're doing. Also, you need a check constraint to ensure that all three fields are set or all three are NULL. Your TASKS table needs to be something like:
CREATE TABLE TASKS (
TASK_ID NUMBER(6) NOT NULL,
NAME VARCHAR2(32) NOT NULL,
IS_BOSS NUMBER(1),
EMPLOYEE_ID NUMBER(6),
FINISH_DATE DATE,
CONSTRAINT TASKS_CK1
CHECK (is_boss IN (1)),
CONSTRAINT TASKS_FK1
FOREIGN KEY (IS_BOSS, EMPLOYEE_ID)
REFERENCES EMPLOYEES (IS_BOSS, EMPLOYEE_ID),
CONSTRAINT TASKS_CK2
CHECK((IS_BOSS IS NULL AND
EMPLOYEE_ID IS NULL AND
FINISH_DATE IS NULL)
OR
(IS_BOSS IS NOT NULL AND
EMPLOYEE_ID IS NOT NULL AND
FINISH_DATE IS NOT NULL))
);

Oracle SQL queries for subclass-table - Inheritance

I have a Staff table with staffNo as the primary key
and staff details present in two other tables inheriting from it: FTLecturer and PTLecturer. I used the Optional Or relationship there.
My question is: how to write Oracle SQL queries for these sub-classes?
I think I do not need a PK for them again because they inherit from `Staff'. Is it so?
This is the Staff table
CREATE TABLE Staff
(staffNo number(10) NOT NULL,
firstName varchar2(50) NOT NULL,
lastName number(2) ,
address varchar2(50) NOT NULL,
CONSTRAINT courseID_pk PRIMARY KEY (staffNo),
CONSTRAINT fk_Module
FOREIGN KEY (moduleID)
REFERENCES Module(Module Module ID)
);
I need to create the FTLecturer table with the Optional Or relationship.
Inheritance is a concept from Object-Oriented Programming: it has no meaning in relational databases. The most important impact of this is that child tables do need a primary key.
"I need to create the FTLecturer table with the Optional Or relationship."
What you're describing is called an Arc in relational databases: a lecturer can be full-time or part-time but not both. We can enforce this with a decent set of constraints.
First add a column to the parent table to identify the Lecturer type.
alter table staff add staffType varchar2(10);
This should be validated with a foreign key against a reference data table, and at a pinch a check constraint. Then we add a unique constraint (yes, as well as a primary key):
alter table staff add constraint staff_uk unique(staffNo, staffType);
We can use this to enforce the arc on the child tables.
create table FTLecturer (
staffNo number not null,
staffType varchar2(10) not null,
tenure varchar2(3) not null,
constraint FTLecturer_pk primary key (staffNo),
constraint FTLecturer_ck check (staffType = 'FTL'),
constraint FTLecturer_Staff_fk foreign key (staffNo, staffType)
references staff (staffNo, staffType);
Note that the foreign key means we can only insert rows in this table which have a parent of the correct type in the STAFF table. This is why we need the StaffType column and that unique constraint on the STAFF table.
Likewise for Part-Time Lecturers:
create table PTLecturer (
staffNo number not null,
staffType varchar2(10) not null,
hours number not null,
constraint PTLecturer_pk primary key (staffNo),
constraint PTLecturer_ck check (staffType = 'PTL'),
constraint PTLecturer_Staff_fk foreign key (staffNo, staffType)
references staff (staffNo, staffType);
Since Oracle 11g we can use a virtual column to apply a constant value for the staffType on the child tables. This avoids the need for the check constraints. Find out more.
To create a record for a specific member of staff it is good practice to populate the parent and child in the same action; we can use the INSERT ALL syntax to populate multiple tables. Find out more.
Finally, you can build a helpful API with views. For instance:
create or replace view fulltimeLecturer as
select staff.*
, ftl.tenure
from staff
join ftlecturer ftl
on staff.staffno = ftl.staffno
and staff.stafftype = ftl.stafftype;
create or replace view parttimeLecturer as
select staff.*
, ptl.hours
from staff
join ptlecturer ptl
on staff.staffno = ptl.staffno
and staff.stafftype = ptl.stafftype;
This may strike you as a lot of work, and inflexible to boot. Here is the difference between Object Programming and Relational Databases. OOP is primarily driven by helping developers to write code; RDBMS is focused on guaranteeing the integrity of the stored data.

Foreign key referring to more than one primary key values(from one table) - Oracle SQL PLUS

I am still a beginner in SQL and i'm facing an issue. hope you can help me.
I have a table called Department where it has an attribute DEPARTMENT_NO as its primary key.
CREATE TABLE DEPARTMENT(
DEPARTMENT_NO INT NOT NULL,
NAME VARCHAR(25) NOT NULL,
LOCATION CHAR(15),
PRIMARY KEY(DEPARTMENT_NO));
I have another table called Doctor where it has an attribute DNUM as a foreign key referring to DEPARTMENT_NO :
CREATE TABLE DOCTOR(
DOCTOR_ID CHAR(9) NOT NULL,
DNUM INT NOT NULL,
NAME VARCHAR(20) NOT NULL,
DOB DATE,
SPECIALTY VARCHAR(20) NOT NULL,
SALARY INT,
CITY VARCHAR(15),
STREET VARCHAR(15),
START_DATE DATE,
PRIMARY KEY(DOCTOR_ID))
FOREIGN KEY(DNUM) REFERENCES DEPARTMENT(DEPARTMENT_NO));
A doctor can be working in one or two departments. So, if I have a doctor working in department 1 and 4 (the values for DNUM will include 1 and 4).
I initially chose the data type of DNUM to be INT(same as DEPARTMENT_NO data type). But INT is not ideal for multiple values.
What should the data type be? or what other solution i have if,for example, I run a query for returning the name of the doctors working in department 4.
The query should return all the names of doctors working in department 4(only) and the ones who work in multiple departments(including 4).
Thanks very much in advance and sorry for the long message.
The standard way to represent a "many to many" relationship is via a "junction" (aka "link") table:
CREATE TABLE DOCTOR_DEPARTMENT (
DOCTOR_ID INT REFERENCES DOCTOR(DOCTOR_ID),
DEPARTMENT_NO INT REFERENCES DEPARTMENT (DEPARTMENT_NO),
PRIMARY KEY (DOCTOR_ID, DEPARTMENT_NO)
);
Note the key on {DOCTOR_ID, DEPARTMENT_NO}, which ensures the same doctor cannot be connected to the same department twice.
It also implicitly creates a composite (aka. "concatenated") index on these fields in that order, which makes it very quick to find departments of a given doctor (via an index range scan). If you need to query in the opposite "direction" (for doctors of the given department), flip the order of fields. If you need both queries, then you'll need both indexes (i.e. you'll need to create one index explicitly).
Consider adding ORGANIZATION INDEX clause, if you need just one of these indexes.
You need an additional table called doctor_department
create table doctor_department
(doctor_id integer references doctor(doctor_id) not null,
dnum integer references department(dnum) not null
)
You can create another table with relation to these 2 tables
Say,
Create table Dept_Doctors(
ID int not null,
DOCTOR_ID char(9) not null,
DEPARTMENT_NO INT NOT NULL,
PRIMARY KEY (ID),
FOREIGN KEY(DEPARTMENT_NO) REFERENCES DEPARTMENT(DEPARTMENT_NO),
FOREIGN KEY(DOCTOR_ID) REFERENCES DOCTOR(DOCTOR_ID));
You can join the 3 tables and get the desired result.

Junction/Bridge tables Oracle SQL

Just a quick question regarding Junction tables in Oracle SQL. I understand their functionality and their role in a 'many to many' relationship but what about a 'one to many' relationship? I have two tables, Employees and Positions. Each Employee can only hold one position, however each each position can have many employees. e.g. John Doe can only be a sales executive, however there are 4 sales executives in the company. This is how I have it coded so far:
CREATE TABLE Positions (
position_id NUMBER(2) NOT NULL,
position_name VARCHAR2(25) NOT NULL,
CONSTRAINT pk_position PRIMARY KEY(position_id)
);
CREATE TABLE Employee (
emp_id NUMBER(3) NOT NULL,
emp_name VARCHAR2(30) NOT NULL,
emp_position NUMBER(2) NOT NULL,
emp_salary NUMBER(5) NOT NULL,
CONSTRAINT pk_employee PRIMARY KEY(emp_id),
CONSTRAINT fk_emp_pos FOREIGN KEY (emp_position)
REFERENCES Position(position_id)
);
CREATE TABLE pos_emp (
position_id NUMBER(2) NOT NULL,
emp_id NUMBER(3) NOT NULL,
CONSTRAINT pk_pos_emp PRIMARY KEY(position_id, emp_id)
);
Is this correct? Is there a need for:
a. The foreign key in the Employee table?
b. The junction table?
I want to enforce the one employee to one role relationship in the employee table while being able to have the one role to many employees relationship in the junction table.
Hope this makes sense
The relations you have set up is a Many-Many relation. So if you want a one to many. Then the most common way is to skip the pos_emp and have the foreign key directly in Employee. So that the table looks something like this:
CREATE TABLE Employee (
emp_id NUMBER(3) NOT NULL,
emp_name VARCHAR2(30) NOT NULL,
emp_position NUMBER(2) NOT NULL,
emp_salary NUMBER(5) NOT NULL,
position_id NUMBER(2) NOT NULL,
...
EDIT
I have my employee table set up like that in my code. Will having the
Position_id in the Employee table as the foreign key be enough to
enforce each position having many employees?
If the position_id in the Employee do not allow nulls then you cannot add a Employee without a position. That means that if you are trying to insert a Employee without a position you will get an exception that the foreign relation is not satisfy.
But you need to check it in code so that when you add a Employee the position_id has a value. So you do not send the insert to that database if it do not have a value. Because that is a unnecessarily database call.
And another interesting point is what should happened if you remove a Employee? Should you remove the Employee related to that position? If the answer is yes. You might consider to have a cascade delete from the position table. Otherwise you might need a trigger for it.

Enforce constraints between tables

How do you establish a constraint where, one column (not the primary key) has to have the same value as another table's column. I'm not quite sure how to phrase it so here's an example:
Ex:
I have three tables, Employee, Director, Division and Department
The structure for the tables are as follows:
Employee
Id
Name
DirectorId (FK)
DepartmentID (FK)
Director
Id
Name
DepartmentID (FK)
Department
Id
Name
DivisionId (FK)
Division
Id
Name
Employees and directors both have departments, each department has a division but their divisions have to be the same. Is there a way to enforce this? (Hopefully without having to resort to triggers)
Create stored procedures that will have proper GRANTS and don't allow user to INSERT into table directly. Use stored procedures as interface to the database, and check required conditions in them before insertion.
First, your example tables are doing too much. There is a design principle that states that a single table should model an entity or a relationship between entities but not both. The relationships between departments, directors and employees (I'm assuming that directors are not employees; I'm also omitting divisions for the moment).
Second, a table can have more than one key, known as candidate keys. Further, you can create a UNIQUE constraint by 'appending' a non-unique column to a key. For example, employees' names do not make for a good key, hence the reason for having an employee ID (I don't think the same can be said for departments i.e. department name in itself is a good enough key). If employee_ID is unique then it follows that (employee_name, employee_ID) will also be unique.
Third, a table can be referenced by any UNIQUE constraint, it doesn't have to be the table's 'primary' key (which partly explains why 'primary key' is a bit of a nonsense).
The great thing about the above is that one can model the required constraints using FOREIGN KEY and row-level CHECK constraints. SQL optimizers and programmers prefer declarative solutions to procedural code (triggers, stored procs, etc). This vanilla SQL DDL will port to most SQL products.
So, the department name can be combined with both the director key and the employee key respectively and these compound keys can be referencesd in a simple two-tier org chart table: because both the employee's department and their director's department will appear in the same table, a simple row-level CHECK constraint can be used to test that they are the same e.g.
Entity tables:
CREATE TABLE Departments
(
department_name VARCHAR(30) NOT NULL UNIQUE
);
CREATE TABLE Employees
(
employee_ID INTEGER NOT NULL UNIQUE,
employee_name VARCHAR(100) NOT NULL
);
CREATE TABLE Directors
(
director_ID INTEGER NOT NULL UNIQUE,
director_name VARCHAR(100) NOT NULL
);
Relationship tables:
CREATE TABLE EmployeeDepartments
(
employee_ID INTEGER NOT NULL UNIQUE
REFERENCES Employees (employee_ID),
employee_department_name VARCHAR(30) NOT NULL
REFERENCES Departments (department_name),
UNIQUE (employee_department_name, employee_ID)
);
CREATE TABLE DirectorDepartments
(
director_ID INTEGER NOT NULL UNIQUE
REFERENCES Directors (director_ID),
director_department_name VARCHAR(30) NOT NULL
REFERENCES Departments (department_name),
UNIQUE (director_department_name, director_ID)
);
CREATE TABLE OrgChart
(
employee_ID INTEGER NOT NULL UNIQUE,
employee_department_name VARCHAR(30) NOT NULL,
FOREIGN KEY (employee_department_name, employee_ID)
REFERENCES EmployeeDepartments
(employee_department_name, employee_ID),
director_ID INTEGER NOT NULL,
director_department_name VARCHAR(30) NOT NULL,
FOREIGN KEY (director_department_name, director_ID)
REFERENCES DirectorDepartments
(director_department_name, director_ID),
CHECK (employee_department_name = director_department_name)
);
Now a slightly more interesting scenario would be when a director is assigned a division, rather than a specific department, and you had to test that the employee's department was in the same division as her director:
Entity tables:
CREATE TABLE Divisions
(
division_name VARCHAR(20) NOT NULL UNIQUE
);
CREATE TABLE Departments
(
department_name VARCHAR(30) NOT NULL UNIQUE,
division_name VARCHAR(20) NOT NULL
REFERENCES Divisions (division_name),
UNIQUE (division_name, department_name)
);
CREATE TABLE Employees
(
employee_ID INTEGER NOT NULL UNIQUE,
employee_name VARCHAR(100) NOT NULL
);
CREATE TABLE Directors
(
director_ID INTEGER NOT NULL UNIQUE,
director_name VARCHAR(100) NOT NULL
);
Relationship tables:
CREATE TABLE EmployeeDepartments
(
employee_ID INTEGER NOT NULL UNIQUE
REFERENCES Employees (employee_ID),
employee_department_name VARCHAR(30) NOT NULL
REFERENCES Departments (department_name),
UNIQUE (employee_department_name, employee_ID)
);
CREATE TABLE DirectorDivisions
(
director_ID INTEGER NOT NULL UNIQUE
REFERENCES directors (director_ID),
director_division_name VARCHAR(20) NOT NULL
REFERENCES divisions (division_name),
UNIQUE (director_division_name, director_ID)
);
CREATE TABLE OrgChart
(
employee_ID INTEGER NOT NULL UNIQUE,
employee_department_name VARCHAR(30) NOT NULL,
FOREIGN KEY (employee_department_name, employee_ID)
REFERENCES EmployeeDepartments
(employee_department_name, employee_ID),
employee_division_name VARCHAR(20) NOT NULL
REFERENCES divisions (division_name),
FOREIGN KEY (employee_division_name, employee_department_name)
REFERENCES Departments (division_name, department_name),
director_ID INTEGER NOT NULL,
director_division_name VARCHAR(20) NOT NULL,
FOREIGN KEY (director_division_name, director_ID)
REFERENCES DirectorDivisions
(director_division_name, director_ID),
CHECK (employee_division_name = director_division_name)
);
There is no limitation on creating foreign keys--there's nothing to stop you from defining a foreign key constraint on these tables:
EMPLOYEE
DIRECTOR
...associating them to the DEPARTMENT table. Though frankly, I don't see the need for a DIRECTOR table--that should be either a boolean indicator in the EMPLOYEE table, or possibly an EMPLOYEE_TYPE_CODE with it's own foreign key constraint to distinguish between employees and directors.
Multiple Foreign Keys
The presence of a foreign key also doesn't stop you from putting a second (or third, etc) constraint on the same column. Consider the scenario of TABLE_C.column having foreign key constraints to both TABLE_A.col and TABLE_B.col -- this is perfectly acceptable in the database, but it means that only values that exist in both TABLE_A.col and TABLE_B.col can exist in the TABLE_C.column. IE:
TABLE_A
col
----
a
b
c
TABLE_B
col
----
c
Based on this example data, TABLE_C.column could only ever allow "c" as value to exist in the column if someone added two foreign key constraints to the TABLE_C.column, referencing TABLE_A.col and TABLE_B.col.