Well here's my problem I have three tables; regions, countries, states. Countries can be inside of regions, states can be inside of regions. Regions are the top of the food chain.
Now I'm adding a popular_areas table with two columns; region_id and popular_place_id. Is it possible to make popular_place_id be a foreign key to either countries OR states. I'm probably going to have to add a popular_place_type column to determine whether the id is describing a country or state either way.
Here is a correction to Bill Karwin's "supertable" approach, using a compound key
( place_type, place_id )
to resolve the perceived normal form violations:What this design cannot ensure that for every row in
places
there exists a row instates
orcountries
(but not both). This is a limitations of foreign keys in SQL. In a full SQL-92 Standards compliant DBMS you could define deferrable inter-table constraints that would allow you to achieve the same but it is clunky, involves transaction and such a DBMS has yet to make it to market.What you're describing is called Polymorphic Associations. That is, the "foreign key" column contains an id value that must exist in one of a set of target tables. Typically the target tables are related in some way, such as being instances of some common superclass of data. You'd also need another column along side the foreign key column, so that on each row, you can designate which target table is referenced.
There's no way to model Polymorphic Associations using SQL constraints. A foreign key constraint always references one target table.
Polymorphic Associations are supported by frameworks such as Rails and Hibernate. But they explicitly say that you must disable SQL constraints to use this feature. Instead, the application or framework must do equivalent work to ensure that the reference is satisfied. That is, the value in the foreign key is present in one of the possible target tables.
Polymorphic Associations are weak with respect to enforcing database consistency. The data integrity depends on all clients accessing the database with the same referential integrity logic enforced, and also the enforcement must be bug-free.
Here are some alternative solutions that do take advantage of database-enforced referential integrity:
Create one extra table per target. For example
popular_states
andpopular_countries
, which referencestates
andcountries
respectively. Each of these "popular" tables also reference the user's profile.This does mean that to get all of a user's popular favorite places you need to query both of these tables. But it means you can rely on the database to enforce consistency.
Create a
places
table as a supertable. As Abie mentions, a second alternative is that your popular places reference a table likeplaces
, which is a parent to bothstates
andcountries
. That is, both states and countries also have a foreign key toplaces
(you can even make this foreign key also be the primary key ofstates
andcountries
).Use two columns. Instead of one column that may reference either of two target tables, use two columns. These two columns may be
NULL
; in fact only one of them should be non-NULL
.In terms of relational theory, Polymorphic Associations violates First Normal Form, because the
popular_place_id
is in effect a column with two meanings: it's either a state or a country. You wouldn't store a person'sage
and theirphone_number
in a single column, and for the same reason you shouldn't store bothstate_id
andcountry_id
in a single column. The fact that these two attributes have compatible data types is coincidental; they still signify different logical entities.Polymorphic Associations also violates Third Normal Form, because the meaning of the column depends on the extra column which names the table to which the foreign key refers. In Third Normal Form, an attribute in a table must depend only on the primary key of that table.
Re comment from @SavasVedova:
I'm not sure I follow your description without seeing the table definitions or an example query, but it sounds like you simply have multiple
Filters
tables, each containing a foreign key that references a centralProducts
table.Joining the products to a specific type of filter is easy if you know which type you want to join to:
If you want the filter type to be dynamic, you must write application code to construct the SQL query. SQL requires that the table be specified and fixed at the time you write the query. You can't make the joined table be chosen dynamically based on the values found in individual rows of
Products
.The only other option is to join to all filter tables using outer joins. Those that have no matching product_id will just be returned as a single row of nulls. But you still have to hardcode all the joined tables, and if you add new filter tables, you have to update your code.
Another way to join to all filter tables is to do it serially:
But this format still requires you to write references to all tables. There's no getting around that.
This isn't the most elegant solution in the world, but you could use concrete table inheritance to make this work.
Conceptually you are proposing a notion of a class of "things that can be popular areas" from which your three types of places inherit. You could represent this as a table called, for example,
places
where each row has a one-to-one relationship with a row inregions
,countries
, orstates
. (Attributes that are shared between regions, countries, or states, if any, could be pushed into this places table.) Yourpopular_place_id
would then be a foreign key reference to a row in the places table which would then lead you to a region, country, or state.The solution you propose with a second column to describe the type of association happens to be how Rails handles polymorphic associations, but I'm not a fan of that in general. Bill explains in excellent detail why polymorphic associations are not your friends.