Integrity constraint for tables not immediately related - sql

I am a SQL beginner and I can't figure out how to properly create an integrity constraint for situations like this:
The schema describes a delivery system - each Restaurant offers some items, which can be delivered to customers (outside the visible schema).
The problem comes with the in_delivery table - items from menu are registered with the delivery through this table. With the current state of things, it is possible to add a menu_item to a delivery which is done by a restaurant, but that restaurant may not offer the menu_item!
When inserting into in_delivery, I need to somehow check, if the Menu_Item_MenuItem_ID is present in offers, that has Restaurant_RestaurantID equal to RestaurantID in Delivery associated with the table.
I don't know if I can use a foreign key here, because the tables are not "adjacent"..
What comes into mind is to have a RestaurantID in in_delivery, that would be a foreign key both to Restaurant and Delivery. Then I could find that in offers. Is there a better way?
Thanks for your help

You could enforce your constraint with the following changes:
add the restaurant_id column in the in_delivery table
add a unique constraint on delivery (delivery_id, restaurant_id) (needed for 3.)
change the foreign key from in_delivery -> delivery to point to (delivery_id, restaurant_id)
change the foreign key from in_delivery -> menu_item to in_delivery -> offers

Alternatively you can use a trigger to check the constraint:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE Restaurants (
RestaurantID NUMBER(2) PRIMARY KEY,
Name VARCHAR2(30) NOT NULL
)
/
INSERT INTO Restaurants
SELECT 1, 'Soylent Green Express' FROM DUAL
UNION ALL SELECT 2, 'Helga''s House of Ribs' FROM DUAL
/
CREATE TABLE Menu_Items (
Menu_Item_ID NUMBER(2) PRIMARY KEY,
Name VARCHAR2(20) NOT NULL
)
/
INSERT INTO Menu_Items
SELECT 1, 'Soylent Green' FROM DUAL
UNION ALL SELECT 2, 'Ribs' FROM DUAL
/
CREATE TABLE Offers (
RestaurantID NUMBER(2),
Menu_Item_ID NUMBER(2),
PRIMARY KEY ( RestaurantID, Menu_Item_ID ),
FOREIGN KEY ( RestaurantID ) REFERENCES Restaurants ( RestaurantID ),
FOREIGN KEY ( Menu_Item_ID ) REFERENCES Menu_Items ( Menu_Item_ID )
)
/
INSERT INTO Offers
SELECT 1, 1 FROM DUAL
UNION ALL SELECT 2, 2 FROM DUAL
/
CREATE TABLE Deliveries (
RestaurantID NUMBER(2) NOT NULL,
Delivery_ID NUMBER(2) PRIMARY KEY,
FOREIGN KEY ( RestaurantID ) REFERENCES Restaurants ( RestaurantID )
)
/
INSERT INTO Deliveries
SELECT 1, 1 FROM DUAL
UNION ALL SELECT 2, 2 FROM DUAL
/
CREATE TABLE in_delivery (
Delivery_ID NUMBER(2),
Menu_Item_ID NUMBER(2),
PRIMARY KEY ( Delivery_ID, Menu_Item_ID ),
FOREIGN KEY ( Delivery_ID ) REFERENCES Deliveries ( Delivery_ID ),
FOREIGN KEY ( Menu_Item_ID ) REFERENCES Menu_Items ( Menu_Item_ID )
)
/
Just for ease of reading I've created two useful functions (you would probably want some exception handling in them):
CREATE OR REPLACE FUNCTION get_Delivery_RestaurantID (
p_Delivery_ID Deliveries.Delivery_ID%TYPE
) RETURN Restaurants.RestaurantID%TYPE
AS
v_RestaurantID Restaurants.RestaurantID%TYPE;
BEGIN
SELECT RestaurantID
INTO v_RestaurantID
FROM Deliveries
WHERE Delivery_ID = p_Delivery_ID;
RETURN v_RestaurantID;
END get_Delivery_RestaurantID;
/
CREATE OR REPLACE FUNCTION does_Restaurant_Offer_Item (
p_RestaurantID Restaurants.RestaurantID%TYPE,
p_Menu_Item_ID Menu_Items.Menu_Item_ID%TYPE
) RETURN NUMBER
AS
v_exists NUMBER(1);
BEGIN
SELECT CASE WHEN EXISTS ( SELECT 1
FROM Offers
WHERE RestaurantID = p_RestaurantID
AND Menu_Item_ID = p_Menu_Item_ID
)
THEN 1
ELSE 0
END
INTO v_exists
FROM DUAL;
RETURN v_exists;
END does_Restaurant_Offer_Item;
/
Then just add a trigger to the table to check that the Restaurant offers the item and, if not, raise an exception.
CREATE TRIGGER check_Valid_Delivery_Item
BEFORE INSERT OR UPDATE OF Delivery_ID, Menu_Item_ID
ON in_delivery
FOR EACH ROW
BEGIN
IF does_restaurant_Offer_Item( get_Delivery_RestaurantID( :new.Delivery_ID ), :new.Menu_Item_ID ) = 0
THEN
RAISE_APPLICATION_ERROR (-20100, 'Invalid Delivery Item');
END IF;
END check_Valid_Delivery_Item;
/
INSERT INTO in_delivery VALUES( 1, 1 )
/
INSERT INTO in_delivery VALUES( 2, 2 )
/
Query 1:
SELECT * FROM in_delivery
Results:
| DELIVERY_ID | MENU_ITEM_ID |
|-------------|--------------|
| 1 | 1 |
| 2 | 2 |
If you try to do:
INSERT INTO in_delivery VALUES( 1, 2 );
Then you get:
ORA-20100: Invalid Delivery Item ORA-06512: at "USER_4_F9593.CHECK_VALID_DELIVERY_ITEM", line 4 ORA-04088: error during execution of trigger 'USER_4_F9593.CHECK_VALID_DELIVERY_ITEM' : INSERT INTO in_delivery VALUES( 1, 2 )

Related

How to create an Oracle audit trigger?

