Modelling Hierarchical attributes - sql

I am trying to figure out the data model for a retailer.
The retailer has several stores across the country and they are modeled using the following hierarchy:
Channel -> Zone -> City -> Store
Each store contains several articles. And each article has attributes like
Activation flag (this indicates the presence of article)
Price
Supplier
Warehouse
Now, the retailer can set these attributes at any level in the hierarchy. Consider the following cases:
Setting price for an article at channel level will apply it to all stores.
The price set at a higher level can be overridden at any other level. For e.g., at a city level for just the stores in a city or for a particular store.
This applies to all attributes listed above.
As of now, they have modeled it using RDBMS by defining global rules at the top of hierarchy and calling out exceptions separately as individual rows. Say, price table, will have price set for an article at Channel level and any changes at any level will be specified separately. Obviously, this is not very efficient when fetching the attributes at the store level.
Sample Data
Assume Channel, Zone, City and Store are collectively called entities. Channel will have ids ranging >= 4000, Zone >= 3000, City >= 2000 and stores range from 0 to 1000.
A subset of the hierarchy relationship data is given below:
Channel | Zone | City | Store |
----------+----------+------------------
4001 | 3001 | 2001 | 13 |
4001 | 3001 | 2001 | 14 |
4001 | 3001 | 2002 | 15 |
4001 | 3002 | 2003 | 16 |
4001 | 3003 | 2006 | 74 |
Price Table
ArticleID | EntityID | Price
----------+----------+----------
12345 | 4001 | 2.5
12345 | 2003 | 2.9
12345 | 74 | 3.0
Here, the price 2.5 for channel 4001 will be set for the article in all stores. The next two rows set the price exceptions in some stores. The second price 2.9 set for city 2003 will be applicable only for the article in store 16 as per the hierarchy relationship mentioned above. And the third row directly sets price 3.0 for the article in store 74.
Hope this gives an idea of current model. So, can you please suggest a better way to store this?

