Creating Triggers to check conflicts - oracle

My task is to check there are no conflicts with day and time when you insert or update a group fitness class on a customer timetable. Can someone please help me out. These are the tables I've got:
Name Null Type
------------- -------- ------------
CUSTOMER_T_ID NOT NULL NUMBER(38)
C_DATE NOT NULL TIMESTAMP(6)
TIMETABLE_ID NOT NULL NUMBER(38)
CUSTOMER_ID NOT NULL NUMBER(38)
Desc Timetable
Name Null Type
----------------- -------- ------------
TIMETABLE_ID NOT NULL NUMBER(38)
CLASS_DAY NOT NULL VARCHAR2(50)
CLASS_LOCATION NOT NULL VARCHAR2(50)
CLASS_START_TIME NOT NULL TIMESTAMP(6)
CLASS_FINISH_TIME NOT NULL TIMESTAMP(6)
WORKOUT_CLASS_ID NOT NULL NUMBER(38)
TRAINER_ID NOT NULL NUMBER(38)
Desc Workout_class
Name Null Type
---------------- -------- -------------
WORKOUT_CLASS_ID NOT NULL NUMBER(38)
NAME NOT NULL VARCHAR2(20)
WORKOUT_TYPE NOT NULL VARCHAR2(200)
EQUIPMENT_USED NOT NULL VARCHAR2(200)
RESULTS NOT NULL VARCHAR2(200)
COST NOT NULL FLOAT(126)
INTENSITY_LEVEL NOT NULL VARCHAR2(50)
CLASS_DURATION NOT NULL NUMBER(38)
Desc Customers
Name Null Type
---------------- -------- -------------
CUSTOMER_ID NOT NULL NUMBER(38)
FIRST_NAME NOT NULL VARCHAR2(50)
LAST_NAME NOT NULL VARCHAR2(50)
AGE NOT NULL NUMBER(38)
ADDRESS NOT NULL VARCHAR2(100)
CITY NOT NULL VARCHAR2(50)
MOBILE_PHONE NOT NULL NUMBER(10)
EMAIL VARCHAR2(50)
PICTURE BFILE()
CUSTOMER_TYPE_ID NOT NULL NUMBER(5)
Trigger is compiling but when i try to insert the same customer to the same timetable he is already into its not raising an error but the row is inserted
create or replace trigger timetableconflict
before insert or update on customer_timetable
for each row
begin
if :new.customer_T_ID = :old.Customer_T_ID and :new.Customer_ID = :old.Customer_ID
then
raise_application_error(-20000,'customer cannot enrol into same class again');
end if;
end;

Your trigger is comparing the OLD and NEW pseudorecords. When you insert a new row there is no OLD row in the table so all the fields in that record are null; your comparison therefore doesn't match. (You can't compare anything with null but that's a separate discussion). The condition you've used can never be matched on insert. When you update you're comparing the existing value for the field in the row being updated, with the new value you're updating it too; so you're currently saying that an update must change the customer_t_id and customer_id. Aside from anything else it looks like customer_t_id is probably supposed to be a primary key, so changing that would normally not be a good idea.
So I think you're misunderstanding what OLD represents. It looks like the customer_t_id reference is a mistake and you're trying to make sure there is not already a record for the customer in the table - but OLD does not tell you that as it only refers to the current row affected by the statement, not any other rows in the table.
I don't think that's actually your end goal from what you said in the question - I think you don't want a customer to have the same timetable twice.
Given that OLD doesn't do what you want, you might think about querying the table to see if such a row already exists. But you can't (easily) do that in a trigger; if you're inserting or updating more than one row at a time then you'll get a mutating table error. It's easy to abuse triggers by trying to make them do something they weren't designed for.
The normal way to enforce that uniqueness is with a unique constraint (or "unique composite key") rather than a trigger:
alter table customer_timetable
add constraint timetableconflict unique (customer_id, timetable_id);
That will also create a unique index to back it up; you could just create a unique index directly but declaring a constraint is more explicit. (It's not a great constraint name but I've just copied what you were going to call the trigger).
You can read more about how triggers and constraints differ:
Constraints are easier to write and less error-prone than triggers that enforce the same rules. However, triggers can enforce some complex business rules that constraints cannot. Oracle strongly recommends that you use triggers to constrain data input only in these situations:
To enforce referential integrity when child and parent tables are on different nodes of a distributed database
To enforce complex business or referential integrity rules that you cannot define with constraints
What you're describing doesn't fall into those categories.