CREATE OR REPLACE TRIGGER EVALUATION
BEFORE INSERT OR UPDATE OR DELETE ON BOOKING
FOR EACH ROW
DECLARE
BEGIN
SELECT BOOKING_EVALUATION FROM BOOKING WHERE BOOKING_EVALUATION > 2;
SELECT BOOKING_EVALUATION INTO EVALUATIONAUDIT FROM BOOKING;
IF INSERTING THEN
INSERT INTO EVALUATIONAUDIT (VOYAGES_ID,CUSTOMER_NAME,START_DATE,SHIP_NAME,BOOKING_EVALUATION)
VALUES(:NEW.VOYAGES_ID,:NEW.CUSTOMER_NAME,:NEW.START_DATE,:NEW.SHIP_NAME,:NEW.BOOKING_EVALUATION);
END IF;
IF UPDATING THEN
INSERT INTO EVALUATIONAUDIT (VOYAGES_ID,CUSTOMER_NAME,START_DATE,SHIP_NAME,BOOKING_EVALUATION)
VALUES(:OLD.VOYAGES_ID,:OLD.CUSTOMER_NAME,:OLD.START_DATE,:OLD.SHIP_NAME,:OLD.BOOKING_EVALUATION);
END IF;
IF DELETING THEN
INSERT INTO EVALUATIONAUDIT (VOYAGES_ID,CUSTOMER_NAME,START_DATE,SHIP_NAME,BOOKING_EVALUATION)
VALUES(:OLD.VOYAGES_ID,:OLD.CUSTOMER_NAME,:OLD.START_DATE,:OLD.SHIP_NAME,:OLD.BOOKING_EVALUATION);
END IF;
END;
This is my audit table:
desc evaluationaudit;
Name Null? Type
----------------------------------------- -------- -----------------------
AUDITT_ID NOT NULL NUMBER(10)
VOYAGES_ID NOT NULL NUMBER(10)
CUSTOMER_NAME NOT NULL VARCHAR2(20)
START_DATE NOT NULL DATE
SHIP_NAME NOT NULL VARCHAR2(20)
BOOKING_EVALUATION NOT NULL NUMBER(20)
and this is my show error output:
SQL> SHOW ERRORS;
Errors for TRIGGER EVALUATION:
LINE/COL ERROR
-------- -------------------------------------------------------------
12/24 PLS-00049: bad bind variable 'NEW.CUSTOMER_NAME'
12/43 PLS-00049: bad bind variable 'NEW.START_DATE'
12/59 PLS-00049: bad bind variable 'NEW.SHIP_NAME'
18/24 PLS-00049: bad bind variable 'OLD.CUSTOMER_NAME'
18/43 PLS-00049: bad bind variable 'OLD.START_DATE'
18/59 PLS-00049: bad bind variable 'OLD.SHIP_NAME'
24/24 PLS-00049: bad bind variable 'OLD.CUSTOMER_NAME'
24/43 PLS-00049: bad bind variable 'OLD.START_DATE'
24/59 PLS-00049: bad bind variable 'OLD.SHIP_NAME'
I wanted to add to the audit table. If a customer gives a poor evaluation of 2 or less, the details of their voyage (customer_name, name and date of the cruise, ship name and evaluation) but I'm getting error
Warning: Trigger created with compilation errors.
And the audit table is empty, showing no rows selected. I can't seem to find the problem where I am going wrong.
Maybe the following code snippets will help you. Suppose we have 2 tables: BOOKING, and EVALUATION_AUDIT (everything written in uppercase letters is taken from the code in your question).
Tables
create table booking (
VOYAGES_ID number primary key
, CUSTOMER_NAME VARCHAR2(20) NOT NULL
, START_DATE DATE NOT NULL
, SHIP_NAME VARCHAR2(20) NOT NULL
, BOOKING_EVALUATION NUMBER(20) NOT NULL
) ;
create table evaluationaudit (
AUDITT_ID number generated always as identity start with 5000 primary key
, trg_cond_pred varchar2( 64 ) default 'Row not added by evaluation trigger!'
, VOYAGES_ID NUMBER(10) NOT NULL
, CUSTOMER_NAME VARCHAR2(20) NOT NULL
, START_DATE DATE NOT NULL
, SHIP_NAME VARCHAR2(20) NOT NULL
, BOOKING_EVALUATION NUMBER(20) NOT NULL
) ;
Trigger
-- "updating" and "deleting" code omitted for clarity
CREATE OR REPLACE TRIGGER EVALUATION_trigger
BEFORE INSERT OR UPDATE OR DELETE ON BOOKING
FOR EACH ROW
BEGIN
case
when INSERTING then
if :new.booking_evaluation <= 2 then
INSERT INTO EVALUATIONAUDIT
( trg_cond_pred,
VOYAGES_ID, CUSTOMER_NAME, START_DATE, SHIP_NAME, BOOKING_EVALUATION )
VALUES (
'INSERTING'
, :NEW.VOYAGES_ID
, :NEW.CUSTOMER_NAME
, :NEW.START_DATE
, :NEW.SHIP_NAME
, :NEW.BOOKING_EVALUATION
);
end if ;
end case ;
END ;
/
Testing
One of your requirements (in your question) is:
I wanted to add to the audit table. If a customer gives a poor
evaluation of 2 or less, the details of their voyage (customer_name,
name and date of the cruise, ship name and evaluation)
delete from evaluationaudit ;
delete from booking ;
-- booking_evaluation greater than 2 -> no entry in audit table
insert into booking
( VOYAGES_ID, CUSTOMER_NAME, START_DATE, SHIP_NAME, BOOKING_EVALUATION )
values ( 1111, 'customer1', date '2018-05-24', 'ship1', 9999 ) ;
select * from evaluationaudit ;
-- no rows selected
-- booking_evalution = 2 -> insert a row into the audit table
insert into booking
( VOYAGES_ID, CUSTOMER_NAME, START_DATE, SHIP_NAME, BOOKING_EVALUATION )
values ( 1112, 'customer1', date '2018-05-24', 'ship1', 2 ) ;
select * from evaluationaudit ;
AUDITT_ID TRG_COND_PRED VOYAGES_ID CUSTOMER_NAME START_DATE SHIP_NAME BOOKING_EVALUATION
5000 INSERTING 1112 customer1 24-MAY-18 ship1 2
__Update__
If - as you wrote in your comment - you need to pull in some more data from other tables, maybe you want to try the following approach: keep the trigger code rather brief, and use it to call a procedure for the more complicated stuff eg
Tables
create table evaluationaudit (
AUDITT_ID number generated always as identity start with 7000 primary key
, trg_cond_pred varchar2( 64 ) default 'Row not added by evaluation trigger!'
, VOYAGES_ID NUMBER NOT NULL
, CUSTOMER_NAME VARCHAR2(20) NOT NULL
, SHIP_NAME VARCHAR2(20) NOT NULL
, BOOKING_EVALUATION NUMBER(20) NOT NULL
) ;
create table ships ( name varchar2( 64 ), id number unique ) ;
create table customers ( name varchar2( 64 ), id number unique ) ;
insert into ships ( name, id ) values ( 'ship1', 501 );
insert into ships ( name, id ) values ( 'ship2', 502 );
insert into ships ( name, id ) values ( 'ship3', 503 );
insert into customers ( name, id ) values ( 'customer1', 771 ) ;
insert into customers ( name, id ) values ( 'customer2', 772 ) ;
insert into customers ( name, id ) values ( 'customer3', 773 ) ;
create table bookings (
id number generated always as identity start with 5000 primary key
, voyagesid number
, shipid number
, customerid number
, evaluation number
, bookingdate date
);
Trigger
create or replace trigger bookingeval
before insert on bookings
for each row
when ( new.evaluation <= 2 ) -- Use NEW without colon here! ( see documentation )
begin
auditproc( :new.voyagesid, :new.customerid, :new.shipid, :new.evaluation ) ;
end ;
/
Procedure
create or replace procedure auditproc (
voyagesid_ number
, customerid_ number
, shipid_ number
, evaluation_ number
)
as
customername varchar2( 64 ) := '' ;
shipname varchar2( 64 ) := '' ;
begin
-- need to find the customername and shipname before INSERT
select name into customername from customers where id = customerid_ ;
select name into shipname from ships where id = shipid_ ;
insert into evaluationaudit
( trg_cond_pred,
voyages_id, customer_name, ship_name, booking_evaluation )
values (
'INSERTING'
, voyagesid_
, customername
, shipname
, evaluation_
);
end ;
/
Testing
-- evaluation > 2 -> no INSERT into evaluationaudit
insert into bookings
( voyagesid, customerid, shipid, evaluation, bookingdate )
values ( 1111, 771, 501, 9999, sysdate ) ;
select * from evaluationaudit ;
-- no rows selected
-- evaluation = 2
-- -> trigger calling audit procedure -> inserts into evaluationaudit
insert into bookings
( voyagesid, customerid, shipid, evaluation, bookingdate )
values ( 1112, 772, 502, 2, sysdate ) ;
select * from evaluationaudit ;
AUDITT_ID TRG_COND_PRED VOYAGES_ID CUSTOMER_NAME SHIP_NAME BOOKING_EVALUATION
7000 INSERTING 1112 customer2 ship2 2

