Oracle SQL trigger using add columns,comment - sql

I should create a trigger in Oracle SQL that
Add department_amount column to locations table
Add comment 'Contains the amount of departments in the location'
Create a trigger ,which will update the amount of departments in location every time a row is inserted/deleted from departments.
Tables:
CREATE TABLE departments
(
department_id NUMBER(4) PRIMARY KEY,
department_name VARCHAR2(30) NOT NULL,
manager_id NUMBER(6),
location_id NUMBER(4) NOT NULL
);
CREATE TABLE locations
(
location_id NUMBER(4) PRIMARY KEY,
street_address VARCHAR2(40),
postal_code VARCHAR2(12),
city VARCHAR2(30),
state_province VARCHAR2(25),
country_id CHAR(2) NOT NULL
);

To answer your questions
Add department_amount column to locations table
alter table locations add department_amount number ;
Add comment 'Contains the amount of departments in the location'
comment on column locations.deparment_amount is 'Contains the amount of departments in the location';
Create a trigger ,which will update the amount of departments in location every time a row is inserted/deleted from departments.
create or replace trigger trg_loc
after insert or delete on departments
declare
begin
merge into locations t
using ( select count(department_id) as dpt_amount, location_id as loc_id
from departments b
group by location_id ) s
on (t.location_id = s.loc_id)
when matched then
update set t.department_amount = s.dpt_amount ;
end;
/
You have below a db<>fiddle with data example and the trigger demonstration that updates the department_amount in locations table when you insert or delete a department for each location.
db<>fiddle

I think that this task is so much of a don't-do-that that it serves no purpose. Not even for training.
What you'd have to do is
Check whether the column locations.department_amount exists. You can do this by looking into the system view dba_tables (or all_tables or user_tables if appropriate).
If that column does not exist, create it via ALTER TABLE. This, however is DDL, not DML, so you must run it dynamically via EXECUTE IMMEDIATE.
In case you've just created the column, also create the comment.
Now, in that same trigger add or subtract 1 from the department_amount. Thus you guarantee that the very first action (the one that leads to creating the column) already updates it.
Points 1 to 3 only have to happen on statement level, while point 4 should happen on row level. For this reason and in order to avoid problems with other triggers, this trigger should be a compound trigger.
But then, if you can create the trigger in the database, why can't you add the column and its comment, too? Why must you write the trigger such that it has to check whether the column exists? As mentioned, this just makes no sense, so the best thing here is not to do that.

Related

Is there any way to restrict the number of inserted rows?

