Model database to represent constraints - database

I'm building a database (for a class) to model a parts ordering application. "Suppliers" provide parts (different suppliers may each supply one or more parts that fulfill the same role, and every part fulfills exactly one role), "managers" decide which parts are going to be orderable (only one part that fulfills a given role can be orderable), and users can order the parts.
I'm currently in the E-R diagram drawing stage. I'm not sure how to model the parts, roles and orderables. I can represent each orderable/role as a (conceptual) "customer part" entity and create two relationships to the "supplier parts" entity:
That looks tremendously tacky with the "Created by Trial Version" all over it, but believe me, it's better than my chicken scratch handwriting.
However, there is one crucial constraint that would not be captured here. Imagine that you have two sets of supplier parts that fulfill one role each. Each set of parts (each role) would be represented by one customer part. However, the model does not guarantee that the customer part would correspond to a part that fulfills the correct role in the "orders" relationship.
I have also tried modelling it with a ternary relationship and an aggregation, but I still can't capture all the constraints.
My questions boils down to: I have parts, roles, and orderables. Roles and orderables map 1-1 and onto and can really be merged into a single entity. How do I represent that each part is associated with exactly one role, each role is associated with exactly one orderable and vice versa, and each orderable is associated with exactly one part that is also associated with the role corresponding to that orderable?
Thank you for any insights you may have.

I'm in a bit of a hurry, and I might have gotten lost in your requirements. (But +1 for stating them fairly clearly.) Let's tackle the easy stuff first. I'm mostly using natural keys here, because natural keys make it easier to see what's going on. The really important part for you, I think, is the overlapping foreign key constraints (near the end).
The easy stuff--tables for parts, suppliers, and roles.
create table test.parts (
part_num varchar(15) primary key
);
insert into test.parts values
('Part A'), ('Part B'), ('Part C');
-- "Suppliers" provide parts.
create table test.suppliers (
supplier_name varchar(35) primary key
);
insert into test.suppliers values
('Supplier A'), ('Supplier B'), ('Supplier C');
create table test.roles (
role_name varchar(15) primary key
);
insert into test.roles values
('Role 1'), ('Role 2'), ('Role 3');
One requirement: Every part fulfills exactly one role. (More about the UNIQUE constraint, and about using this table instead of simply adding a column to "parts" later.)
create table test.part_roles (
part_num varchar(15) primary key references test.parts (part_num),
role_name varchar(15) not null references test.roles (role_name),
unique (part_num, role_name)
);
insert into test.part_roles values
('Part A', 'Role 1'), ('Part B', 'Role 1'), ('Part C', 'Role 2');
Another requirement--each supplier may supply one or more parts that fulfill the same role. I think this simplifies to "Each supplier supplies multiple parts." (Storing the facts about which role a part belongs to is a different table's responsibility.)
create table test.supplied_parts (
supplier_name varchar(35) not null
references test.suppliers (supplier_name),
part_num varchar(15) not null references test.parts (part_num),
primary key (supplier_name, part_num)
);
insert into test.supplied_parts values
('Supplier A', 'Part A'),
('Supplier A', 'Part B'),
('Supplier A', 'Part C'),
('Supplier B', 'Part A'),
('Supplier B', 'Part B');
Another requirement--managers decide which parts are going to be orderable. (Handle the managers with GRANT and REVOKE.) Only one part that fulfills a given role can be orderable. (That implies either a primary key constraint or a unique constraint on role_name.) And you can't order a part unless someone supplies it. (So we'll need overlapping foreign key constraints.)
This is where the UNIQUE constraint on test.part_roles (part_num, role_name) I mentioned earlier comes in.
create table test.orderable_parts (
role_name varchar(15) primary key references test.roles,
part_num varchar(15) not null,
foreign key (part_num, role_name)
references test.part_roles (part_num, role_name),
supplier_name varchar(35) not null,
foreign key (supplier_name, part_num)
references test.supplied_parts (supplier_name, part_num)
);
insert into test.orderable_parts values
('Role 1', 'Part A', 'Supplier A'),
('Role 2', 'Part C', 'Supplier A');
I think you're probably better off with a separate table of part_roles. (Better than adding a column to parts, for example.) Suppliers usually supply more parts than you're interested in today, but businesses often want to plan ahead, gathering information about parts before they commit to using them (in your case, in a particular role).

