Since the LOCATIONS and employee CLASSES are quite important for your database, you should probably give them separate tables. Here some suggestions - which may serve as a starting point (including some test data, using Oracle 12c).
create table locations ( id, description )
as
select 100, 'Executive Lounge' from dual union all
select 200, 'Executive Washroom' from dual union all
select 300, 'Supply Closet' from dual union all
select 400, 'Employee Lounge' from dual union all
select 500, 'Server Room' from dual union all
select 600, 'Server Closet' from dual;
create table employeeclasses ( id, description )
as
select 1, 'class 1' from dual union all
select 2, 'class 2' from dual union all
select 3, 'class 3' from dual union all
select 4, 'class 4' from dual union all
select 5, 'class 5' from dual union all
select 6, 'class 6' from dual;
create table employees ( id, fname, lname, employeeclass )
as
select 5000, 'fname5000', 'lname5000', 1 from dual union
select 5001, 'fname5001', 'lname5000', 2 from dual union
select 5002, 'fname5002', 'lname5000', 1 from dual union
select 5003, 'fname5003', 'lname5000', 2 from dual union
select 5004, 'fname5004', 'lname5000', 6 from dual ;
alter table locations
add constraint pkey_locations primary key ( id ) ;
alter table employeeclasses
add constraint pkey_eclasses primary key ( id );
alter table employees
add constraint pkey_employees primary key ( id ) ;
alter table employees
add constraint fkey_empclass
foreign key ( employeeclass ) references employeeclasses ( id ) ;
When it comes to permissions, things get a bit more complicated :-). Apparently, you want to be able to assign access permissions to GROUPS (classes of employees). However, in the "access log" (your table3), you are recording events that are associated with SINGLE users. Thus, (just as an idea) we could create a PERMISSIONS table, where we record all permissions (per employee). You see that any of the valid combinations of EMPLOYEEID and LOCATIONID can be recorded in the access log table (code below), whose foreign key constraint references the permissions table.
create table permissions (
employeeid number references employees( id )
, locationid number references locations( id )
, constraint pkey_permissions
primary key ( employeeid, locationid )
);
create table accesslog (
id number generated always as identity start with 2000
, employeeid number
, locationid number
, access_time date default sysdate
, constraint fkey_accesslog
foreign key ( employeeid, locationid )
references permissions ( employeeid, locationid )
);
Testing
-- Grant all members of class 1 access to locations 100 and 200.
insert into permissions ( employeeid, locationid )
select id, 100
from employees
where employeeclass in ( 1, 2 )
union
select id, 200
from employees
where employeeclass in ( 1, 2 ) ;
-- Employee 5000 accesses location 100 -> access granted, row inserted.
insert into accesslog ( employeeid, locationid )
values ( 5000, 100 ) ;
-- 1 row inserted.
-- Is employee 5000 allowed access to location 500? No.
insert into accesslog ( employeeid, locationid )
values ( 5000, 500 ) ;
ORA-02291: integrity constraint (...FKEY_ACCESSLOG) violated - parent key not found
-- Employees 5000, 5001, 5002, and 5003 want to access location 200.
-- They have permission to do so.
declare
location constant number := 200 ;
begin
for i in 5000 .. 5003
loop
insert into accesslog ( employeeid, locationid )
values ( i, location ) ;
end loop;
end;
/
-- PL/SQL procedure successfully completed.
Our "access log" now contains:
select id, employeeid, locationid
, to_char( access_time, 'YYYY-MM-DD HH24:MI:SS' )
from accesslog ;
ID EMPLOYEEID LOCATIONID TO_CHAR(ACCESS_TIME
---------- ---------- ---------- -------------------
2000 5000 100 2018-10-08 04:46:35
2002 5000 200 2018-10-08 04:50:20
2003 5001 200 2018-10-08 04:50:20
2004 5002 200 2018-10-08 04:50:20
2005 5003 200 2018-10-08 04:50:20
Now - if you also want to record "access denied" events, and want to use GROUP/class level permissions, too, then we need triggers (frowned upon...) or procedures (the bee's knees...). The main reason for this being: CHECK constraints only work within a single table. Hence, we cannot check whether a particular employee belongs to a particular group AND whether this group is allowed to access a particular location (even in your original layout, the class data and employee data are located in 2 tables ...).
For recording all valid combinations of classes and locations, we should probably add another table (your original design will contain loads of NULLs in table1). Also, the accesslog gets an additional column, that allows us to record "access denied" events.
alter table accesslog
add allowdeny varchar2(128) default 'access granted' ;
SQL> select * from accesslog;
ID EMPLOYEEID LOCATIONID ACCESS_TIME ALLOWDENY
2000 5000 100 08-OCT-18 access granted
2002 5000 200 08-OCT-18 access granted
2003 5001 200 08-OCT-18 access granted
2004 5002 200 08-OCT-18 access granted
2005 5003 200 08-OCT-18 access granted
-- just an example!
create table locations_classes ( locationid, classid )
as
select 100, 1 from dual union all
select 200, 2 from dual union all
select 300, 3 from dual union all
select 400, 4 from dual ;
The procedure code should go into a package (which is omitted here, for clarity). The code given below is far from perfect, but it should give you something to work with - if you have done some PL/SQL before.
create or replace procedure getAccess (
employeeid_ number
, locationid_ number
)
is
employeeclass_ number := 0 ;
classfound_ number := 0 ;
begin
select employeeclass into employeeclass_
from employees where id = employeeid_ ;
select count(*) into classfound_
from locations_classes
where rownum = 1 and locationid = locationid_ and classid = employeeclass_ ;
if classfound_ > 0 then
insert into accesslog ( employeeid, locationid )
values ( employeeid_, locationid_ ) ;
else
insert into accesslog ( employeeid, locationid, allowdeny )
values ( employeeid_, locationid_, 'ACCESS DENIED' ) ;
end if;
end;
/
When testing the procedure, you may find that some calls will cause an ORA-02991. You can disable the accesslog's FK constraint in order to allow the logging of "access denied" events.
-- parameters: employee id, location id
begin
getAccess( 5000, 100 ) ;
getAccess( 5003, 100 ) ;
getAccess( 5002, 100 ) ;
getAccess( 5004, 300 ) ;
commit ;
end ;
/
-- ORA-02291: integrity constraint (...FKEY_ACCESSLOG) violated - parent key not found
alter table accesslog disable constraint fkey_accesslog ;
SQL> select * from accesslog ;
ID EMPLOYEEID LOCATIONID ACCESS_TIME ALLOWDENY
2024 5000 100 08-OCT-18 access granted
2025 5003 100 08-OCT-18 ACCESS DENIED
2026 5002 100 08-OCT-18 access granted
2027 5004 300 08-OCT-18 ACCESS DENIED
Sorry for the length of this answer! The gist is: do whatever you can with integrity constraints! Let the DDL code do the work. CHECK constraints do not work across tables. When you look at the (very crude) diagram, you will see that the LOCATIONS_CLASSES is not connected to the access log. The procedure "enforces a constraint" - but not really, as we want to record "access denied" as well.
If you want to use a procedure, you may not need the PERMISSIONS table. However, if you can get away WITHOUT writing a procedure, and only use integrity constraints, you'll get a more robust solution!
Allowed
column in table3 for if records can only be entered when access is allowed?Allowed
would describe if the employee was actually granted access or not.JOIN
with aCASE
statement.Emp_id
is checked againstEmp_class
(from table2), and further it is checked if thatEmp_class
has access to a particular location (2-7 of table1). So location will be entered only if value of anEmp_class
against that location is true (table1). The values of location, I am now realizing can be from column names 2-7 in table1. If we get true from table1 thenAllowed
is TRUE, else FALSE. table1 and table2 are given but table3 can be modified or discarded to make new table(s).