If this were my design decision, I would probably go with more of an 'Option C' (modified option a).
First, why not 'Option B':
For one thing, I like the clarity that each product has it's own table affords. If it's all one big table with a field to determine the type, the relation isn't as clear.
For another, the indexing strategy would always require that type field to be listed. Since it's only 4 types, the index cardinality is extremely low (SELECT * FROM product_table WHERE type='X'
is basically doing a full table scan anyway)
Option C
- Create a parent table that holds only the columns that all types share
- Create each product type as it's own table with their individual columns, with one extra: A link to the parent table
- Create each 'link' table: Product_Option, Model_option, etc with links to the respective keys.
- For those with reciprocal links (MODEL_OPTION, OPTION_MODEL) go ahead and create those tables as well. This will add clarity in your joins for anyone looking at it.
The downside is the complexity of making sure to avoid orphans when things are updated/deleted, and initially designing the queries that use these tables.
Way two is the correct way.
Your base class gets a table, and then child classes get their own tables with just the additional fields they introduce, plus foreign key references to the base table.
As Joel suggested in his comments on this answer, you can guarantee that a user will have either a direct login or an OpenID login, but not both (and also possibly neither) by adding a type column to each sub-type table that keys back to the root table. The type column in each sub-type table is restricted to have a single value representing the type of that table. Because this column is foreign keyed to the root table, only one sub-type row can link to the same root row at a time.
For example, the MySQL DDL would look something like:
CREATE TABLE Users
(
uid INTEGER AUTO_INCREMENT NOT NULL
, type ENUM("DirectLogin", "OpenID") NOT NULL
// ...
, PRIMARY_KEY(uid)
);
CREATE TABLE DirectLogins
(
uid INTEGER NOT_NULL
, type ENUM("DirectLogin") NOT NULL
// ...
, PRIMARY_KEY(uid)
, FORIGEN_KEY (uid, type) REFERENCES Users (uid, type)
);
CREATE TABLE OpenIDLogins
(
uid INTEGER NOT_NULL
, type ENUM("OpenID") NOT NULL
// ...
PRIMARY_KEY(uid),
FORIGEN_KEY (uid, type) REFERENCES Users (uid, type)
);
(On other platforms you would use a CHECK
constraint instead of ENUM
.) MySQL supports composite foreign keys so this should work for you.
Way one is valid, though you are wasting space in those NULL
-able columns because their use depends on the type of user. The advantage is that if you choose to expand what kinds of user types to store and those types don't require additional columns, you can just expand the domain of your ENUM
and use the same table.
Way three forces any queries that reference users to check against both tables. This also prevents you from referencing a single users table via foreign key.
Best Answer
If you are using MySQL you can do this:
Tip: You don't have to write inner join, just join.