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

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.

Related

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);

Delete Cascade with Script

I have 3 tables,which are not created with the ON DELETE CASCADE option, nor is it an option to create them as such.
I may need to delete from all three tables in succession. Is there a way to do this using only the promotion_id as a key? Because I need to delete in reverse order, the promotion_id is gone by the time I get to the dependent tables.
I am thinking that the only way to do this is to SELECT the keys of the 3 tables using a JOIN, and then use them individually. But it would be nice if there was a pure SQL solution to it.
I am using JDBC, Spring, and Oracle. Thanks.
create table test_rates (
rate_id varchar2(10) primary key,
rate number
);
create table test_offers (
offer_id varchar2(10) primary key ,
rate_id varchar2(10),
foreign key (rate_id) references test_rates (rate_id)
);
create table test_promotions (
promotion_id varchar2(10) primary key ,
offer_id varchar2(10),
foreign key (offer_id) references test_offers (offer_id)
);
insert into test_rates (rate_id,rate) values (1,199);
insert into test_offers (offer_id,rate_id) values (11,1);
insert into test_promotions (promotion_id,offer_id) values (21,11);
commit;
delete from test_promotions where promotion_id = 21;
delete from test_offers where offer_id in (select offer_id from test_promotions where promotion_id = 1); -- key is gone by now
In the common case, when there are N promotions (N>1) for a single offer, it wouldn't make sense to delete the offer if one promotion only is deleted. You would end up with orphaned promotions.
If you want to delete a rate, it would make sense to start with deleting all children promotions then all children offers then delete the rate. In that case though, the rate_id could be used along the way.
If you delete a child record, there's no need to delete the parent record unless of course this is a requirement, in which case start with looking for the parent id and see above.

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.

Searching in the parent table with checkboxes (Oracle Forms Builder)

Where could I find any info on that.
a)I'll need to select which column is being searched
b)Select what data will be displayed with check boxes
c)If the selected column(in which you search) is the primary key column, then the form will also display the child table check boxes which, if checked, will display the columns along with the checked parent table columns.
The tables:
CREATE TABLE CHAMPIONS (
CNAME VARCHAR2(15) NOT NULL,
PRICELEVEL NUMBER(1) NOT NULL,
ROLE VARCHAR2(10) NOT NULL,
HPLEVEL NUMBER(2) NOT NULL,
ATKLEVEL NUMBER(2) NOT NULL,
MAGICLEVEL NUMBER(2) NOT NULL,
DIFFLEVEL NUMBER(2) NOT NULL
);
CREATE TABLE SKINS (
SNAME VARCHAR2(20) NOT NULL,
CNAME VARCHAR2(15) NOT NULL,
PRICELEVEL NUMBER(1) NOT NULL);
ALTER TABLE Champions ADD CONSTRAINT pk_Champions PRIMARY KEY (CNAME);
ALTER TABLE Skins ADD CONSTRAINT fk_Skins FOREIGN KEY (CNAME) REFERENCES champions(CNAME);
I don't really care how this is implemented since i don't know which way is possible. Since it's just a 1-1 relationship, some sort of hiding might work.
if the pressed button is cname(the primary key) the first result table will of course have only one row while the second result table will be populated by the data associated with the selected primary key. If any other button is pressed the other table isn't even displayed however the first one might have multiple rows displayed. The columns that are displayed in the results depend on the check boxes
If you don't want to repeat the champion data displayed, create two separated database blocks, and add a relation between them.
If you want to display all data in one multi-row block, you will need a view that joins the data from the two tables.
Your search options will be a non-database block. When you click in the search button, you will need to change the default_where of the blocks, adding the value searched according to the field. For this, you will need to add the trigger pre-query. Something like:
trigger of search button
begin
go_block('champions_block');
execute_query; -- this triggers pre-query of champions_block
end;
trigger pre-query
if :search.search_text is not null then
-- this changes the where of your block
set_block_property('champions_block',default_where, 'cname like %'||:search.search_text||'%');
--
else
-- remove previous where
set_block_property('champions_block',default_where, '');
end if;
The show / hide of fields is more complicated, you will need to show / hide the items using:
set_item_property('name_of_block.name_of_item', visible, property_true); -- false to hide
And also will need to adjust the position of the other fields:
set_item_property('name_of_block.name_of_item', position, pos_x, pos_y);

About restrictive record creation in a child table. (Oracle Forms Builder)

I created a form where i add records to a child table. However I don't want people to be able to edit the secondary key, only select it from the list of primary keys of the parent or something along those lines. How could i do that?
The tables:
CREATE TABLE CHAMPIONS (CNAME VARCHAR2(15) NOT NULL, PRICELEVEL NUMBER(1) NOT NULL, ROLE VARCHAR2(10) NOT NULL, HPLEVEL NUMBER(2) NOT NULL, ATKLEVEL NUMBER(2) NOT NULL, MAGICLEVEL NUMBER(2) NOT NULL, DIFFLEVEL NUMBER(2) NOT NULL);
CREATE TABLE SKINS (SNAME VARCHAR2(20) NOT NULL, CNAME VARCHAR2(15) NOT NULL, PRICELEVEL NUMBER(1) NOT NULL);
ALTER TABLE Champions ADD CONSTRAINT pk_Champions PRIMARY KEY (CNAME);
ALTER TABLE Skins ADD CONSTRAINT fk_Skins FOREIGN KEY (CNAME) REFERENCES champions(CNAME);
Too less information to be able to give an answer, but I can give it a try.
You can have LOVs created in the detail table and have that LOV driven off of the parent tables primary key, also the text item with which you would be linking the LOV you need to put a WHEN-VALIDATE-ITEM trigger with the same query as in the LOVs RECORD GROUP so that in case the user types instead of using the LOV you can validate the input and then alert them in case of invalid input and fetch the correct master primary key in case of correct value entered.
Below is how the datablock with the LOV buttons look like-
And below is the Trigger on the STORE item in the datablock-
You can code the RG similarly as the query in the trigger considering your master table in the query.
The simplest way to get this functionality is to create a LOV based on champions.cname and attach it to item form column skins.cname (Please refer the Forms help if you are not familiar with creating LOV:s). Then set the item property 'Validate from list' of item skins.cname to Yes. This will validate all entered values against the existing cname values.

Resources