Related

Oracle Database Can set Constraint for Upper Case Values?

Is there anyway that we can set a constraint in database table level to have upper or lower case values for certain columns? When we create a table, we can set NOT NULL to avoid having null values on a column. Same way, can we do that for either uppercase or lower case?
You can do that using a check constraint:
create table foo
(
only_lower varchar(20) not null check (lower(only_lower) = only_lower),
only_upper varchar(20) not null check (upper(only_upper) = only_upper)
);
I had almost same case, tried with check constraint, but if the user is not mentioning it as UPPER() or LOWER() it gives error so I took TRIGGER route as below code.
--creating table
create table user_name (
first_name varchar2(50),
last_name varchar2(50));
--creating trigger
CREATE OR REPLACE TRIGGER TRG_USER_NAME_IU
BEFORE INSERT OR UPDATE ON USER_NAME
FOR EACH ROW
BEGIN
:NEW.FIRST_NAME := UPPER(:NEW.FIRST_NAME);
:NEW.LAST_NAME := UPPER(:NEW.LAST_NAME);
END;
/
Can test and share feedback or comments

Contraint to set one column as the sum of two others automatically

I'm wondering is it possible to use a constraint to set the value of one column to be sum of two others. For example given the following tables:
CREATE TABLE Room (
Room_Num NUMBER(3),
Room_Band_ID NUMBER(2),
Room_Type_ID NUMBER(2),
Room_Price NUMBER(4),
PRIMARY KEY (Room_Num),
FOREIGN KEY(Room_Band_ID)
REFERENCES Room_Band(Room_Band_ID),
FOREIGN KEY(Room_Type_ID)
REFERENCES Room_Type(Room_Type_ID)
);
CREATE TABLE Booking (
Booking_ID NUMBER(10) NOT NULL,
GuestID NUMBER(4) NOT NULL,
StaffID NUMBER(2) NOT NULL,
Payment_ID NUMBER(4) NOT NULL,
Room_Num NUMBER(3) NOT NULL,
CheckInDate DATE NOT NULL,
CheckOutDate DATE NOT NULL,
Booking NUMBER(2) NOT NULL,
Price NUMBER(4),
PRIMARY KEY (Booking_ID),
FOREIGN KEY(GuestID)
REFERENCES Guest(GuestID),
FOREIGN KEY(StaffID)
REFERENCES Staff(StaffID),
FOREIGN KEY(Payment_ID)
REFERENCES Payment(Payment_ID),
FOREIGN KEY(Room_Num)
REFERENCES Room(Room_Num)
);
I know it is possible to do something like:
Constraint PriceIs CHECK (Booking.Price=(Room.Room_Price*
(Booking.CheckOutDate - Booking.CheckInDate)));
Is it also possible to set up a constraint that doesn't just ensure that the price is correct, but to calculate the price automatically into the price field for the relevant tuple?
Update,
So I've tried to set up a trigger as follows:
CREATE OR REPLACE trigger PriceCompute
AFTER INSERT ON Booking
FOR each row
BEGIN
UPDATE Booking
SET
SELECT (Room.Room_Price*(Booking.CheckOutDate - Booking.CheckInDate))
INTO
Booking.Price
FROM Booking
JOIN ROOM ON Booking.Room_Num = Room.Room_Num
END;
/
But I'm getting the following errors back:
Can anyone see where I'm going astray here, as its beyond me.
Yes, you can. Here are your options. Listed in order of my personal preference:
You can have a table without this column. And create a view that will be calculating this column on a fly.
You may use oracle virtual columns
create table Room (
...
price NUMBER GENERATED ALWAYS AS (room_price*(checkOut-checkIn)) VIRTUAL,
...)
You may use actual column (same as 2, per Dave Costa):
create table Room (
...
price AS (room_price*(checkOut-checkIn)),
...)
You can write trigger to populate it (like Mat M suggested)
You can write stored procedure, but it will be an overkill in this situation
I think you would have to put a trigger on both tables for whenever the price value of the room is changed or the checkout/in dates are changed, it will update the PriceIs field from your calculation.
If you don't need the calculated portion stored in an actual field, you can always create a view that calculates it whenever you look at the view.
I think the better solution is to use a view that calculates the value on the fly. But regarding your attempt to create a trigger, you should use :new.<column_name> to refer to the values being inserted into the Booking table. You don't need to perform updates and queries on that table to get or modify the values in the row that is being inserted*. You just refer to them as variables. So you would want to do something like:
SELECT (Room.Room_Price*(:new.CheckOutDate - :new.CheckInDate))
INTO
:new.Price
FROM ROOM WHERE :new.Room_Num = Room.Room_Num
*In fact, you can't perform queries or updates on the table whose modification invoked the trigger in the first place. You would get the infamous "mutating table" error if your trigger actually compiled and ran.