Proper way to have foreign keys to subclasses in SQL?

A nurse orders medical supplies via a requisition to one of three different supplies, all supplied by a supplier.
Nurse > Requisition < Supplies (3 kinds) < Supplier
Since items can be one of three kinds and a requisition may not exist yet for an item, the requisition table has the foreign keys of the 3 supply types.
The issue: my correctly listed foreign keys all point to 3 different tables, all but one of which will not have a corresponding foreign key for each entry.
I get the following error:
ERROR at line 1: ORA-02091: transaction rolled back
ORA-02291: integrity constraint (MMM1339.ITEMNO_PHAR_FK) violated - parent key not found
CREATE TABLE SUPPLIER
(SUPPLIERNO INT,
SUPPLIERNAME VARCHAR2(100),
PHONENO VARCHAR2(12),
ADDRESS VARCHAR(100),
FAXNO VARCHAR(12),
CONSTRAINT SUPPLIERNO_SSPL_PK PRIMARY KEY(SUPPLIERNO));
CREATE TABLE SUPPLIES_PHARMACEUTICAL
(ITEMNO INT,
SUPPLIERNO INT,
NAME VARCHAR2(25),
DESCRIPTION VARCHAR2(25),
QUANTITYINSTOCK INT,
REORDERLEVEL INT,
COSTPERUNIT DECIMAL(6,2),
DOSAGE VARCHAR2(12),
CONSTRAINT ITEMNO_PHAR_PK PRIMARY KEY(ITEMNO));
CREATE TABLE SUPPLIES_SURGICAL
(ITEMNO INT,
NAME VARCHAR2(25),
DESCRIPTION VARCHAR2(25),
QUANTITYINSTOCK INT,
REORDERLEVEL INT,
COSTPERUNIT DECIMAL(6,2),
SUPPLIERNO INT,
CONSTRAINT ITEMNO_SUP_PK PRIMARY KEY(ITEMNO));
CREATE TABLE SUPPLIES_NONSURGICAL
(ITEMNO INT,
NAME VARCHAR2(25),
DESCRIPTION VARCHAR2(25),
QUANTITYINSTOCK INT,
REORDERLEVEL INT,
COSTPERUNIT DECIMAL(6,2),
SUPPLIERNO INT,
CONSTRAINT ITEMNO_NONSURG_PK PRIMARY KEY(ITEMNO));
CREATE TABLE STAFF_CHARGENURSE
(STAFFNO INT,
ADDRESS VARCHAR2(25),
POSITION VARCHAR2(12),
BUDGET DECIMAL(6,2),
SPECIALTY VARCHAR2(12),
CONSTRAINT STAFFNO_CHNURSE_PK PRIMARY KEY(STAFFNO));
CREATE TABLE REQUISITION
(REQNO INT,
STAFFNO INT,
STAFFNAME VARCHAR2(25),
WARDNO INT,
ITEMNO INT,
QUANTITY INT,
DATEORDERED DATE,
DATERECIEVED DATE,
CONSTRAINT REQ_PK PRIMARY KEY(REQNO));
Foreign keys:
ALTER TABLE SUPPLIES_PHARMACEUTICAL
ADD CONSTRAINT SUPPLIERNO_PHA_FK FOREIGN KEY(SUPPLIERNO) REFERENCES SUPPLIER(SUPPLIERNO)
DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE SUPPLIES_SURGICAL
ADD CONSTRAINT SUPPLIERNO_SURG_FK FOREIGN KEY(SUPPLIERNO) REFERENCES SUPPLIER(SUPPLIERNO)
DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE SUPPLIES_NONSURGICAL
ADD CONSTRAINT SUPPLIERNO_NONSURG_FK FOREIGN KEY(SUPPLIERNO) REFERENCES SUPPLIER(SUPPLIERNO)
DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE REQUISITION
ADD CONSTRAINT STAFFNO_REQ_FK FOREIGN KEY(STAFFNO) REFERENCES STAFF_CHARGENURSE(STAFFNO)
DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE REQUISITION
ADD CONSTRAINT ITEMNO_PHAR_FK FOREIGN KEY(ITEMNO) REFERENCES SUPPLIES_PHARMACEUTICAL(ITEMNO)
DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE REQUISITION
ADD CONSTRAINT ITEMNO_SURG_FK FOREIGN KEY(ITEMNO) REFERENCES SUPPLIES_SURGICAL(ITEMNO)
DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE REQUISITION
ADD CONSTRAINT ITEMNO_NONSURG_FK FOREIGN KEY(ITEMNO) REFERENCES SUPPLIES_NONSURGICAL(ITEMNO)
DEFERRABLE INITIALLY DEFERRED;
Test data:
INSERT INTO REQUISITION VALUES(1, 20, 'Julie Wood', 8, 888520, 2, '27-FEB-2018', '15-MAR-2018');
INSERT INTO REQUISITION VALUES(2, 20, 'Julie Wood', 8, 923956, 1, '25-FEB-2018', '28-FEB-2018');
INSERT INTO REQUISITION VALUES(3, 21, 'Sarah Michaels', 7, 054802, 3, '20-FEB-2018', '22-FEB-2018');
INSERT INTO SUPPLIES_PHARMACEUTICAL VALUES (823456, 100001, 'Zanax', 'Anti Depressant', 8, 2, 100.50, '50mg');
INSERT INTO SUPPLIES_PHARMACEUTICAL VALUES (923956, 100001, 'Zupridol', 'Blood Pressure Treatment', 12, 5, 50, '20mg');
INSERT INTO SUPPLIES_PHARMACEUTICAL VALUES (3952, 200001, 'Amibreezax', 'Artificial Ear Wax', 2, 1, 200, '5g');
INSERT INTO SUPPLIES_PHARMACEUTICAL VALUES (4955, 200001, 'Ambridax', 'Skin Treatment', 5, 10, 20, '2mg');
INSERT INTO SUPPLIES_SURGICAL VALUES (54802, 'Scalpel', 'Surgical Tool', 20, 10, 200.42, 100001);
INSERT INTO SUPPLIES_SURGICAL VALUES (634520, 'Stitches', 'Suture Tool', 100, 10, 2.50, 200001);
INSERT INTO SUPPLIES_NONSURGICAL VALUES (888520, 'Cart', '5ftx2ftx3ft', 2, 0, 200.00, 100001);
INSERT INTO SUPPLIES_NONSURGICAL VALUES (423, 'Tool Holder', 'Holds Inspection Equip.', 4, 2, 50.00, 100001);
INSERT INTO STAFF_CHARGENURSE VALUES(20, '32 Stark St. Portland, OR', 'Charge Nurse', 8000.99, 'Head Trauma');
INSERT INTO STAFF_CHARGENURSE VALUES(21, '18 Wilson Rd Portland, OR', 'Charge Nurse', 6000, 'Epidermus');
INSERT INTO SUPPLIER VALUES (100001,'Company A', '503-222-3333', '100 SE Stark Rd Portland, OR', '503-666-4444');
INSERT INTO SUPPLIER VALUES (200001,'Company B', '666-333-4444', '500 SE Bilerica Rd Akron, OH', '666-444-3333');
COMMIT;
As mentioned in responses to your other related questions, there is no superclass and no subclasses. SUPPLIES_PHARMACEUTICAL, SUPPLIES_SURGICAL and SUPPLIES_NONSURGICAL are just tables with no connection to each other.
All enabled constraints are enforced. Defining them as deferrable just puts off validation until the next commit, which doesn't really change anything in your example. If you define three constraints on a column, there is no syntax or mechanism to say 'only one of these constraints needs to be enforced', and I can't see how such a system would ever be workable.
In data modelling, you define subtypes using either
A single table (e.g. SUPPLIES) with a category or similar indicator column, or
A parent table having just the common columns, and child tables under it having just the type-specific columns, linked back to the parent via foreign key constraints. The child table can have a unique or primary key on the FK column, making it an optional 1:1 relationship.
Other tables can then have FK constraints referencing either the parent or one of its children, as required.
Here's your modified original code (UPPER case original, lower case: modifications), using #William Robertson's ideas: {1} using a single SUPPLIES table, {2} with a is_surgical column, and {3} a pharma_dosage table. Maybe you like it ...
CREATE TABLE SUPPLIER
(SUPPLIERNO integer,
SUPPLIERNAME VARCHAR2(100),
PHONENO VARCHAR2(12),
ADDRESS VARCHAR(100),
FAXNO VARCHAR(12),
CONSTRAINT SUPPLIERNO_SSPL_PK PRIMARY KEY(SUPPLIERNO));
-- one table instead of 3
create table supplies (
ITEMNO integer,
SUPPLIERNO integer,
NAME VARCHAR2(25),
DESCRIPTION VARCHAR2(25),
QUANTITYINSTOCK integer,
REORDERLEVEL integer,
COSTPERUNIT number(6,2),
is_surgical varchar2(1) not null,
constraint supplies_surgical_yn_chk check( is_surgical in ('Y','N') )
, constraint supplies_pk primary key( itemno )
, constraint supplies_fk foreign key( supplierno )
references supplier( supplierno )
);
create table pharma_dosage (
itemno integer
, dosage varchar2( 64 ) not null
, constraint pharma_supplies_fk foreign key( itemno )
references supplies( itemno )
, constraint pharma_supplies_pk primary key( itemno )
);
Two more tables, and - commented out - things we don't need:
-- not needed
-- CREATE TABLE SUPPLIES_PHARMACEUTICAL
-- CREATE TABLE SUPPLIES_SURGICAL
-- CREATE TABLE SUPPLIES_NONSURGICAL
CREATE TABLE STAFF_CHARGENURSE
(STAFFNO integer,
ADDRESS VARCHAR2(25),
POSITION VARCHAR2(12),
BUDGET DECIMAL(6,2),
SPECIALTY VARCHAR2(12),
CONSTRAINT STAFFNO_CHNURSE_PK PRIMARY KEY(STAFFNO));
CREATE TABLE REQUISITION
(REQNO integer,
STAFFNO integer,
STAFFNAME VARCHAR2(25),
WARDNO integer,
ITEMNO integer,
QUANTITY integer,
DATEORDERED DATE,
DATERECIEVED DATE,
CONSTRAINT REQ_PK PRIMARY KEY(REQNO),
constraint req_fk foreign key ( itemno ) references supplies ( itemno )
);
-- not needed
-- ALTER TABLE SUPPLIES_PHARMACEUTICAL ADD CONSTRAINT SUPPLIERNO_PHA_FK
-- ALTER TABLE SUPPLIES_SURGICAL ADD CONSTRAINT SUPPLIERNO_SURG_FK
-- ALTER TABLE SUPPLIES_NONSURGICAL ADD CONSTRAINT SUPPLIERNO_NONSURG_FK
ALTER TABLE REQUISITION ADD CONSTRAINT STAFFNO_REQ_FK
FOREIGN KEY(STAFFNO) REFERENCES STAFF_CHARGENURSE(STAFFNO) DEFERRABLE INITIALLY DEFERRED;
-- not needed
-- ALTER TABLE REQUISITION ADD CONSTRAINT ITEMNO_PHAR_FK
-- ALTER TABLE REQUISITION ADD CONSTRAINT ITEMNO_SURG_FK
-- ALTER TABLE REQUISITION ADD CONSTRAINT ITEMNO_NONSURG_FK
INSERTs
-- parents first
begin
INSERT INTO STAFF_CHARGENURSE VALUES(20, '32 Stark St. Portland, OR', 'Charge Nurse', 8000.99, 'Head Trauma');
INSERT INTO STAFF_CHARGENURSE VALUES(21, '18 Wilson Rd Portland, OR', 'Charge Nurse', 6000, 'Epidermus');
INSERT INTO SUPPLIER VALUES (100001,'Company A', '503-222-3333', '100 SE Stark Rd Portland, OR', '503-666-4444');
INSERT INTO SUPPLIER VALUES (200001,'Company B', '666-333-4444', '500 SE Bilerica Rd Akron, OH', '666-444-3333');
end;
/
PL/SQL procedure successfully completed.
More test data
begin
-- pharmaceutical
INSERT INTO SUPPLIES( itemno, supplierno, name, description, quantityinstock
, reorderlevel, costperunit, is_surgical )
VALUES (823456, 100001, 'Zanax', 'Anti Depressant', 8, 2, 100.50, 'N');
INSERT INTO SUPPLIES( itemno, supplierno, name, description, quantityinstock
, reorderlevel, costperunit, is_surgical )
VALUES (923956, 100001, 'Zupridol', 'Blood Pressure Treatment', 12, 5, 50, 'N');
INSERT INTO SUPPLIES( itemno, supplierno, name, description, quantityinstock
, reorderlevel, costperunit, is_surgical )
VALUES (3952, 200001, 'Amibreezax', 'Artificial Ear Wax', 2, 1, 200, 'N');
INSERT INTO SUPPLIES( itemno, supplierno, name, description, quantityinstock
, reorderlevel, costperunit, is_surgical )
VALUES (4955, 200001, 'Ambridax', 'Skin Treatment', 5, 10, 20, 'N');
-- pharma_dosage
insert into pharma_dosage ( itemno, dosage ) values ( 823456, '50mg' ) ;
insert into pharma_dosage ( itemno, dosage ) values ( 923956, '20mg' ) ;
insert into pharma_dosage ( itemno, dosage ) values ( 3952, '5g' ) ;
insert into pharma_dosage ( itemno, dosage ) values ( 4955, '2mg' ) ;
-- surgical
INSERT INTO SUPPLIES( itemno, supplierno, name, description, quantityinstock
, reorderlevel, costperunit, is_surgical )
VALUES (54802, 100001, 'Scalpel', 'Surgical Tool', 20, 10, 200.42, 'Y');
INSERT INTO SUPPLIES( itemno, supplierno, name, description, quantityinstock
, reorderlevel, costperunit, is_surgical )
VALUES (634520, 200001, 'Stitches', 'Suture Tool', 100, 10, 2.50, 'Y');
-- nonsurgical
INSERT INTO SUPPLIES( itemno, supplierno, name, description, quantityinstock
, reorderlevel, costperunit, is_surgical)
VALUES (888520, 100001, 'Cart', '5ftx2ftx3ft', 2, 0, 200.00, 'N');
INSERT INTO SUPPLIES( itemno, supplierno, name, description, quantityinstock
, reorderlevel, costperunit, is_surgical )
VALUES (423, 100001,'Tool Holder', 'Holds Inspection Equip.', 4, 2, 50.00, 'N');
-- requisition
INSERT INTO REQUISITION VALUES(1, 20, 'Julie Wood', 8, 888520, 2, '27-FEB-2018', '15-MAR-2018');
INSERT INTO REQUISITION VALUES(2, 20, 'Julie Wood', 8, 923956, 1, '25-FEB-2018', '28-FEB-2018');
INSERT INTO REQUISITION VALUES(3, 21, 'Sarah Michaels', 7, 054802, 3, '20-FEB-2018', '22-FEB-2018');
end ;
/
PL/SQL procedure successfully completed.
Now, some SELECTs ...
SQL> select * from supplier ;
SUPPLIERNO SUPPLIERNAME PHONENO ADDRESS FAXNO
100001 Company A 503-222-3333 100 SE Stark Rd Portland, OR 503-666-4444
200001 Company B 666-333-4444 500 SE Bilerica Rd Akron, OH 666-444-3333
SQL> select * from supplies ;
ITEMNO SUPPLIERNO NAME DESCRIPTION QUANTITYINSTOCK REORDERLEVEL COSTPERUNIT IS_SURGICAL
823456 100001 Zanax Anti Depressant 8 2 100.5 N
923956 100001 Zupridol Blood Pressure Treatment 12 5 50 N
3952 200001 Amibreezax Artificial Ear Wax 2 1 200 N
4955 200001 Ambridax Skin Treatment 5 10 20 N
54802 100001 Scalpel Surgical Tool 20 10 200.42 Y
634520 200001 Stitches Suture Tool 100 10 2.5 Y
888520 100001 Cart 5ftx2ftx3ft 2 0 200 N
423 100001 Tool Holder Holds Inspection Equip. 4 2 50 N
SQL> select * from requisition;
REQNO STAFFNO STAFFNAME WARDNO ITEMNO QUANTITY DATEORDERED DATERECIEVED
1 20 Julie Wood 8 888520 2 27-FEB-18 15-MAR-18
2 20 Julie Wood 8 923956 1 25-FEB-18 28-FEB-18
3 21 Sarah Michaels 7 54802 3 20-FEB-18 22-FEB-18
SQL> select * from staff_chargenurse;
STAFFNO ADDRESS POSITION BUDGET SPECIALTY
20 32 Stark St. Portland, OR Charge Nurse 8000.99 Head Trauma
21 18 Wilson Rd Portland, OR Charge Nurse 6000 Epidermus
SQL> select * from pharma_dosage;
ITEMNO DOSAGE
823456 50mg
923956 20mg
3952 5g
4955 2mg
Version 2
If you still want to have one "supertype" and 3 "subtype" tables, have a look at the following DDL. You can probably find a solution that's kind of "in between" the two. (What follows is just "proof of concept" code, several columns and some of the original tables omitted.)
create table supplies (
supplierno number primary key
, category varchar2( 16 )
, constraint unique_parentcategory unique ( supplierno, category )
, constraint check_category check (
category in ( 'surgical', 'non-surgical', 'pharmaceutical' )
)
);
Then ...
create table supplies_pharmaceutical (
supplierno number primary key
, category varchar2( 16 )
--
-- more "specific" columns here
--
, constraint check_category1 check ( category in ( 'pharmaceutical' ) )
, constraint s_p_fk foreign key ( supplierno, category )
references supplies ( supplierno, category )
) ;
create table supplies_nonsurgical (
supplierno number primary key
, category varchar2( 16 )
--
-- more "specific" columns here
--
, constraint check_category2 check ( category in ( 'non-surgical' ) )
, constraint s_n_fk foreign key ( supplierno, category )
references supplies ( supplierno, category )
) ;
create table supplies_surgical (
supplierno number primary key
, category varchar2( 16 )
--
-- more "specific" columns here
--
, constraint check_category3 check ( category in ( 'surgical' ) )
, constraint s_s_fk foreign key ( supplierno, category )
references supplies ( supplierno, category )
) ;
Test data:
begin
insert into supplies( supplierno, category ) values ( 1000, 'pharmaceutical' ) ;
insert into supplies( supplierno, category ) values ( 2000, 'non-surgical' ) ;
insert into supplies( supplierno, category ) values ( 3000, 'surgical' ) ;
insert into supplies_pharmaceutical( supplierno, category )
values ( 1000, 'pharmaceutical' ) ;
insert into supplies_nonsurgical( supplierno, category )
values ( 2000, 'non-surgical' ) ;
insert into supplies_surgical( supplierno, category )
values ( 3000, 'surgical' ) ;
end;
/
-- must fail:
insert into supplies ( supplierno, category ) values ( 1001, 'food' ) ;
insert into supplies_pharmaceutical( supplierno, category )
values ( 2000, 'pharmaceutical' ) ;

