Creating trigger for date of Birth - oracle

Please can someone enlighten me on how to create trigger for date of birth having upper limit of 60 and lower limit of 20. I am working on an assignment that requires constraint on staff age range for a shipping company on oracle 11g. thank you

Don't use triggers to enforce relational integrity, use constraints. That's what they're for.
You haven't troubled yourself to provide us with the table structure, so you will need to tweak this for your actual table.
alter table employees
add constraint minimum_age_ck check
(hire_date >= add_months(date_of_birth, 240);
alter table employees
add constraint maximum_age_ck check
(hire_date <= add_months(date_of_birth, 720);
Of course, if your employees table lacks a hire_date column you got a big problem.

Thanks. I finally used this
alter table staff
add constraint minimum_age_ck check
((dateOfBirth >= add_months(dateOfBirth, -18)));
and this
alter table staff
add constraint maximum_age_ck check
((dateOfBirth <= add_months(date_of_birth, 60)));
Will try inserting the values and check if the constraints are valid.
Meanwhile a check on the above answer by inserting values proved it was not valid. i also tried using this
ALTER TABLE STAFF
ADD CONSTRAINT minimum_age_ck
CHECK ( (months_between (sysdate, dateOfBirth) >= 240));
CHECK ( (months_between (sysdate, dateOfBirth) >= 240))
*
ERROR at line 3:
ORA-02436: date or system variable wrongly specified in CHECK constraint
Is sysdate not a valid usable comparison for months_between?

Related

Oracle constraint within a sequence

I'm trying to create a constraint on a column that it must be within a sequence (i.e. col_name < seq.maxval)
I tried to use a regular constraint, but the column doesn't have anything to tie to - it is just a sequence, not a column in a table.
Checks can't reference any kind of query, so I don't think that would work either.
ALTER TABLE STE_FILECOLL ADD (
CONSTRAINT STE_FC_CLFC_REF_STEF_IDFILE
FOREIGN KEY (CLFILECOLL)
REFERENCES ????
ENABLE VALIDATE
);
I expect there is a way to make sure that the values of a column are within a sequence, but an hour of reading documentation and duckduckgoing have been fruitless, so I turn to here.
In Oracle DB version 12c, a sequence's next value might be set as default for a column :
create table STE_FILECOLL
(
col0 int default seq1.nextval not null,
col_name int
);
and then check constraints might be added to provide the desired condition as :
alter table STE_FILECOLL
add constraint STE_CC_CLFC_REF_STEF_IDFILE
check (col_name < col0);
The only sequence pseudocolumns are nextval and currval, so you can't use that kind of syntax. Inline constraints are pretty limited. I think your best bet is to use a trigger.
[...]
select as.maxval into l_maxval from all_sequences where sequence_name = 'my_sequence';
if :new.col_name > l_maxval then
raise_application_error( -20001, 'too big' );
end if;

Why oracle sql not allow sysdate in table creation time for count age?

Query....
I want to try to make check constraints on birthdate and check age should be greater than 18.
Create table emp
(
Birthdate date,
Check( MONTHS_BETWEEN(SYSDATE,Birthdate))
);
Error on above query....why?
Anyone help me...
Why oracle sql not allow sysdate in table creation time for count age?
SYSDATE is not allowed because the constraint must be either "true" or "false" at any time you look at the data. If you were able to use SYSDATE in a check constraint, you could insert a row that satisfied the constraint at that time, but the constraint would be violated later. No good!
In your example, once the constraint is satisfied at insert time, it can't become "not satisfied" later. But here you are asking Oracle to think. It can't. It just doesn't allow you to use SYSDATE in constraints. Period.
Instead, you should write a simple trigger to do the check for you. Note that you are missing the comparison to 18 * 12 in your purported check constraint; MONTHS_BETWEEN may give some weird results in some cases; and it is always best to write code that mirrors your thinking: in this case the condition (in a trigger, not a check constraint) should be ***
sysdate >= birthdate + interval '18' year
*** EDIT: As Alex Poole points out below, adding INTERVAL to a date may sometimes be as weird as MONTHS_BETWEEN. The safe way to write the check is
sysdate >= add_months ( birthdate, 18 * 12 ) -- age >= 18 years or 18 * 12 months
(That is how I would write it - with the comment to explain the purpose, and 18 * 12.)
Maybe try:
SQL> create table person
(name varchar2(100),
dob date,
created_date date default sysdate not null,
constraint dob_check check
(
dob <= add_months(trunc(created_date), (12*18)*-1)
)
)
Table created.
SQL> insert into person(name,dob) values ('Bob', to_date('19740101','YYYYMMDD'))
1 row created.
SQL> commit
Commit complete.
SQL> insert into person(name,dob) values ('Jane', to_date('20050101','YYYYMMDD'))
insert into person(name,dob) values ('Jane', to_date('20050101','YYYYMMDD'))
Error at line 17
ORA-02290: check constraint (MYUSER.DOB_CHECK) violated
This is because of Oracle limitation. The reason is SYSDATE is non-deterministic. You can get a different result every time you call it. So the outcome (true/false) can (will) change over time. So Oracle can't guarantee that the expression is always true for every row.
See also https://asktom.oracle.com/pls/apex/asktom.search?tag=sysdate-in-check-constraints

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.

Oracle trigger that update record after insert

I want update book ant set amount-1, when after insert record to sell table.
create table book (
id number(3) not null,
name varchar(20),
author varchar(12),
amount number(3) not null,
constraint book_pk primary key(id)
);
create table sell (
id number(3) not null,
date varchar(20),
book_id number(3),
constraint sell_pk primary key(id)
);
I want after insert to table sell record update book table amount-1;
CREATE OR REPLACE TRIGER changes_amount_trigger
AFTER INSERT ON sell
FOR EACH ROW
BEGIN
UPDATE BOOK SET amount = amount-1 WHERE id = book_id
END;
I not know how to get inserted record book id, to update this record in book table.
Try like this,
CREATE OR REPLACE TRIGGER changes_amount_trigger
AFTER INSERT ON sell
FOR EACH ROW
BEGIN
UPDATE BOOK SET amount = amount-1 WHERE id = :new.book_id;
END;
/
Data Model Assumptions:
I am assuming you will register transactions by changing the data in the SELL table through INSERT DML SQL operations. This is also supported by your set up of a DML trigger on SELL to pass its changes as SALES information to the BOOK table. This is workable.
By accident, I tried setting up the trigger a little differently and I'd like to suggest a different approach:
Consider possibly working in the opposite direction: Change book quantities directly on the BOOK table, so a single purchase of book_id = 5 would handle queries that could:
UPDATE book SET amount = amount -1
WHERE id = 5; COMMIT;
Restocking would mean increasing the quantity of available books
by incrementing the AMOUNT value instead.
There are a few additional changes that might tighten up this two-table design and protect the integrity of the data within them for the longer term:
CREATE TABLE book (
id number(3) not null,
name varchar(20),
author varchar(12),
amount number(3) not null,
CONSTRAINT book_pk PRIMARY KEY(id)
);
ALTER TABLE book
ADD CONSTRAINT book_amt_ck CHECK (amount > 0);
ALTER TABLE book
ENABLE CONSTRAINT book_amt_ck;
To prevent negative book amount (quantity) values, a TABLE CHECK CONSTRAINT would prevent the entry of values by means of arithmetic errors in DML operations such as:
UPDATE book SET amount := amount - 1
In the example above, there is no control over decrementing the book inventory even if the quantity on hand has reached 0. Check out a few references on TABLE CHECK CONSTRAINTS to get a better understanding of what it can do for specific design situations.
Here are some design suggestions for the trigger:
Changes in book quantities should be the only triggering data element that affects the SELL table.
The trigger should account for changes in book quantities > 1.
CREATE OR REPLACE TRIGGER orders_after_update
AFTER UPDATE
ON book
FOR EACH ROW
DECLARE
v_amount number;
BEGIN
IF (:new.amount < :old.amount ) THEN
FOR v_amount in 1 .. (:old.amount - :new.amount)
LOOP
INSERT INTO sell (id, date, book_id)
VALUES (sell_seq.nextval, sysdate, :new.id);
COMMIT;
END LOOP;
END IF;
END;
For more information on triggers and their design, check a few instances to get a better understanding of how they are designed and set up.
CREATE SEQUENCE sell_seq
MINVALUE 1
START WITH 1
INCREMENT BY 1
CACHE 20;
We needed a sequence to populate the primary key/index of the SELL table. Oracle Sequences are useful for this purpose.
By watching the table changes with a trigger on the BOOK table, you can use the built in references which already exist when a table trigger fires. For example, BOOK.ID does not require an additional query because a trigger automatically is made aware of the beginning and ending value of each trigger monitored record.
Some useful discussions on triggers are discussed in more detail through an Internet search.
Setting Up a Foreign Key Relationship
Although the trigger will probably keep this relation clean, a Foreign Key relation between elements BOOK.ID and SELL.BOOK_ID would be good, otherwise queries on Sales transactions may yield book sales without any descriptive production information. The following is a reference on Foreign Keys and their use.
CREATE TABLE sell (
id number(3) not null,
date varchar(20),
book_id number(3)
);
ALTER TABLE table_name
ADD CONSTRAINT sell_fk
FOREIGN KEY (book_id)
REFERENCES book(id);
Do you already have records in sell table and want to update the amount in book table ?
if this is your case you can update your book.amount as following:
update book b
set b.amount = b.amount - (select count(*) from sell s where b.id = s.book_id);

Trigger for incrementing a date and inserting into another table

I want to create a trigger, in Oracle. When the dateOrdReceived in my order table is updated or inserted the trigger takes this date whatever it may be and updates it by 14 days into another table productList ordDateDelivery so that it equals to
dateOrdReceived + 14 days = new ordDateDelivery
I did have a couple of attempts and guessed I'd need a query which would join my two tables. I also learned that maybe using DATEADD would allow me add 14 days but altogether I can't quite get it right.
My trigger attempt
`CREATE OR REPLACE TRIGGER "PRODUCTLIST_DATE_DELIVERY"
BEFORE
insert or update on "PRODUCTLIST"
for each row
begin
select p.dateOrdRecieved, o.ordDateDelivery
from productList p JOIN orders o
ON p.ordID = o.ordID;
new.OrdDateDelivery := DATEADD(day,14,new.p.dateOrdRecieved)
end;
/
ALTER TRIGGER "PRODUCTLIST_DELIVERY_DATE" ENABLE
and my tables for this trigger are as follows
PRODUCTLIST TABLE
CREATE TABLE "PRODUCTLIST"
( "ORDID" NUMBER(3,0) NOT NULL ENABLE,
"PRODUCTID" NUMBER(3,0) NOT NULL ENABLE,
"QUANTITY" NUMBER(4,2) NOT NULL ENABLE,
"ORDDATEDELIVERY" DATE,
"DISCOUNT" NUMBER(3,0),
"TOTALCOST" NUMBER(4,2),
CONSTRAINT "PK_PRODUCTLIST" PRIMARY KEY ("ORDID", "PRODUCTID") ENABLE
)
/
ALTER TABLE "PRODUCTLIST" ADD CONSTRAINT "FK_ORDERS" FOREIGN KEY ("ORDID")
REFERENCES "ORDERS" ("ORDID") ENABLE
/
ALTER TABLE "PRODUCTLIST" ADD CONSTRAINT "FK_PRODUCTS" FOREIGN KEY ("PRODUCTID")
REFERENCES "PRODUCT" ("PRODUCTID") ENABLE
/
ORDERS TABLE
CREATE TABLE "ORDERS"
( "ORDID" NUMBER(3,0) NOT NULL ENABLE,
"DATEORDRECIEVED" DATE,
"CUSID" NUMBER(3,0) NOT NULL ENABLE,
PRIMARY KEY ("ORDID") ENABLE
)
/
ALTER TABLE "ORDERS" ADD CONSTRAINT "FK_CUSTOMER" FOREIGN KEY ("CUSID")
REFERENCES "CUSTOMER" ("CUSID") ENABLE
/
DATEADD() is not an Oracle function... Oracle's datetime arithmetic is based around the day. If you add 1 to a date it increments the date by one day, adding 1.5 by 36 hours etc.
Now, your trigger.
You can't automatically update or insert a record into another table. The trigger is "on" one table, which means you need to create the DML in order to add or update it into that table.
update productlist
set dateOrdRecieved = :new.OrdDateDelivery + 14
where ordid = :new.ordid
The :new. here references the new data of the table on which the trigger is on. It's a specific "variable" that you can access rather than a general concept of what you're trying to achieve. You can't use it to assign data to other tables directly, though you can use it as a means of doing so.
Next you need to consider where your trigger is. You're looking to update PRODUCTLIST whenever ORDERS is changed, this means that the trigger needs to be on the table ORDERS.
create or replace trigger productlist_date_delivery
before insert or update on orders
for each row
begin
update productlist
set OrdDateDelivery = :new.dateOrdRecieved + 14
where ordid = :new.ordid;
end;
/
Notice a few extra differences to your own:
I use :new. instead of new.
I'm not selecting from the table; there's no need to do this as the data is already available. It's also impossible as you're selecting data that Oracle's trying to update, it forbids this to ensure integrity.
I haven't used cased identifiers. There's no need to do this; Oracle upper-cases everything by default. It's also really painful if everything's not upper case as you have to remember
Every statement ends in a semi-colon.
If you're having problems I recommend Tech on the Net, it has a good basic guide. As always though, there's the documentation on the CREATE TRIGGER statement.

Resources