"Create table as select" does not preserve not null

I am trying to use the "Create Table As Select" feature from Oracle to do a fast update. The problem I am seeing is that the "Null" field is not being preserved.
I defined the following table:
create table mytable(
accountname varchar2(40) not null,
username varchar2(40)
);
When I do a raw CTAS, the NOT NULL on account is preserved:
create table ctamytable as select * from mytable;
describe ctamytable;
Name Null Type
----------- -------- ------------
ACCOUNTNAME NOT NULL VARCHAR2(40)
USERNAME VARCHAR2(40)
However, when I do a replace on accountname, the NOT NULL is not preserved.
create table ctamytable as
select replace(accountname, 'foo', 'foo2') accountname,
username
from mytable;
describe ctamytable;
Name Null Type
----------- ---- -------------
ACCOUNTNAME VARCHAR2(160)
USERNAME VARCHAR2(40)
Notice that the accountname field no longer has a null, and the varchar2 field went from 40 to 160 characters. Has anyone seen this before?
This is because you are no longer selecting ACCOUNTNAME, which has a column definition and meta-data. Rather you are selecting a STRING, the result of the replace function, which doesn't have any meta-data. This is a different data type entirely.
A (potentially) better way that might work is to create the table using a query with the original columns, but with a WHERE clause that guarantees 0 rows.
Then you can insert in to the table normally with your actual SELECT.
By having query of 0 rows, you'll still get the column meta-data, so the table should be created, but no rows will be inserted. Make sure you make your WHERE clause something fast, like WHERE primary_key = -999999, some number you know would never exist.
Another option here is to define the columns when you call the CREATE TABLE AS SELECT. It is possible to list the column names and include constraints while excluding the data types.
An example is shown below:
create table ctamytable (
accountname not null,
username
)
as
select
replace(accountname, 'foo', 'foo2') accountname,
username
from mytable;
Be aware that although this syntax is valid, you cannot include the data type. Also, explicitly declaring all the columns somewhat defeats the purpose of using CREATE TABLE AS SELECT.

ORA-00907: missing right parenthesis Error while creating a table?

I am new to oracle,
I have created two tables using following queries,
CREATE TABLE employee
(
emp_name VARCHAR(20) NOT NULL,
street VARCHAR(50) NOT NULL,
city VARCHAR(20) NOT NULL,
PRIMARY KEY(emp_name)
)
and
CREATE TABLE company
(
comp_name VARCHAR(20) NOT NULL,
city VARCHAR(20) NOT NULL,
PRIMARY KEY(comp_name)
)
Now I am trying to create another table using some foreign keys,
CREATE TABLE works
(
emp_name varchar(20) NOT NULL,
comp_name varchar(20) NOT NULL,
salary int(10) NOT NULL,
FOREIGN KEY(emp_name) REFERENCES employee(emp_name),
FOREIGN KEY(comp_name) REFERENCES company(comp_name)
)
Getting ERROR : ORA-00907: missing right parenthesis
I have also tried with
CREATE TABLE works
(
emp_name varchar(20) NOT NULL,
comp_name varchar(20) NOT NULL,
salary int(10) NOT NULL,
constraint wemployee FOREIGN KEY(emp_name) REFERENCES employee(emp_name),
constraint wcompany FOREIGN KEY(comp_name) REFERENCES company(comp_name)
)
But getting same error.
Can any one tell me that where I am doing mistake?
I'm no expert in oracle, but are you allowed to specify the (10) in salary int(10) NOT NULL?
1: you should have a table called "test" with two columns, id and testdata. (This is just a dumb quick example, so I won't bother to specify any constraints on id.)
create table test (id number, testdata varchar2(255));
2: Next we'll create a sequence to use for the id numbers in our test table.
create sequence test_seq
start with 1
increment by 1
nomaxvalue;
You could change "start with 1" to any number you want to begin with (e.g. if you already have 213 entries in a table and you want to begin using this for your 214th entry, replace with "start with 214"). The "increment by 1" clause is the default, so you could omit it. You could also replace it with "increment by n" if you want it to skip n-1 numbers between id numbers. The "nomaxvalue" tells it to keep incrementing forever as opposed to resetting at some point.i (I'm sure Oracle has some limitation on how big it can get, but I don't know what that limit is).
3: Now we're ready to create the trigger that will automatically insert the next number from the sequence into the id column.
create trigger test_trigger
before insert on test
for each row beginselect test_seq.nextval into :new.id from dual;
end;
/
There are two different ways to create a table with constraints:
1)
create table department(
deptno number(5) primary key,
deptname varchar2(30),
empno number(5) references emp(empno));
2)
create table department(
deptno number(5),
deptname varchar2(30),
empno number(5),
constraint pkey_deptno primary key(deptno),
constraint fkey_empno foreign key(empno) references Emp(empno));
When creating the index inline with the rest of the table creation statement try dropping the FOREIGN KEY part:
CREATE TABLE works
(
emp_name varchar(20) NOT NULL,
comp_name varchar(20) NOT NULL,
salary int(10) NOT NULL,
emp_name REFERENCES employee(emp_name),
comp_name REFERENCES company(comp_name)
)
See this question for more details:
ORA-00907: missing right parenthesis