Check if ID of parent table's record is contained in just one of the child tables

To preface this, I have 3 tables, there is table
Product
- id
- name
- availability
Then is has 2 child tables:
Tables
- id
- product_id (foreign key to product(id))
- size
Chairs
- id
- product_id (foreign key to product(id))
- color
What I want to do is everytime I insert a new record into the chairs/tables table, I want to check whether it is not already contained in one of them. Or in other words, one product cannot be chair and table at once. How do I do this?
Thank you.
You could use a CHECK constraint for this, which, in combination with some other constraints, may give you the required behaviour. There are some restrictions on check constraints, one of them being:
The condition of a check constraint can refer to any column in the
table, but it cannot refer to columns of other tables.
( see the documentation )
In the following example, the ID columns "chairs.id" and "tables.id" have been moved into the "products" table, which contains the CHECK constraint. The UNIQUE constraints enforce a one-to-one relationship as it were ( allowing only one value for each of the REFERENCED ids ). The DDL code looks a bit busy, but here goes:
Tables
create table products (
id number generated always as identity primary key
, name varchar2(128)
, availability varchar2(32)
);
-- product_id removed
create table tables (
id number primary key
, size_ number
) ;
-- product_id removed
create table chairs (
id number primary key
, color varchar2(32)
);
Additional columns and constraints
alter table products
add (
table_id number unique
, chair_id number unique
, check (
( table_id is not null and chair_id is null )
or
( table_id is null and chair_id is not null )
)
);
alter table tables
add constraint fkey_table
foreign key ( id ) references products ( table_id ) ;
alter table chairs
add constraint fkey_chairs
foreign key ( id ) references products ( chair_id ) ;
Testing
-- {1} Add a chair: the chair_id must exist in PRODUCTS.
insert into chairs ( id, color ) values ( 1000, 'maroon' ) ;
-- ORA-02291: integrity constraint ... violated - parent key not found
-- Each chair needs an entry in PRODUCTS first:
insert into products ( name, availability, chair_id )
values ( 'this is a chair', 'in stock', 1000 ) ;
insert into chairs ( id, color ) values ( 1000, 'maroon' ) ;
-- okay
-- {2} We cannot add another chair that has the same chair_id. Good.
insert into products ( chair_id ) values ( 1000 ) ;
-- ORA-00001: unique constraint ... violated
-- {3} Add a table.
insert into products ( name, availability, table_id )
values ( 'this is a table', 'unavailable', 1000 ) ;
-- okay
insert into tables ( id, size_ ) values ( 1000, 60 ) ;
-- {4} Is it possible to add another table, with the same table_id? No. Good.
insert into tables ( id, size_ ) values ( 1000, 60 ) ;
-- ORA-00001: unique constraint ... violated
insert into products ( name, availability, table_id )
values ('this is a table', 'unavailable', 1000 ) ;
-- ORA-00001: unique constraint ... violated
-- {5} We cannot add something that is a chair _and_ a table (at the same time).
insert into products ( name, availability, table_id, chair_id )
values ( 'hybrid', 'awaiting delivery', 2000, 2000 ) ;
-- ORA-02290: check constraint ... violated
Resultset
SQL> select * from products;
ID NAME AVAILABILITY TABLE_ID CHAIR_ID
21 this is a chair in stock NULL 1000
23 this is a table unavailable 1000 NULL
NOTE: The product ID (NOT the table_id or the chair_id) "identifies" a particular table/chair. The values in the TABLE_ID and CHAIR_ID columns are only used to "link" to the (unique) IDs in the child table(s).
Alternative: object-relational approach.
This solution may be more suitable for solving the problem. Here, we create a supertype (product_t) first, and then 2 subtypes (chair_t and table_t, respectively). This allows us to create a table PRODUCTS_, using product_t for storing the data we need.
Types and table
create or replace type product_t as object (
name varchar2(64)
, availability varchar2(64)
)
not final;
/
create or replace type chair_t under product_t (
color varchar2(64)
)
/
create or replace type table_t under product_t (
size_ number
)
/
create table products_ (
id number generated always as identity primary key
, product product_t
);
Testing
-- "standard" INSERTs
begin
insert into products_ ( product )
values ( chair_t( 'this is a chair', 'in stock', 'maroon' ) );
insert into products_ ( product )
values ( table_t( 'this is a table', 'not available', 60 ) );
end;
/
-- unknown types cannot be inserted
insert into products_ ( product )
values ( unknown_t( 'type unknown!', 'not available', 999 ) );
-- ORA-00904: "UNKNOWN_T": invalid identifier
insert into products_ ( product )
values ( product_t( 'supertype', 'not available', 999 ) );
-- ORA-02315: incorrect number of arguments for default constructor
-- object of SUPERtype can be inserted
insert into products_ ( product )
values ( product_t( 'supertype', 'not available' ) );
-- 1 row inserted.
Query
select
id
, treat( product as table_t ).name as name_of_table
, treat( product as chair_t ).name as name_of_chair
, case
when treat( product as table_t ) is not null
then 'TABLE_T'
when treat( product as chair_t ) is not null
then 'CHAIR_T'
when treat( product as product_t ) is not null
then 'PRODUCT_T'
else
'TYPE unknown :-|'
end which_type_is_it
from products_ ;
-- result
ID NAME_OF_TABLE NAME_OF_CHAIR WHICH_TYPE_IS_IT
1 NULL this is a chair CHAIR_T
2 this is a table NULL TABLE_T
3 NULL NULL PRODUCT_T -- neither a chair nor a table ...