Oracle newbie here. I need to build a database which fulfils the requirements below:
A department is allowed to register for only two programs in a year
The maximum participants in each program must not exceed the number of people in respective departments.
*There are 14 departments in total.
As per requirement, seems like I have to restrict the number of rows inserted.
For example, if the total number of people in Department A is 100, the 101st row has to be rejected.
Apologies if there are many errors as I'm writing this question because now is 1.30AM. I tried to keep the table simple with less columns so it's easier to test the code.
CREATE TABLE department(
DEPT_ID CHAR(5) not null primary key,
TOTAL_P NUMBER);
CREATE TABLE participant(
P_ID CHAR(5) not null primary key,
DEPT_ID CHAR(5) not null);
CREATE TABLE program(
PROG_ID CHAR(5) not null primary key,
PROG_NAME VARCHAR(30),
DEPT_ID CHAR(5),
START_DATE DATE,
END_DATE DATE,
FOREIGN KEY(DEPT_ID) references department(DEPT_ID) on delete cascade);
and I have tried using trigger, but I keep getting warning: trigger created with compilation errors.
(I tried to count the rows in table program and group them by dept_id, then proceed to check the condition)
CREATE OR REPLACE TRIGGER prog
BEFORE INSERT ON program
DECLARE
CountRows NUMBER;
BEGIN
SELECT COUNT(*)
INTO CountRows
GROUP BY DEPT_ID
FROM program;
IF CountRows > 2 THEN
RAISE_APPLICATION_ERROR(-20001, 'Only 2 programs are allowed');
END IF;
END;
/
I don't even know if my idea does make sense or not. I tried many other ways like putting the condition where(to specify dept_id) before begin, after begin, I still get the warning. I have been experimenting a whole day and still cannot figure it out.
MY QUESTIONS:
Is it better to create multiple conditions in one trigger as I will have 14 departments?
if so, how to do that without getting the warning?
any alternative way to restrict the number of rows?
Any help, hints, tips, anything, is deeply appreciated. thanks.
You could do it by maintaining in the trigger handling insert/update/delete operation on the "child" table (e.g. the program), an intersection table "parent_child" (e.g. department_program) containing the 2 foreign keys on the parent/child tables, and an index on which you will put the check constraint (e.g. < 3 for the number of program per department) + any other column defining the scope of the constraint (e.g. here the year of the start_date of the program). The 2 columns with the FK, the index and the other scope columns should be the PK of this intersection table.
e.g.
CREATE TABLE program_department
(
DEPT_ID CHAR(5),
PROG_ID CHAR(5),
PROG_YEAR NUMBER(4),
PROG_IDX NUMBER(10,0) DEFAULT 0,
-- to force always equal to a number the constraint must be defererrable
CONSTRAINT CK_PROG_IDX CHECK (PROG_IDX >= 0 AND PROG_IDX < 3) ENABLE,
PRIMARY KEY (PROG_ID, DEPT_ID, PROG_YEAR, PROG_CNT)
)
;
The idea is to maintain the PROG_IDX that will contain the index of the relation between the department and the program of the specific year.
In the trigger on the table program, you have to update the program_department according to each action, when updating/removing this may imply/implies decrementing the PROG_IDX of the ones having PROG_IDX greater than the one removed.
And of course you will have to apply about the "same" logic for the participant's relationship, however there you can't hardcode the constrain by a CHECK since the # of people in each department is not known at compile time. This case is more complex also because you have to think about the consequence of changes of the # of persons in a department. Probably you will have to keep in the intersection table, the # of people in the department at the start_date of the program.

SQL Constraint to check the value of another table's column