Oracle trigger to update an entity in different table

So this is from a uni coursework and is my first time working with Oracle (and using triggers). We are supposed to be creating a database for an airlines.
Part of the database is
CREATE TABLE FLIGHT_BOOKING (
BOOKING_ID NUMBER(11) PRIMARY KEY,
BOOKING_TIME DATE NOT NULL,
EMPLOYEE_ID NUMBER(11) NOT NULL,
FLIGHT_ID NUMBER(11) NOT NULL,
TOTAL_COST NUMBER(4,2) NOT NULL
);
CREATE TABLE FLIGHT (
FLIGHT_ID NUMBER(11) PRIMARY KEY,
PLANE_ID NUMBER(11) NOT NULL,
START_ID NUMBER(11) NOT NULL,
DESTINATION_ID NUMBER(11) NOT NULL,
TRANSIT_ID NUMBER(11),
DEPARTURE_TIME DATE NOT NULL,
ARRIVAL_TIME DATE NOT NULL,
NUM_BOOKED NUMBER (4) NOT NULL
);
CREATE TABLE PASSENGER (
PASSENGER_ID NUMBER(11) PRIMARY KEY,
FIRST_NAME VARCHAR2(20) NOT NULL,
MIDDLE_NAME VARCHAR2(20) NULL,
LAST_NAME VARCHAR2(20) NOT NULL,
TELEPHONE NUMBER(11) NOT NULL,
BOOKING_ID NUMBER(11) NOT NULL
);
So what I want to do is create a trigger such that every time a new passenger is added to the PASSENGER table, the trigger finds the corresponding FLIGHT_ID from the FLIGHT_BOOKING table and increments NUM_BOOKED for the corresponding flight in the FLIGHT table.
I have tried going through the oracle documentation, but i could not find anything that describes a situation where two or more tables are concerned.
Any help would be really appreciated!
you can do it like this:
CREATE OR REPLACE TRIGGER update_flight_booking_info
AFTER INSERT ON PASSENGER
FOR EACH ROW
DECLARE
v_flight_id number;
v_booking_id number;
BEGIN
v_booking_id := :new.booking_id ;
select flight_id into v_flight_id
from flight_booking
where booking_id = v_booking_id;
update flight
set NUM_BOOKED = NUM_BOOKED + 1
where flight_id = v_flight_id;
END;
HTH.
I'd rather not store that number, and calculate it as needed, but okay, it is just course material. :)
When you create a trigger, inside it you can put all kinds of code, including update statements.
So you can write a trigger like this:
create or replace trigger TIDB_BOOKING
before insert or delete
for each row
declare
V_Increment int;
begin
-- Inc or dec, depending on insert or update.
-- Hasn't a booking got a number of seats?
-- Also, can bookings be updated/moved to other flights?
-- These problems aren't yet taken into account in this code.
V_Increment := 1;
if deleting then
V_Increment := -1;
update FLIGHT f
set f.NUM_BOOKED = f.NUM_BOOKED + V_Increment
where f.FLIGHT_ID = nvl(:new.FLIGHT_ID, :old.FLIGHT_ID);
end;

Resources