Relational database design issue - category scoped tags

I'm designing a database where I have a number of products, each of which belongs to one and only one category.
Products are meant to be tagged, but only with tags allowed for the category they belong to.
This is what I've got so far:
Let's say I have these 2 categories:
Smartphones
Laptops
Tags for the "Smartphones" category:
Dual SIM
GPS
Tags for the "Laptop" category:
Backlit keyboard
HDMI
The problem with this design is that the database doesn't prevent a product from being tagged with a tag from another category: a bug in my app's code could easily cause a laptop to be tagged with the "Dual SIM" tag, which obviously is not what I want.
I would like to prevent this situation at database level with foreign keys and without using triggers. Is this possible at all?
I was able to do the following in Oracle. Note how the last insert fails, I believe this is what you are after.
CREATE TABLE product (
id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
PRIMARY KEY ( id ),
CONSTRAINT uq_prod_cat UNIQUE ( id,category_id )
);
INSERT INTO product (
id,
category_id
) VALUES (
1,
1
);
CREATE TABLE tags (
id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
PRIMARY KEY ( id ),
CONSTRAINT uq_tag_cat UNIQUE ( id,category_id )
);
INSERT INTO tags (
id,
category_id
) VALUES (
1,
1
);
INSERT INTO tags (
id,
category_id
) VALUES (
2,
1
);
INSERT INTO tags (
id,
category_id
) VALUES (
3,
2
);
CREATE TABLE product_tags (
id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY ( id ),
FOREIGN KEY ( product_id,category_id )
REFERENCES product ( id,category_id ),
FOREIGN KEY ( tag_id,category_id )
REFERENCES tags ( id,category_id )
);
INSERT INTO product_tags (
id,
product_id,
category_id,
tag_id
) VALUES (
1,
1,
1,
1
);
1 row inserted.
INSERT INTO product_tags (
id,
product_id,
category_id,
tag_id
) VALUES (
2,
1,
1,
2
);
1 row inserted.
INSERT INTO product_tags (
id,
product_id,
category_id,
tag_id
) VALUES (
3,
1,
1,
3
);
Error starting at line : 35 in command -
INSERT INTO product_tags (id, product_id, category_id, tag_id) VALUES (3, 1, 1, 3)
Error report -
ORA-02291: integrity constraint (SYS_C008023) violated - parent key not found
My answer is pretty similar to that of Ashuntosh A, except that I would create a separate table for associating tags with categories, so that the schema allows for a tag to apply to more one category (e.g. both a tablet and phone could have a DualSim):
--TSQL
create table ProductCategory
(
id int primary key identity,
name varchar(50) not null
)
create table ProductTag
(
id int primary key identity,
name varchar(50) not null
)
create table TagCategory
(
tag_id int foreign key references ProductTag,
category int foreign key references ProductCategory,
primary key (tag_id, category)
)
create table Product
(
id int primary key identity,
type int foreign key references ProductCategory,
unique (id, type)
)
create table TaggedProduct
(
product int,
tag int,
type int,
primary key (product, tag),
foreign key (product, type) references Product (id, type),
foreign key (tag, type) references TagCategory (tag_id, category)
)
insert ProductCategory
select 'Laptop' union
select 'Phone'
insert ProductTag
select 'HDMI' union
select 'Backlit Keyboard' union
select 'Dual Sim' union
select 'GPS'
insert TagCategory
select 1, 1 union -- HDMI/LAPTOP
select 2, 1 union -- Backlit/LAPTOP
select 3, 2 union -- DualSim/PHONE
select 4, 2 -- GPS/PHONE
insert Product
select 1 --a laptop
insert TaggedProduct
select 1, 1, 1 --laptop has hdmi
union select 1, 2, 1 --laptop has backlit keyboard
insert TaggedProduct select 1, 3, 1
--fails because 'DualSim/Laptop' is not a valid category