There are few ways to represent trees and hierarchies in relational model -- a search on SO will return quite a few answers.
The main idea in this model is to represent hierarchy with a closure table and a hierarchy-level.
So, the Level table has (1, Channel) , (2, Zone) , (3, City) , (4, Store).
The closure table exposes each node and all descendants of the node. It is important to note that each node is its own descendant too.
The first CTE query (q_00) selects a node's price and assigns it to all descendants. The LevelDiff column calculates how many levels from the descendant node is the price-specifying node.
Because the price may be specified on more than one level, the final query selects store prices specified on the level with minimum LevelDiff.
The syntax is PostgeSQL, but should be easy to convert to others too.
with q_00 as (
select
a.ProductID
, c.LocationID
, c.LocationLevel
, a.Price
, t.DescendantLocationID
, t.DescendantLevel
, (t.DescendantLevel - c.LocationLevel ) as LevelDiff
from ProductPrice as a
join Product as b on b.ProductId = a.ProductID
join Location as c on c.LocationID = a.LocationID
join TreeClosure as t on t.LocationID = c.LocationID
)
select
a.ProductID
, DescendantLocationID
, Price
from q_00 as a
join Level as w on w.LevelNo = a.DescendantLevel
where w.LevelName = 'Store'
and a.Leveldiff = (select min(LevelDiff)
from q_00 as x
where x.DescendantLocationID = a.DescendantLocationID
and x.ProductID = a.ProductID ) ;
So to summarize, here is a test result, pricing was defined as:
Channel=1, Product=1, Price = 11.0
Channel=1, City=111, Product=1, Price = 11.5
Channel=1, City=111, Store =1112, Product=1, Price = 12.0
The query returns (see test data below)
ProductID | DescendantLocationID | PriceID
-----------------------------------------
1 1231 11.00
1 1232 11.00
1 1111 11.50
1 1112 12.00
Here is the DDL (PosgreSQL)
CREATE TABLE Level (
LevelNo integer NOT NULL ,
LevelName varchar(20) NOT NULL
);
ALTER TABLE Level ADD CONSTRAINT XPKLevel PRIMARY KEY (LevelNo) ;
CREATE TABLE Location (
LocationID integer NOT NULL ,
LocationLevel integer NOT NULL
);
ALTER TABLE Location ADD CONSTRAINT XPKLocation PRIMARY KEY (LocationID);
ALTER TABLE Location ADD CONSTRAINT XAK1Location UNIQUE (LocationID, LocationLevel) ;
CREATE TABLE Product (
ProductID integer NOT NULL
);
ALTER TABLE Product ADD CONSTRAINT XPKProduct PRIMARY KEY (ProductID);
CREATE TABLE ProductPrice (
ProductID integer NOT NULL ,
LocationID integer NOT NULL ,
Price decimal(19,2) NOT NULL
);
ALTER TABLE ProductPrice ADD CONSTRAINT XPKProductPrice PRIMARY KEY (ProductID, LocationID);
CREATE TABLE ProductSupplier (
ProductID integer NOT NULL ,
LocationID integer NOT NULL ,
SupplierID integer NOT NULL
);
ALTER TABLE ProductSupplier ADD CONSTRAINT XPKProductSupplier PRIMARY KEY (ProductID, LocationID);
CREATE TABLE Supplier (
SupplierID integer NOT NULL
);
ALTER TABLE Supplier ADD CONSTRAINT XPKSupplier PRIMARY KEY (SupplierID) ;
CREATE TABLE TreeClosure (
LocationID integer NOT NULL ,
DescendantLocationID integer NOT NULL ,
DescendantLevel integer NOT NULL
);
ALTER TABLE TreeClosure ADD CONSTRAINT XPKTreeClosure PRIMARY KEY (LocationID, DescendantLocationID);
ALTER TABLE Location
ADD CONSTRAINT FK1_Location FOREIGN KEY (LocationLevel) REFERENCES Level(LevelNo);
ALTER TABLE ProductPrice
ADD CONSTRAINT FK1_ProductPrice FOREIGN KEY (ProductID) REFERENCES Product(ProductID);
ALTER TABLE ProductPrice
ADD CONSTRAINT FK2_ProductPrice FOREIGN KEY (LocationID) REFERENCES Location(LocationID);
ALTER TABLE ProductSupplier
ADD CONSTRAINT FK1_PrdSup FOREIGN KEY (ProductID) REFERENCES Product(ProductID);
ALTER TABLE ProductSupplier
ADD CONSTRAINT FK2_PrdSup FOREIGN KEY (SupplierID) REFERENCES Supplier(SupplierID);
ALTER TABLE ProductSupplier
ADD CONSTRAINT FK3_PrdSup FOREIGN KEY (LocationID) REFERENCES Location(LocationID);
ALTER TABLE TreeClosure
ADD CONSTRAINT FK1_TC FOREIGN KEY (LocationID) REFERENCES Location(LocationID);
ALTER TABLE TreeClosure
ADD CONSTRAINT FK2_TC FOREIGN KEY (DescendantLocationID,DescendantLevel) REFERENCES Location(LocationID,LocationLevel);
And some data to test with
insert into Level (LevelNo, LevelName)
values
(1, 'Channel')
, (2, 'Zone')
, (3, 'City')
, (4, 'Store')
;
insert into Product (ProductID)
values (1) , (2) , (3)
;
-- Locations
insert into Location (LocationID, LocationLevel)
values
(1, 1)
, (11, 2)
, (111, 3)
, (1111, 4)
, (1112, 4)
, (12, 2)
, (123, 3)
, (1231, 4)
, (1232, 4)
;
-- Tree closure (hierarchy)
insert into TreeClosure (LocationID, DescendantLocationID, DescendantLevel)
values
(1 , 1 , 1)
, (1 , 11 , 2)
, (1 , 111 , 3)
, (1 , 1111, 4)
, (1 , 1112, 4)
, (11 , 11 , 2)
, (11 , 111 , 3)
, (11 , 1111, 4)
, (11 , 1112, 4)
, (111 , 111 , 3)
, (111 , 1111, 4)
, (111 , 1112, 4)
, (1111, 1111, 4)
, (1112, 1112, 4)
--
, (1 , 12 , 2)
, (1 , 123 , 3)
, (1 , 1231, 4)
, (1 , 1232, 4)
, (12 , 12 , 2)
, (12 , 123 , 3)
, (12 , 1231, 4)
, (12 , 1232, 4)
, (123 , 123, 3)
, (123 , 1231, 4)
, (123 , 1232, 4)
, (1231, 1231, 4)
, (1232, 1232, 4)
;
-- pricing
insert into ProductPrice (ProductID, LocationID, Price) values (1, 1 , 11.0);
insert into ProductPrice (ProductID, LocationID, Price) values (1, 111 , 11.5);
insert into ProductPrice (ProductID, LocationID, Price) values (1, 1112, 12.0);