Suppose I have 2 tables, Employee and Sale.
Employee table has EMP_ID int as PK, and ACTIVE_STATUS bit (0 for inactive and 1 for active).
Sale table has SALE_ID as PK, EMP_ID as FK referencing Employee.EMP_ID and DATE_OF_SALE
Now, I want a constraint that checks if the EMP_ID I'm trying to insert into Sale has a value of 1 for the ACTIVE column in the Employee table, because I don't want to register a sale that is being attempted by an Inactive user.
How would I go about that? I tried CONSTRAINT CHECK_IF_ACTIVE CHECK(Employee.ACTIVE = 1) but it's not a valid statement.
You can do what you are specifically asking for using foreign keys and computed columns. First, define a redundant unique constraint in employees:
alter table employees add constraint unq_employees_empid_active_status on (empid, active_status);
Then, define a computed column in sales. Alas, this needs to be persisted, I think:
alter table sales add active_status as (convert(bit, 1)) persisted;
Then, define the foreign key constraint using both:
alter table sales add foreign key fk_sales_employees_active
foreign key (empid, active_status) references employees(empid, active_status);
Voila! The employee id can only reference active employees.
Now, you will have a problem with this -- be careful what you ask for. It is not really what you want. This enforces the constraint over all time. So, you won't be able to change the status on employees who have sales. That suggests that you need an insert trigger instead -- or a user defined function and check constraint:
create function is_employee_active (
#empid int
) returns bit as
begin
return (select active_status from employees e where e.empid = #empid);
end;
alter table sales add constraint chk_sales_employee_active
check (is_employee_active(empid) = convert(bit, 1));
Voila! This only does the check on insertion or updates. Note that once an employee is not active, you won't be able to update the row either.
You'll notice that I usually name my tables in the plural, because they contain lots of examples of an entity. My fingers just add the "s" when I'm thinking about tables.

How to link three tables?

I'm new to SQL and ask for your help.
There are 3 tables, these are "Employees", "Positions" and "EmployeesPositions".
For example, 2 positions can be attached to one employee.
How to link tables so that duplicates do not occur? I read about foreign keys and JOIN, but I have not yet figured out how to do it correctly.
Table structure:
Employees (id, Name);
Positions (id, Post, Rate); EmployeesPositions - I do not know how to make it right.
What I need: when adding an employee to the "Employees" table, associate an entry with posts from the "Positions" table, but as I wrote above, one employee can be associated with 2 posts (but not always). How correctly to implement the third table (EmployeesPositions), because in Positions only posts and rates are stored, and in EmployeesPositions there should be records, for example, Name1 => Post1 and Post2, and Name2 only Post 1?
If I thought something wrong, tell me please how best to implement it.
There are several ways to solve your problem, each with their own pros and cons.
First if we simplify your problem to "an employee has zero or more positions", then you can use the following table to associate an employee with a position:
create table employeespositions (
employee_id integer not null,
position_id integer not null,
constraint pk_employeespositions
primary key (employee_id, position_id),
constraint fk_employeespositions_employee
foreign key (employee_id) references employees (id),
constraint fk_employeespositions_position
foreign key (position_id) references positions (id)
)
The foreign keys enforce the existence of the employee and the position, while the primary key ensures a combination of employee and position only exists once.
This solution has two downsides:
It does not enforce that an employee has at least one position
It allows an employee to have more than two positions
The second problem is easily fixed by adding a trigger that checks if there is at most 1 position for an employee when attempting to insert (this allows a maximum of two):
create exception tooManyPositions 'Too many positions for employee';
set term #;
recreate trigger employeespositions_bi
active before insert on employeespositions
as
declare position_count integer;
begin
select count(*)
from employeespositions
where employee_id = new.employee_id
into position_count;
if (position_count > 1) then
exception tooManyPositions;
end#
set term ;#
However this solution does not enforce that an employee has at least one position. You could add a before delete trigger that ensures that the last position cannot be deleted, but that does not ensure that a newly created employee has at least one position. If you want to enforce that, you may want to consider using stored procedures for inserting and updating employees and their positions, and have the code of those stored procedures enforce that (eg by requiring a position when creating an employee).
Alternatively, you could also consider denormalizing your design, and making the positions part of the employees record, where the employee has a 'primary' and (optionally) a 'secondary' position.
create table employees (
-- using Firebird 3 identity column, change if necessary
id integer generated by default as identity primary key,
name varchar(100),
primary_position_id integer not null,
secondary_position_id integer,
constraint fk_employees_primary_position
foreign key (primary_position_id) references positions (id),
constraint fk_employees_secondary_position
foreign key (secondary_position_id) references positions (id),
constraint chk_no_duplicate_position
check (secondary_position_id <> primary_position_id)
)
The not null constraint on primary_position_id enforces the existence of this position, while the check constraint prevents assignment of the same position to both columns. Optionally you could consider adding a before insert or update trigger that when primary_position_id is set null, will set it to the value of secondary_position_id and sets secondary_position_id to null.
This solution has the advantage of allowing the enforcement of the existence of a primary position, but may lead to additional complexities when querying positions. This disadvantage can be overcome by creating a view:
create view employeespositions
as
select id as employee_id, primary_position_id as position_id
from employees
union all
select id as employee_id, secondary_position_id as position_id
from employees
where secondary_position_id is not null;
This view can then be used as if it is a table (although you can't insert into it).

How to update a row on parent table after child table has been changed

I have a simple DB which has two tables, serie and season.
Serie has this structure:
create table serie(
name varchar2(30) not null,
num_seasons number(2,0),
launch date,
constraint pk_serie primary key(name)
);
Whereas season has this other structure:
create table season(
name_serie varchar2(30) not null,
num_season number(2,0) not null,
launch date not null,
end date,
constraint pk_season primary key(name_serie,num_season),
constraint fk_season foreign key(name_serie) references serie(name),
constraint check_time check(launch<end)
);
For example, for a serie with two seasons (num_seasons=2), it would have in season table two rows, num_season=1 and num_season=2.
I would like the num_seasons column in table serie to be a count of how many rows are in season table with the name of the serie. In fact, I want that column to depend in changes in the season table, if you insert a new season of a serie, increase the num_seasons value by 1.
Thank you for your help :)
If you want to insert into table serie then do as below :
INSERT INTO serie (name,num_seasons,launch)
SELECT
name_serie
,COUNT(num_season)
,launch
FROM season
GROUP BY name_serie,launch
;
You can get the number of seasons using a sub-select. No need to store it in serie at all. You could make a view for this.
If you store the seasons anyway, storing the number of seasons is redundant information. You should avoid storing redundant information unless for specific performance reasons.
SELECT
s.name,
( SELECT count(*)
FROM season ss
WHERE ss.name_serie = s.name) as season_count
FROM
serie s
You should maybe review your model.
First you have the serie table where the primary key is the name. Which is not a good idea because the name is text and can change. Better is to set up an Id and the name as another field. Keep information related to the serie in serie and season in season.
Maybe what you need is another table called let's say episodes!
The order a serie is composed by seasons which are composed by episodes.
If you have many to many relatioship you need to add relation tables like serie_season and season_episodes. Then you can count esaily the number of season and episodes for each serie.
The others answer are simply wrong since they are telling you to perform an insert using a select to check how many season exists only once (at insert time).
What it would happens on Update/Delete on season table?
The answer is obvious, you will have all counter not aligned and the data will be unrielable.
For this purpose you have to modify the serie table, in particular:
num_season NUMBER(2,0) DEFAULT 0
and create some TRIGGER on season table:
Triggers are procedures that are stored in the database and are
implicitly run, or fired, when something happens.
Traditionally, triggers supported the execution of a PL/SQL block when
an INSERT, UPDATE, or DELETE occurred on a table or view. Triggers
support system and other data events on DATABASE and SCHEMA. Oracle
Database also supports the execution of PL/SQL or Java procedures.
CREATE TRIGGER incSeasonNum AFTER INSERT ON season
FOR EACH ROW
BEGIN
UPDATE serie SET num_seasons = num_seasons + 1
WHERE name = NEW.name_serie;
END
Another one in case for any rows deletion:
CREATE TRIGGER decSeasonNum AFTER DELETE ON season
FOR EACH ROW
BEGIN
UPDATE serie SET num_seasons = num_seasons - 1
WHERE name = OLD.name_serie;
END
And just to be sure to avoid strange update on serie's name or number:
CREATE TRIGGER incDecSeasonNum AFTER UPDATE ON season
FOR EACH ROW
BEGIN
UPDATE serie SET num_seasons = num_seasons - 1
WHERE name = OLD.name_serie;
UPDATE serie SET num_seasons = num_seasons + 1
WHERE name = NEW.name_serie;
END
Hope this help.

Why is a "references" privilege required?

In order to create a FOREIGN KEY Constraint, the <AuthorizationID> that owns the referencing Table must be the current <AuthorizationID> and must have the REFERENCES Privilege on every referenced Column named. Source: https://mariadb.com/kb/en/constraint_type-foreign-key-constraint/
WHY?
The reason for the references privilege is that the table referencing another imposes a constraint on the other table, and the owner of that other table may not be willing to allow that constraint.
Let's see what could happen if there was no references privilege.
CREATE TABLE Employees
(
EmpID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(32) NOT NULL,
…
);
INSERT INTO Employees VALUES (1, "Hernandez", …);
Now along comes Joe and creates a table:
CREATE TABLE EmployeeRefs
(
EmpID INTEGER NOT NULL PRIMARY KEY REFERENCES Employees
);
INSERT INTO EmployeeRefs SELECT EmpID FROM Employees;
Now suppose that the HR department needs to let Hernandez go and work elsewhere, and the code knows that the relevant EmpID is 1:
DELETE FROM Employees WHERE EmpID = 1;
This delete fails; the primary key value is still being referenced from the EmployeeRefs table.
This is what is supposed to happen as far as the DBMS is concerned. It was told that values in the EmployeeRefs.EmpID column must match a value in the Employee.EmpID column, and if it deletes Hernandez's record, that won't be true, so it must prevent the deletion (since Joe didn't set up a cascading delete).
Clearly, if any user (with privilege to create tables in the DB) can prevent the HR department from doing its work, there is a problem. The problem is resolved by saying that Joe can only create the reference to the Employee table if he has been given the REFERENCES privilege. And the owner/creator of each table gets to choose who has REFERENCES privilege for the tables that they create.