trigger for delete row with reference to another deleted row in another table

I need help with my trigger. I am doing trigger in Oracle for delete rows in first table with foreigner keys references to second table where is deleted row with primary key with value like foreign key in first table.
I have these tables:
CREATE TABLE Room (
id_room NUMBER(5) NOT NULL,
.
.
price VARCHAR(10) NOT NULL,
PRIMARY KEY(id_mistnosti),
);
CREATE TABLE item1 (
id_room NUMBER(5) NOT NULL,
.
.
FOREIGN KEY(id_room) REFERENCES Room
);
CREATE TABLE item2(
id_room NUMBER(5) NOT NULL,
.
.
FOREIGN KEY(id_room ) REFERENCES Room
);
I have table room, which has 2 items, so I need delete these 2 items when I delete their room.
Now I am trying delete only one item:
CREATE OR REPLACE TRIGGER removeRoomsItems
BEFORE DELETE ON Room
FOR EACH ROW
WHEN (:Room.id_mistnosti = :item1.id_mistnosti)
BEGIN
DELETE FROM item1;
END;
/
My SQLdeveloper writes that error is in clause WHEN.
I need some guide with deleting both of items.
expresion in trigger is bad. Trigger can looks like this:
CREATE OR REPLACE TRIGGER removeRoomsItems
BEFORE DELETE ON Room
FOR EACH ROW
BEGIN
DELETE FROM item1 WHERE id_room = :old.id_room ;
END;
In pl/sql function don't use WHEN, but use IF,ELSE and END IF;.
But you don't need this trigger, use cascade FK, so u need create table this way:
CREATE TABLE item2(
id_room NUMBER(5) NOT NULL,
.
.
FOREIGN KEY(id_room ) REFERENCES Room ON DELETE CASCADE;
);
Never use triggers like this, it's very bad way.
You don't need any trigger in this case.
Just use ON DELETE CASCADE clause:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/clauses002.htm
ON DELETE Clause
The ON DELETE clause lets you determine how Oracle
Database automatically maintains referential integrity if you remove a
referenced primary or unique key value. If you omit this clause, then
Oracle does not allow you to delete referenced key values in the
parent table that have dependent rows in the child table.
Specify CASCADE if you want Oracle to remove dependent foreign key
values.
Specify SET NULL if you want Oracle to convert dependent foreign key
values to NULL.
See this simple example:
CREATE TABLE Room (
id_mistnosti int,
id_room NUMBER(5) NOT NULL,
price VARCHAR(10) NOT NULL,
PRIMARY KEY(id_mistnosti)
);
CREATE TABLE item1 (
id_room NUMBER(5) NOT NULL,
item_name varchar(100),
FOREIGN KEY(id_room) REFERENCES Room ON DELETE CASCADE
);
CREATE TABLE item2(
id_room NUMBER(5) NOT NULL,
item_name varchar(100),
FOREIGN KEY(id_room ) REFERENCES Room ON DELETE CASCADE
);
insert into room values( 1, 1, 100 );
insert into room values( 2, 2, 200 );
insert into item1 values( 1, 'room 1' );
insert into item1 values( 1, 'room 11' );
insert into item1 values( 2, 'room 2' );
insert into item1 values( 2, 'room 22' );
insert into item2 values( 1, 'room 1' );
insert into item2 values( 1, 'room 11' );
insert into item2 values( 2, 'room 2' );
insert into item2 values( 2, 'room 22' );
commit;
and now:
SELECT * FROM item2;
ID_ROOM ITEM_NAME
---------- ----------
1 room 1
1 room 11
2 room 2
2 room 22
Delete the room and see how this delete affects items:
delete from room where id_mistnosti = 1;
select * from item2;
ID_ROOM ITEM_NAME
---------- ----------
2 room 2
2 room 22