suppliers
---------
PK supplier_id
parts -- 1-1 part to role
-----
PK part_id
FK role_id
stocks -- suppliers' parts
------
PK stock_id
FK supplier_id
FK part_id
roles
-----
PK role_id
managers
--------
PK manager_id
selections -- part selected by a manager for a role
----------
PK selection_id
FK manager_id
FK role_id
FK part_id
LEGEND: PK = Primary Key (assuming SERIAL PRIMARY KEY), FK = Foreign Key
Instead of the selections table you could also add FK part_id to roles.

Related

n:1 to two n:1, is this table normalized properly?

thank you for your attention. It's been a while since I learned normalization in school and university and if you go work in the industries, you will find that often these theoretic things might interfere with what you boss tells you to do. So here is an example of something I'm thinking about for a while and I would love to hear your oppinion. I'm not even sure if there is a right or wrong on this approach so every answer is welcome.
Scenario:
We are developing a person management system (PMS). For this, we have a Person table, to store generic information about each person (e. g. Name), a Country table, to store the native country of a person and a Race table for the persons race.
Therefore you could have two foreign key columns on Person, linking to Country and Race, right?
However, my boss want's me to use a fourth table. Lets call it PersonType. The PersonType consists of a primary key as well as the foreign keys colums to Country and Race. The Person table then only has one foreign key column to PersonType.
Therefore I would have a n:1 relation between Person and PersonType as well as n:1 relations between PersonType and Country/Race, right?
In my opinion, the PersonType table is not necessary because you could just put the foreign key columns directly on the Person table but my boss argues that the PersonType can be used to constrain which Country/Race combinations are valid. I understand that argument but I'm asking myself if this database is still properly normalized.
(Of course we are not really developing a PMS but I thought it's easy to imagine and I can't talk about what we are really developing due to an NDA).
UPDATE 21/10/2016
Heres how the table structure looks like in an abstract way:
table person_v1(
person_id int primarykey,
name string,
country_id int foreignkey(country),
race_id int foreignkey(race)
)
table person_v2(
person_id int primarykey,
name string,
person_type_id int foreignkey(person_type)
)
table person_type(
person_type_id int primarykey,
country_id int foreignkey(country),
race_id int foreignkey(race)
)
table country(
country_id int primarykey,
name string
)
table race(
race_id int primarykey,
name string
)
Thank you for your answers so far
The number of 1:N or M:N relationships doesn't determine a relation's normal form. The question actually has nothing to do with normalization.
Some tables . . .
Your design
Follow standards when you can.
For countries, I'll follow ISO 3166-1.
create table countries (
iso_country_code char(3) primary key,
country_name varchar(75) not null unique
);
insert into countries (iso_country_code, country_name) values
('USA', 'United States of America'),
('GBR', 'United Kingdom of Great Britain and Northern Ireland'),
('MKD', 'Macedonia (the former Yugoslav Republic of)'),
('ZZZ', 'Unknown country'); -- 'ZZZ' is reserved for a user-assigned value.
For race, I'll follow CDC/HL7 race codes.
There are other standards.
One of them might be more appropriate.
See http://www.cdc.gov/nchs/data/dvs/race_ethnicity_codeset.pdf
Most applications allow multiple race codes for each person.
I'm ignoring that real-world fact for this question.
create table races (
cdc_unique_id char(6) primary key,
cdc_race_concept varchar(50) not null unique
);
insert into races (cdc_unique_id, cdc_race_concept) values
('2056-0', 'Black'),
('2106-3', 'White'),
('2076-8', 'Native Hawaiian or other Pacific islander'),
('zzzz-z', 'Unknown');
create table persons (
person_id integer primary key,
person_full_name varchar(25) not null,
iso_country_code char(2) not null
default 'ZZZ'
references countries (iso_country_code)
on update cascade
on delete set default,
cdc_unique_id char(6) not null
default 'zzzz-z'
references races (cdc_unique_id)
on update cascade
on delete set default
);
All three of these tables are in at least 5NF.
One potential problem with your design
is that it allows arbitrary parings
of country and race.
Imagine that, instead of country and race,
we were talking about city and state in the USA.
An arbitrary paring of city and state would allow
"San Francisco, AL".
But there is no city named "San Francisco" in Alabama.
That's why allowing arbitrary parings might be a bad decision.
Your boss's design
-- The same as the table above.
create table countries (
iso_country_code char(3) primary key,
country_name varchar(75) not null unique
);
insert into countries (iso_country_code, country_name) values
('USA', 'United States of America'),
('GBR', 'United Kingdom of Great Britain and Northern Ireland'),
('MKD', 'Macedonia (the former Yugoslav Republic of)'),
('ZZZ', 'Unknown country'); -- 'ZZZ' is reserved for a user-assigned value.
-- Also the same as the table above.
create table races (
cdc_unique_id char(6) primary key,
cdc_race_concept varchar(50) not null unique
);
insert into races (cdc_unique_id, cdc_race_concept) values
('2056-0', 'Black'),
('2106-3', 'White'),
('2076-8', 'Native Hawaiian or other Pacific islander'),
('zzzz-z', 'Unknown');
-- This table is new.
create table person_types (
iso_country_code char(3) not null
default 'ZZZ'
references countries (iso_country_code)
on update cascade
on delete set default,
cdc_unique_id char(6) not null
default 'zzzz-z'
references races (cdc_unique_id)
on update cascade
on delete set default,
primary key (iso_country_code, cdc_unique_id)
);
insert into person_types values
('USA', '2016-3'),
('USA', '2056-0'),
('GBR', '2016-3'),
('GBR', '2056-0'),
This "person_types" table records a fact
that your database design does not.
It records the fact
that white persons and black persons
might be native to both the USA and to Great Britain.
If recording that fact is important, you must include "person_types".
Also, it's worth noting that this table is immune to the problems cited in other comments; you cannot repeatedly add rows where both iso_country_code and cdc_unique_id are null (not null constraint), you cannot duplicate iso_country_code and cdc_unique_id (primary key constraint), etc.
Conceptually, you decide which facts to store before you normalize. Normalization can't help you with attributes that don't exist in your schema. That's a different database design task.
-- Structurally identical to the table above.
-- Constraints are slightly different.
--
create table persons (
person_id integer primary key,
person_full_name varchar(25) not null,
iso_country_code char(2) not null
default 'ZZZ',
cdc_unique_id char(6) not null
default 'zzzz-z',
constraint person_types_fk foreign key (iso_country_code, cdc_unique_id)
references person_types (iso_country_code, cdc_unique_id)
on update cascade
on delete set default
);
All four of these tables are in at least 5NF.
The difference is not that one set of tables is more normalized than the other.
The difference is that one set of tables records a fact that the other does not.
Let's assume this table is common to both yours and your boss's designs:
CREATE TABLE RaceMeetings
( country_name VARCHAR(30) NOT NULL,
race_name VARCHAR(25) NOT NULL,
UNIQUE ( country_name, race_name ) );
From what I can tell, your design is this (one table):
CREATE TABLE People_v1
( person_name VARCHAR(35) NOT NULL UNIQUE,
country_name VARCHAR(30) NOT NULL,
race_name VARCHAR(25) NOT NULL,
FOREIGN KEY ( country_name, race_name )
REFERENCES RaceMeetings ( country_name, race_name ) );
...and you boss's design is this (two tables):
CREATE TABLE People_v2
( person_name VARCHAR(35) NOT NULL UNIQUE );
CREATE TABLE RaceMeetingAttendance
( person_name VARCHAR(35) NOT NULL UNIQUE
REFERENCES People_v2 ( person_name ),
country_name VARCHAR(30) NOT NULL,
race_name VARCHAR(25) NOT NULL,
FOREIGN KEY ( country_name, race_name )
REFERENCES RaceMeetings ( country_name, race_name ) );
Both designs
are fully normalized
are in 5NF
restrict { country_name, race_name } combination for a person to be defined in table RaceMeetings.
Your boss's design is additionally in 6NF but that does not necessarily confer any practical advantage.
However, I do prefer your boss's design based on the rule of thumb that a table should model either an entity or a relationship between entities but never both. Put another way, a race meeting such as "The race 'Pacific Grand Prix' is held in Japan" doesn't strike me as being an attribute of a person. It seem to me more like a relationship (a correspondence from person to race meeting) and using a separate table to model this relationship allows me to usefully give it the name `RaceMeetingAttendance'.
That said, your design has the advantage that a person who hasn't attended a race meeting cannot exist in your database, should that be a requirement.

Tables that 'extend' other tables

I have a table that stores information on Vendors called dbo.Vendor. Its has fields like this:
1. VendorID
2. VendorName
3. VendorType
4. AddressLine1
5. EMail
6. Telephone
7. and so on....
This information is common to all vendors. But depending on the type of vendor (VendorType field) I need to collect more specific information. For example a vendor that is a charity will have a Charity Number but a vendor that are Lawyers will have some kind of legal registration number instead. If a vendor is a cinema then I may need to know seating capacity which won't apply to other vendors of course.
Do I really have to create a unique table for each of these different vendors e.g. dbo.VendorLaw, dbo.VendorCinema. Or can I create all possible fields in the main dbo.Vendor table and leave NULL values where the field does not apply to that vendor? This is breaking normalization rules of course.
Depending on the scope of how much additional optional info you need per vendor type, I would create another two tables: one reference table, which stores all the different types of additional info, and one table that stores all the records (and links to the main table).
CREATE TABLE schema.VendorAdditionalInfo (
autoId serial NOT NULL,
vendorId int,
vendorInfoId int,
vendorInfoText varchar
);
Then create your reference table:
CREATE TABLE schema.VendorInfo (
vendorInfoId serial NOT NULL,
vendorType int,
vendorInfoName text
)
This way you can create any amount of records in VendorAdditionalInfo based on what vendor type it is.
EDIT: Example of the info you'd input:
INSERT INTO schema.VendorInfo (vendorType, vendorInfoName)
VALUES
(1, 'Lawyer Registration Number'),
(2, 'Nurse ID Number'),
(3, 'Hot Dog Business License')
Then for your records table you'd enter your info as such:
INSERT INTO schema.VendorAdditionalInfo (vendorId, vendorInfoId, vendorInfoText)
VALUES
(10, 1, 'LAW13245'),
(11, 2, 'NURSE234234'),
(12, 1, 'LAW56156'),
(13, 3, 'HOTDOGBUSINESSLIC23')
Basically - the text field is the field that's unique for each additional info type.
I would create the additional tables. This allows you to enforce null/non-null (and other) constraints easily based on the vendor type - and you can even create a superkey in your existing table on (VendorID,VendorType) and a computed column in each vendor specific column to ensure that e.g. only Cinema vendors have entries in the VendorCinema table.
CREATE TABLE Vendors (
VendorID int IDENTITY(-47,1) not null,
VendorName varchar(19) not null,
VendorType varchar(11) not null,
AddressLine1 varchar(35) not null,
EMail varchar(312) null,
Telephone varchar(15) null,
constraint PK_Vendors PRIMARY KEY (VendorID),
constraint UQ_Vendor_Types UNIQUE (VendorID,VendorType),
constraint CK_Vendor_Types CHECK (VendorType in ('Law','Cinema'))
)
and
CREATE TABLE CinemaVendors (
VendorID int not null,
VendorType as CONVERT(varchar(11),'Cinema') persisted,
Seating int not null,
BruceWillisMovies int not null,
constraint PK_CinemaVendors PRIMARY KEY (VendorID),
constraint FK_CinemaVendors_Vendors FOREIGN KEY
(VendorID,VendorType)
references Vendors (VendorID,VendorType),
constraint CK_BruceWillisMovies CHECK (BruceWillisMovies > 3)
)
This is far easier to do in separate tables than to have a slew of nullable columns in one single table and then trying to enforce all of the actual constraints.
This also addresses the concerns with the EAV model - where we want an int stored for cinema vendors, we're sure that an int has actually been stored.
(It's optional whether you also declare a foreign key between the two above tables based on just the VendorID column. Sometimes I do, sometimes I don't. It's the "real" foreign key, but we use the two column one above to ensure that only Cinema vendors end up in the CinemaVendors table)

Database Design For Multiple Product Types with variable attributes

I have a database containing different product types. Each type contains fields that differ greatly with each other. The first type of product, is classified in three categories. The second type of product, is classified in three categories. But the third and the fourth one, is not classified in anything.
Each product can have any number of different properties.
I am using database model which is basically like below:
(see the link)
https://www.damirsystems.com/static/img/product_model_01.png
I have a huge database, containing about 500000 products in product table.
So when I am going to fetch a product from database with all its attributes, or going to search product filtering by attributes, it affects performance badly.
Could anyone help me what will be the tables structure in sql or do some more indexing or any feasible solution for this problem. Because different ecommerce sites are using this kind of database and working fine with huge different types of products.
EDIT : The link to the image (on my site) is blocked, so here is the image
The model you link to looks like partial entity–attribute–value (EAV) model. EAV is very flexible, but offers poor data integrity, and is cumbersome and usually inefficient. It's not really in the spirit of the relational model. Having worked on some large e-commerce sites, i can tell you that this is not standard or good database design practice in this field.
If you don't have an enormous number of types of product (up to tens, but not hundreds) then you can handle this using one of a two common approaches.
The first approach is simply to have a single table for products, with columns for all the attributes that might be needed in each different kind of product. You use whichever columns are appropriate to each kind of product, and leave the rest null. Say you sell books, music, and video:
create table Product (
id integer primary key,
name varchar(255) not null,
type char(1) not null check (type in ('B', 'M', 'V')),
number_of_pages integer, -- book only
duration_in_seconds integer, -- music and video only
classification varchar(2) check (classification in ('U', 'PG', '12', '15', '18')) -- video only
);
This has the advantage of being simple, and of not requiring joins. However, it doesn't do a good job of enforcing integrity on your data (you could have a book without a number of pages, for example), and if you have more than a few types of products, the table will get highly unwieldy.
You can plaster over the integrity problem with table-level check constraints that require each type of products to have values certain columns, like this:
check ((case when type = 'B' then (number_of_pages is not null) else true end)))
(hat tip to Joe Celko there - i looked up how to do logical implication in SQL, and found an example where he does it with this construction to construct a very similar check constraint!)
You might even say:
check ((case when type = 'B' then (number_of_pages is not null) else (number_of_pages is null) end)))
To ensure that no row has a value in a column not appropriate to its type.
The second approach is to use multiple tables: one base table holding columns common to all products, and one auxiliary table for each type of product holding columns specific to products of that type. So:
create table Product (
id integer primary key,
type char(1) not null check (type in ('B', 'M', 'V')),
name varchar(255) not null
);
create table Book (
id integer primary key references Product,
number_of_pages integer not null
);
create table Music (
id integer primary key references Product,
duration_in_seconds integer not null
);
create table Video (
id integer primary key references Product,
duration_in_seconds integer not null,
classification varchar(2) not null check (classification in ('U', 'PG', '12', '15', '18'))
);
Note that the auxiliary tables have the same primary key as the main table; their primary key column is also a foreign key to the main table.
This approach is still fairly straightforward, and does a better job of enforcing integrity. Queries will typically involve joins, though:
select
p.id,
p.name
from
Product p
join Book b on p.id = b.id
where
b.number_of_pages > 300;
Integrity is still not perfect, because it's possible to create a row in an auxiliary tables corresponding to a row of the wrong type in the main table, or to create rows in multiple auxiliary tables corresponding to a single row in the main table. You can fix that by refining the model further. If you make the primary key a composite key which includes the type column, then the type of a product is embedded in its primary key (a book would have a primary key like ('B', 1001)). You would need to introduce the type column into the auxiliary tables so that they could have foreign keys pointing to the main table, and that point you could add a check constraint in each auxiliary table that requires the type to be correct. Like this:
create table Product (
type char(1) not null check (type in ('B', 'M', 'V')),
id integer not null,
name varchar(255) not null,
primary key (type, id)
);
create table Book (
type char(1) not null check (type = 'B'),
id integer not null,
number_of_pages integer not null,
primary key (type, id),
foreign key (type, id) references Product
);
This also makes it easier to query the right tables given only a primary key - you can immediately tell what kind of product it refers to without having to query the main table first.
There is still a potential problem of duplication of columns - as in the schema above, where the duration column is duplicated in two tables. You can fix that by introducing intermediate auxiliary tables for the shared columns:
create table Media (
type char(1) not null check (type in ('M', 'V')),
id integer not null,
duration_in_seconds integer not null,
primary key (type, id),
foreign key (type, id) references Product
);
create table Music (
type char(1) not null check (type = 'M'),
id integer not null,
primary key (type, id),
foreign key (type, id) references Product
);
create table Video (
type char(1) not null check (type = 'V'),
id integer not null,
classification varchar(2) not null check (classification in ('U', 'PG', '12', '15', '18')),
primary key (type, id),
foreign key (type, id) references Product
);
You might not think that was worth the extra effort. However, what you might consider doing is mixing the two approaches (single table and auxiliary table) to deal with situations like this, and having a shared table for some similar kinds of products:
create table Media (
type char(1) not null check (type in ('M', 'V')),
id integer not null,
duration_in_seconds integer not null,
classification varchar(2) check (classification in ('U', 'PG', '12', '15', '18')),
primary key (type, id),
foreign key (type, id) references Product,
check ((case when type = 'V' then (classification is not null) else (classification is null) end)))
);
That would be particularly useful if there were similar kinds of products that were lumped together in the application. In this example, if your shopfront presents audio and video together, but separately to books, then this structure could support much more efficient retrieval than having separate auxiliary tables for each kind of media.
All of these approaches share a loophole: it's still possible to create rows in the main table without corresponding rows in any auxiliary table. To fix this, you need a second set of foreign key constraints, this time from the main table to the auxiliary tables. This is particular fun for couple of reasons: you want exactly one of the possible foreign key relationships to be enforced at once, and the relationship creates a circular dependency between rows in the two tables. You can achieve the former using some conditionals in check constraints, and the latter using deferrable constraints. The auxiliary tables can be the same as above, but the main table needs to grow what i will tentatively call 'type flag' columns:
create table Product (
type char(1) not null check (type in ('B', 'M', 'V')),
id integer not null,
is_book char(1) null check (is_book is not distinct from (case type when 'B' then type else null end)),
is_music char(1) null check (is_music is not distinct from (case type when 'M' then type else null end)),
is_video char(1) null check (is_video is not distinct from (case type when 'V' then type else null end)),
name varchar(255) not null,
primary key (type, id)
);
The type flag columns are essentially repetitions of the type column, one for each potential type, which are set if and only if the product is of that type (as enforced by those check constraints). These are real columns, so values will have to be supplied for them when inserting rows, even though the values are completely predictable; this is a bit ugly, but hopefully not a showstopper.
With those in place, then after all the tables are created, you can form foreign keys using the type flags instead of the type, pointing to specific auxiliary tables:
alter table Product add foreign key (is_book, id) references Book deferrable initially deferred;
alter table Product add foreign key (is_music, id) references Music deferrable initially deferred;
alter table Product add foreign key (is_video, id) references Video deferrable initially deferred;
Crucially, for a foreign key relationship to be enforced, all its constituent columns must be non-null. Therefore, for any given row, because only one type flag is non-null, only one relationship will be enforced. Because these constraints are deferrable, it is possible to insert a row into the main table before the required row in the auxiliary table exists. As long as it is inserted before the transaction is committed, it's all above board.

Normalize 3 database tables

Hello I have a problem seperating my 3 payment types: CASH, CREDIT, BANK
Each of them has different details.
The details are user defined which means that in a credit card payment (for ex: you should input your credit card details, bank details, cash details (currency and etc))
Business Process: The user will choose his payment type in a
combobox:
Then the user will input the details of that payment type.
This is what I've tried:
PaymentType(PaymentType_ID(PK), PaymentTypes)
...
.....
......
.........
then I'm stuck. I don't know how. Please help me. If you will answer explain to me please. I don't want to ask the same question here again. If I'm faced with a similar situation.
***I can't merge all of them into 1 table because they different columns. They have different specific details...
All three payment types have a few things in common. They all have an account number, an amount, a timestamp, a payment type, and some kind of transaction identifier. All the common attributes go in one table. (Some of the data types are deliberately naive, because they're application-dependent, and I don't know your application.)
create table payment_types (
payment_type_code char(2) primary key,
payment_type varchar(8) not null unique
);
insert into payment_types values
('Ca', 'Cash'),('Cr', 'Credit'),('Ba', 'Bank');
create table payments (
transaction_id integer primary key,
account_code varchar(5) not null, -- references accounts, not shown
amount_usd numeric(5,2) not null,
payment_type_code char(2) not null references payment_types (payment_type_code),
transaction_timestamp timestamp not null default current_timestamp,
unique (transaction_id, payment_type_code)
);
The unique constraint on {transaction_id, payment_type_code} lets SQL use that pair of columns as the target for a foreign key constraint. That's crucial to keeping the rows from the several tables from getting mixed up.
Each payment has different attributes, depending on the payment type. And each payment can be of only one type.
create table payment_cash (
transaction_id integer primary key,
payment_type_code char(2) not null default 'Ca' check (payment_type_code = 'Ca'),
foreign key (transaction_id, payment_type_code)
references payments (transaction_id, payment_type_code),
other_cash_columns char(1) not null
);
create table payment_credit (
transaction_id integer primary key,
payment_type_code char(2) not null default 'Cr' check (payment_type_code = 'Cr'),
foreign key (transaction_id, payment_type_code)
references payments (transaction_id, payment_type_code),
other_credit_columns char(1) not null
);
create table payment_bank (
transaction_id integer primary key,
payment_type_code char(2) not null default 'Ba' check (payment_type_code = 'Ba'),
foreign key (transaction_id, payment_type_code)
references payments (transaction_id, payment_type_code),
other_bank_columns char(1) not null
);
The default value and check constraint for payment_type_code makes it impossible, for example, to insert credit details for a cash payment. That would be possible--and it would be a Bad Thing--if the foreign key constraint used only the transaction id.
As a general rule, you don't cascade updates or deletes for financial transactions. Instead, correct errors by inserting a compensating transaction.
To make this more friendly to users and application code, create three updatable views that join the payments table to the detail. How to make them updatable depends on your dbms.
create view credit_payments_all as
select p.transaction_id, p.account_code, p.amount_usd,
p.payment_type_code, p.transaction_timestamp,
c.other_credit_columns
from payments p
inner join payment_credit c on c.transaction_id = p.transaction_id
-- Rules, triggers, stored procedures, functions, or whatever you need
-- to make this view updatable.
Then any code that needs to insert a credit transaction can just insert into the view credit_payments_all.

Different user types / objects own content in same table - how?

Any idea how I can relate different objects together? Usecase i am trying to achieve is Comments are usually owned by a user. So i have a user_id for it. But I have company pages also where the company owns the content on its page so the owner is the company_id. (Which ofcoure is admin by several users)
One way is to have 2 tables user_comments and company_comments but the problem is then i need 2 tables per object and if i add more user types then i need to multiple the tables. What i want to achieve is 1 table which has:
comment_id PK
owner_id (user id or company id or etc...) - fk?
So let's say i create a owner table just to link all user types together, what would the columns be to get these all in or is there some other way?
People and organizations are a good example of things in a supertype/subtype relationship. They are not identical, but they are not utterly distinct. They share many attributes. Both people and organizations have addresses and telephone numbers, both people and organizations can be plaintiffs and defendants in a lawsuit, and both people and organizations can apparently own comments in your system.
To implement this in a SQL dbms, put the columns common to both people and organizations in one table called, say, "Parties". Columns unique to people go in a table of people; columns unique to organizations go in a table of organizations. Use views, one per subtype, to hide the implementation details; your clients use the views, not the tables.
You'd use the key from the supertype table, "Parties", as the owner of your comments. (I think.)
Here's a simplified example.
create table parties (
party_id integer not null unique,
party_type char(1) not null check (party_type in ('I', 'O')),
party_name varchar(10) not null unique,
primary key (party_id, party_type)
);
insert into parties values (1,'I', 'Mike');
insert into parties values (2,'I', 'Sherry');
insert into parties values (3,'O', 'Vandelay');
-- For "persons", a Subtype of "parties"
create table pers (
party_id integer not null unique,
party_type char(1) not null default 'I' check (party_type = 'I'),
height_inches integer not null check (height_inches between 24 and 108),
primary key (party_id),
foreign key (party_id, party_type) references parties (party_id, party_type)
);
insert into pers values (1, 'I', 72);
insert into pers values (2, 'I', 60);
-- For "organizations", a subtype of "parties"
create table org (
party_id integer not null unique,
party_type CHAR(1) not null default 'O' check (party_type = 'O'),
ein CHAR(10), -- In US, federal Employer Identification Number
primary key (party_id),
foreign key (party_id, party_type) references parties (party_id, party_type)
);
insert into org values (3, 'O', '00-0000000');
create view people as
select t1.party_id, t1.party_name, t2.height_inches
from parties t1
inner join pers t2 on (t1.party_id = t2.party_id);
create view organizations as
select t1.party_id, t1.party_name, t2.ein
from parties t1
inner join org t2 on (t1.party_id = t2.party_id);
Make the view updatable using whatever feature your dbms provides to do that. (Probably triggers.) Then application code can just insert into the appropriate view.

Resources