Related

What would cause a parent table error midway through posting the same rows?

Can someone tell me why after successfully entering rows (INSERT INTO), using the same INSERT template, rows start to be declined because of a parent table error?
The parent and child tables:
SQL> CREATE TABLE nm_order (
2 orderID INT PRIMARY KEY,
3 customerID INT REFERENCES nm_customer(customerID) NOT NULL,
4 orderDate DATE NOT NULL,
5 orderStatus VARCHAR2(2) NOT NULL,
6 orderShippedDate DATE ) ;
SQL> CREATE TABLE nm_orderLine (
2 orderLineID INT PRIMARY KEY,
3 orderID INT REFERENCES nm_order(orderID),
4 productCode VARCHAR2(15) REFERENCES nm_product(productCode),
5 quantity NUMBER(3) NOT NULL,
6 unitPrice NUMBER(5,2) NOT NULL ) ;
Successful rows and failure....
SQL> INSERT INTO nm_orderLine (
2 orderLineID, orderID, productCode, quantity, unitPrice)
3 VALUES (5, 4, 'R03515', 1, 5.99) ;
1 row created.
SQL> INSERT INTO nm_orderLine (
2 orderLineID, orderID, productCode, quantity, unitPrice)
3 VALUES (6, 5, 'R03669', 1, 8.99) ;
1 row created.
SQL> INSERT INTO nm_orderLine (
2 orderLineID, orderID, productCode, quantity, unitPrice)
3 VALUES (7, 6, 'IM657', 1, 12.99) ;
INSERT INTO nm_orderLine (
*
ERROR at line 1:
ORA-02291: integrity constraint (BISUSER.SYS_C009576) violated - parent key not
found
Several rows worked and then O started receiving this error using the same template. Can anyone see something wrong with the rejected statements? I cannot. Thanks for any insight
The error message is telling you that the child row you are trying to insert does not have a parent: the foreign key relationship prohibits this situation:
CREATE TABLE nm_orderLine (
...
orderID INT REFERENCES nm_order(orderID),
...
)
You need order_id 6 to be inserted in parent table nm_order before you can insert a referencing row in child table nm_orderLine.

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 ...

SELECT From Multiple tables with many to many relations

Hello everyone I have inherirted a poorly designed database and I need to get some information from 3 tables
Franchise
Id(Int, PK)
FrID (varchar(50))
FirstName (varchar(50))
LastName (varchar(50))
Store
Id (Int, PK)
FrID (varchar(50))
StoreNumber (varchar(50))
StoreName
Address
Pricing
Id (int, PK)
StoreNumber (varchar(50))
Price1
Price2
Price3
and the data
ID, FrID ,FirstName,LastName
1, 10 ,John Q , TestCase
2, 10 ,Jack Q , TestCase
3, 11 ,Jack Q , TestCase
ID, FrID, StoreNumber , StoreName , Address
10, 10 , 22222 , TestStore1, 123 Main street
11, 10 , 33333 , TestStore2, 144 Last Street
12, 10 , 44444 , TestStore2, 145 Next Street
13, 11 , 55555 , Other Test, 156 Other st
ID, StoreNumber, Price1, Price2, Price3
1, 22222 , 19.99, 20.99 , 30.99
2, 33333 , 19.99, 20.99 , 30.99
3, 44444 , 19.99, 20.99 , 30.99
4, 55555 , 19.99, 20.99 , 30.99
Here is what I have done
SELECT F.FirstName,F.LastName,F.FrID , S.StoreNumber,S.StoreName,S.Address,
P.Price1,P.Price2,P.Price3
FROM Franchisee F
JOIN Store S on F.FrID = S.FrID
JOIN Pricing P on P.StoreNumber = S.StoreNumber
This part works, but I end up with lots of duplicates, For example Jack Q gets listed for his store plus every store that John Q is on. Is there anyway to fix this with out a database redesign.
Okay, there is a whole laundry list of issues such as character fields such as [FrId] being used as strings, reserved words such as [address] being used as name, etc.
Let's put the bad design issues aside.
First, I need to create a quick test environment. I did not put in Foreign Keys since that constraint is not needed to get the correct answer.
--
-- Setup test tables
--
-- Just playing
use Tempdb;
go
-- drop table
if object_id('franchise')> 0
drop table franchise;
go
-- create table
create table franchise
(
Id int primary key,
FrID varchar(50),
FirstName varchar(50),
LastName varchar(50)
);
-- insert data
insert into franchise values
( 1, 10, 'John Q', 'TestCase'),
( 2, 10, 'Jack Q', 'TestCase'),
( 3, 11, 'Jack Q', 'TestCase');
-- select data
select * from franchise;
go
-- drop table
if object_id('store')> 0
drop table store;
go
-- create table
create table store
(
Id int primary key,
FrID varchar(50),
StoreNumber varchar(50),
StoreName varchar(50),
Address varchar(50)
);
-- insert data
insert into store values
(10, 10, 22222, 'TestStore1', '123 Main street'),
(11, 10, 33333, 'TestStore2', '144 Last Street'),
(12, 10, 44444, 'TestStore2', '145 Next Street'),
(13, 11, 55555, 'Other Test', '156 Other Street');
-- select data
select * from store;
go
-- drop table
if object_id('pricing')> 0
drop table pricing;
go
-- create table
create table pricing
(
Id int primary key,
StoreNumber varchar(50),
Price1 money,
Price2 money,
Price3 money
);
-- insert data
insert into pricing values
(1, 22222, 19.99, 20.99 , 30.99),
(2, 33333, 19.99, 20.99 , 30.99),
(3, 44444, 19.99, 20.99 , 30.99),
(4, 55555, 19.95, 20.95 , 30.95);
-- select data
select * from pricing;
go
The main issue is that the franchise table should have the primary key (PK) on FrId, not Id. I do not understand why there are duplicates.
However, the query below removes them by grouping. I changed the pricing data for Jack Q to show it is a different record.
--
-- Fixed Query - Version 1
--
select
f.FirstName,
f.LastName,
f.FrID,
s.StoreNumber,
s.StoreName,
s.Address,
p.Price1,
p.Price2,
p.Price3
from
-- Remove duplicates from francise
(
select
LastName,
FirstName,
Max(FrID) as FrID
from
franchise
group by
LastName,
FirstName
) as f
join store s on f.FrID = s.FrID
join pricing p on p.StoreNumber = s.StoreNumber;
The correct output is below.
If I am correct, remove the duplicates entries and change the primary key.
Change Requirements
Okay, you are placing two or more owners in the same table.
Below uses a sub query to combine the owners list into one string. Another way is to have a flag called primary owner. Choose that as the display name.
--
-- Fixed Query - Version 2
--
select
f.OwnersList,
f.FrID,
s.StoreNumber,
s.StoreName,
s.Address,
p.Price1,
p.Price2,
p.Price3
from
-- Compose owners list
(
select
FrID,
(
SELECT FirstName + ' ' + LastName + ';'
FROM franchise as inner1
WHERE inner1.FrID = outer1.FrID
FOR XML PATH('')
) as OwnersList
from franchise as outer1
group by FrID
) as f (FrId, OwnersList)
join store s on f.FrID = s.FrID
join pricing p on p.StoreNumber = s.StoreNumber
Here is the output from the second query.

Implementing an Aliases table (self-referencing many-to-many)

I am trying to model an Alias relationship. That is, several records in my person table may represent the same actual person. I don't care who the "Primary" person is. All Person records would carry equal weight.
I have implemented this in the past with the two tables you see below.
------------- ------------
| Person | | Alias |
|-----------| |----------|
| PersonID | | AliasID |
| LastName | | PersonID |
| FirstName | ------------
-------------
Here is some sample data:
Person (1, 'Joseph', 'Smith')
Person (2, 'Jane', 'Doe')
Person (3, 'Joe', 'Smith')
Person (4, 'Joey', 'Smith')
Alias(1, 1)
Alias(1, 3)
Alias(1, 4)
I suppose I could move the AliasID to the Person table since there is a 1-to-1 relationship between the PersonID fields. However, I may want to add additional fields to the Alias table (like Sequence number, etc.) at some point in the future.
Is there a better way to model this than what I have here?
This is how I would do it.
--DROP TABLE [dbo].[Alias]
GO
--DROP TABLE [dbo].[RealPerson]
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[RealPerson]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
BEGIN
DROP TABLE [dbo].[RealPerson]
END
GO
CREATE TABLE [dbo].[RealPerson]
(
RealPersonUUID [UNIQUEIDENTIFIER] NOT NULL DEFAULT NEWSEQUENTIALID()
, CreateDate smalldatetime default CURRENT_TIMESTAMP
, MyCompanyFriendlyUniqueIdentifier varchar(128) not null
)
GO
ALTER TABLE dbo.RealPerson ADD CONSTRAINT PK_RealPerson
PRIMARY KEY NONCLUSTERED (RealPersonUUID)
GO
ALTER TABLE [dbo].[RealPerson]
ADD CONSTRAINT CK_MyCompanyFriendlyUniqueIdentifier_Unique UNIQUE (MyCompanyFriendlyUniqueIdentifier)
GO
GRANT SELECT , INSERT, UPDATE, DELETE ON [dbo].[RealPerson] TO public
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[Alias]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
BEGIN
DROP TABLE [dbo].[Alias]
END
GO
CREATE TABLE [dbo].[Alias]
(
AliasUUID [UNIQUEIDENTIFIER] NOT NULL DEFAULT NEWSEQUENTIALID()
, RealPersonUUID [UNIQUEIDENTIFIER] NOT NULL
, CreateDate smalldatetime default CURRENT_TIMESTAMP
, LastName varchar(128) not null
, FirstName varchar(128) not null
, PriorityRank smallint not null
)
GO
ALTER TABLE dbo.Alias ADD CONSTRAINT PK_Alias
PRIMARY KEY NONCLUSTERED (AliasUUID)
GO
ALTER TABLE [dbo].[Alias]
ADD CONSTRAINT FK_AliasToRealPerson
FOREIGN KEY (RealPersonUUID) REFERENCES dbo.RealPerson (RealPersonUUID)
GO
ALTER TABLE [dbo].[Alias]
ADD CONSTRAINT CK_RealPersonUUID_PriorityRank_Unique UNIQUE (RealPersonUUID,PriorityRank)
GO
ALTER TABLE [dbo].[Alias]
ADD CONSTRAINT CK_PriorityRank_Range CHECK (PriorityRank >= 0 AND PriorityRank < 33)
GO
if exists (select * from dbo.sysindexes where name = N'IX_Alias_RealPersonUUID' and id = object_id(N'[dbo].[Alias]'))
DROP INDEX [dbo].[Alias].[IX_Alias_RealPersonUUID]
GO
CREATE INDEX [IX_Alias_RealPersonUUID] ON [dbo].[Alias]([RealPersonUUID])
GO
GRANT SELECT , INSERT, UPDATE, DELETE ON [dbo].[Alias] TO public
GO
INSERT INTO dbo.RealPerson ( RealPersonUUID , MyCompanyFriendlyUniqueIdentifier )
select '11111111-1111-1111-1111-111111111111' , 'ABC'
union all select '22222222-2222-2222-2222-222222222222' , 'DEF'
INSERT INTO dbo.[Alias] ( RealPersonUUID , LastName, FirstName , PriorityRank)
select '11111111-1111-1111-1111-111111111111' , 'Smith' , 'Joseph' , 0
union all select '11111111-1111-1111-1111-111111111111' , 'Smith' , 'Joey' , 1
union all select '11111111-1111-1111-1111-111111111111' , 'Smith' , 'Joe' , 2
union all select '11111111-1111-1111-1111-111111111111' , 'Smith' , 'Jo' , 3
union all select '22222222-2222-2222-2222-222222222222' , 'Doe' , 'Jane' , 0
select 'Main Identity' as X, * from dbo.RealPerson rp join dbo.[Alias] al on rp.RealPersonUUID = al.RealPersonUUID where al.PriorityRank = 0
select 'All Identities' as X, * from dbo.RealPerson rp join dbo.[Alias] al on rp.RealPersonUUID = al.RealPersonUUID
select 'Aliai Only' as X, * from dbo.RealPerson rp join dbo.[Alias] al on rp.RealPersonUUID = al.RealPersonUUID where al.PriorityRank > 0
First, you should identify your entities. Clearly you have a person and each person will have their own identity. They are unique and should allways be kept as such. Then you have Alias's They should be in their own table with a one to many relationship. This should be enfrced with primary keys, forgien keys, indexes for quick lookup where appropriate. Each table need a clustered index also for performance. You should then use stored procedures to return or update the tables. I've intentionally used certain word, because if you google them, you will get lots of good information on what you need to do.