lookup and insert corresponding column values in oracle - oracle

I have two tables, ACTIVITY and EVENT. ACTIVITY has a list of all possible codes (as INT) in one column and their description in two other columns (REASON and CATEGORY). I'd like users to only enter CODE into EVENT table in the app front end but then perform a lookup to insert matching description columns into the same (EVENT) table.
CREATE TABLE EX.ACTIVITY
(
CODE INTEGER NOT NULL,
REASON VARCHAR2(40 BYTE) NOT NULL,
CATEGORY VARCHAR2(30 BYTE) NOT NULL,
)
CREATE TABLE EX.EVENTS
(
ID INTEGER NOT NULL,
CODE INTEGER NOT NULL,
REASON VARCHAR2(40 BYTE) NOT NULL,
CATEGORY VARCHAR2(30 BYTE) NOT NULL,
)
Thanks.(I'm sort of a newbie btw so forgive me if this is elementary)

You have an ACTIVITY table that stores the attributes of ACTIVITY. Why store those attributes in another table (EVENTS)? That is a violation of what is referred to as normalization (second and third normal form). If you store the attributes of ACTIVITY in the EVENTS table, you've created a host of problems for yourself, such as redundant data (the reason and category are repeated in the events table needlessly). Just store the activity code in the events table! That way if you have to change the category for an activity later, you only need change the ACTIVITY table record corresponding to that code.
To display the values, use a JOIN in a SELECT statement:
SELECT e.id, e.code, a.reason, a.category
FROM events e JOIN activity a ON e.code = a.code
WHERE ...
A JOIN does exactly what it sounds like: it joins the records of the two tables where the condition(s) in the ON clause are satisfied. So, in this example, you'd get one row back for every pair of records in the event and activity tables that have matching code values. You are likely to have many events that have the same code; this is not a problem - each event row with that code will get the same values from the activity table for that code. This maintains the data integrity while allowing you to display the values from both tables in one select statement.

Related

Oracle - Unique constraint while allowing null values

I'm a bit new to PL-SQL coming from T-SQL.
I have a requirement that only one phone number is allowed per user ID, but the phone number column can be null as many times as required.
So table is:
User ID
Phone Number
1
NULL
1
9735152122
1
NULL
2
NULL
3
NULL
1
2124821212
It's that last one I need to block, although the first three are fine. In this case I'm talking about the sample table I've posted, not the actual table order. I just need to allow the NULLs through but block if there are duplicate phone numbers per a given User ID.
I've read about functional indexes but not sure exactly how to apply them here.
CREATE UNIQUE INDEX my_index ON my_table (
CASE WHEN phone_number IS NULL THEN NULL ELSE user_id END,
phone_number
)
With this logic, if phone_number is NULL, then both values in the index will be NULL, so that row will be excluded from the index. If phone_number is not NULL, then the row will be included in the index with the actual values for user_id and phone_number, and uniqueness will be enforced.
P.S. This is not "PL/SQL", it is Oracle SQL. PL/SQL is the procedural language used to write such things as triggers, functions, etc.

Create a generic DB table

I am having multiple products and each of them are having there own Product table and Value table. Now I have to create a generic screen to validate those product and I don't want to create validated table for each Product. I want to create a generic table which will have all the Products details and one extra column called ProductIdentifier. but the problem is that here in this generic table I may end up putting millions of records and while fetching the data it will take time.
Is there any other better solution???
"Millions of records" sounds like a VLDB problem. I'd put the data into a partitioned table:
CREATE TABLE myproducts (
productIdentifier NUMBER,
value1 VARCHAR2(30),
value2 DATE
) PARTITION BY LIST (productIdentifier)
( PARTITION p1 VALUES (1),
PARTITION p2 VALUES (2),
PARTITION p5to9 VALUES (5,6,7,8,9)
);
For queries that are dealing with only one product, specify the partition:
SELECT * FROM myproducts PARTITION FOR (9);
For your general report, just omit the partition and you get all numbers:
SELECT * FROM myproducts;
Documentation is here:
https://docs.oracle.com/en/database/oracle/oracle-database/12.2/vldbg/toc.htm

Oracle performance Issue

Need help query performance.
I have a table A joining to a view and it is taking 7 seconds to get the results. But when i do select query on view i get the results in 1 seconds.
I have created the indexes on the table A. But there is no improvements in the query.
SELECT
ITEM_ID, BARCODE, CONTENT_TYPE_CODE, DEPARTMENT, DESCRIPTION, ITEM_NUMBER, FROM_DATE,
TO_DATE, CONTACT_NAME, FILE_LOCATION, FILE_LOCATION_UPPER, SOURCE_LOCATION,
DESTRUCTION_DATE, SOURCE, LABEL_NAME, ARTIST_NAME, TITLE, SELECTION_NUM, REP_IDENTIFIER,
CHECKED_OUT
FROM View B,
table A
where B.item_id=A.itemid
and status='VALID'
AND session_id IN ('naveen13122016095800')
ORDER BY item_id,barcode;
CREATE TABLE A
(
ITEMID NUMBER,
USER_NAME VARCHAR2(25 BYTE),
CREATE_DATE DATE,
SESSION_ID VARCHAR2(240 BYTE),
STATUS VARCHAR2(20 BYTE)
)
CREATE UNIQUE INDEX A_IDX1 ON A(ITEMID);
CREATE INDEX A_IDX2 ON A(SESSION_ID);
CREATE INDEX A_IDX3 ON A(STATUS);'
So querying the view joined to a table is slower than querying the view alone? This is not surprising, is it?
Anyway, it doesn't make much sense to create separate indexes on the fields. The DBMS will pick one index (if any) to access the table. You can try a composed index:
CREATE UNIQUE INDEX A_IDX4 ON A(status, session_id, itemid);
But the DBMS will still only use this index when it sees an advantage in this over simply reading the full table. That means, if the DBMS expects to have to read a big amount of records anyway, it won't indirectly access them via the index.
At last two remarks concerning your query:
Don't use those out-dated comma-separated joins. They are less readable and more prone to errors than explicit ANSI joins (FROM View B JOIN table A ON B.item_id = A.itemid).
Use qualifiers for all columns when working with more than one table or view in your query (and A.status='VALID' ...).
UPDATE: I see now, that you are not selecting any columns from the table, so why join it at all? It seems you are merely looking up whether a record exists in the table, so use EXISTS or IN accordingly. (This may not make it faster, but a lot more readable at least.)
SELECT
ITEM_ID, BARCODE, CONTENT_TYPE_CODE, DEPARTMENT, DESCRIPTION, ITEM_NUMBER, FROM_DATE,
TO_DATE, CONTACT_NAME, FILE_LOCATION, FILE_LOCATION_UPPER, SOURCE_LOCATION,
DESTRUCTION_DATE, SOURCE, LABEL_NAME, ARTIST_NAME, TITLE, SELECTION_NUM, REP_IDENTIFIER,
CHECKED_OUT
FROM View
WHERE itemid IN
(
SELECT itemid
FROM A
WHERE status = 'VALID'
AND session_id IN ('naveen13122016095800')
)
ORDER BY item_id, barcode;

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.

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

Resources