I couldn't think of a title for this and so didn't even know where to start researching for myself.
I have to make a database where I have a table for CD/DVDs but the type of entertainment on them requires different attributes in terms of metadata/information for example music CDs have artist, publisher, producer, CDNo. etc. Whereas a piece of software may have similarities but has some that music wont have and likely the same with movies and games. And so I'm not sure how this would work in terms of an E-R diagram, so far I decided on:
CD/DVDs being in the items table or stock table not sure on the name yet.
tbl_items -> item_id,
item_format(DVD or CD, maybe axpand to blu-ray or hd-dvd),
item_entertainment_type(Music, Movie etc.) <--- Maybe in another not sure.
foreign key to a metadata table, this is so that when deliveries for new CD/DVDs are made if the metadata already exists I just enter a new item and so its a one to many between metadata and items (items >-- meta).
The question I think is, is it bad practice to have null able foreign key fields and Just choose which to add a relation to, so musicMeta_id INT NULL, FOREIGN KEY musicMetaID REFERENCES tbl_musicMeta(musicMeta_id)
like that for each type? or somehow merge them, or is there a trick databaes have.
I'm using MySQL with php.
Thanks!
There is no general rule or Best Practice the foreign keys should not be nullable. Many times it makes perfect sense for an entity not to have a relationship with another entity. For example, you may have a table of artists you track but, at the moment, you have no CDs recorded by those artists.
As for having Media (CD, DVD, BluRay) that can be either music/audio or software, you can have a table with the information in common and then two foreign keys, one to each extension table (AudioData and SoftwareData), but one must be NULL. This presents a situation called, among other things, an exclusive arc. This is generally considered to be...problematic.
Think of a superclass and two derived classes in an OO language like Java or C++. One way to represent that in a relational schema is:
create table Media(
ID int not null, -- identity, auto_generated, generated always as identity...
Type char( 1 ) not null,
Format char( 1 ) not null,
... <other common data>,
constraint PK_Media primary key( ID ),
constraint FK_Media_Type foreign key( Type )
references MediaTypes( ID ), -- A-A/V, S-Software, G-Game
constraint FK_Media_Format foreign key( Format )
references MediaFormats( ID ) -- C-CD, D-DVD, B-BluRay, etc.
);
create unique index UQ_Media_ID_Type( ID, Type ) on Media;
create table AVData( -- For music and video
ID int not null,
Type char( 1 ) not null,
... <audio-only data>,
constraint PK_AVData primary key( ID ),
constraint CK_AVData_Type check( Type = 'A',
constraint FK_AVData_Media foreign key( ID, Type )
references Media( ID, Type )
);
create table SWData( -- For software, data
ID int not null,
Type char( 1 ) not null,
... <software-only data>,
constraint PK_SWData primary key( ID ),
constraint CK_SWData_Type check( Type = 'S',
constraint FK_SWData_Media foreign key( ID, Type )
references Media( ID, Type )
);
create table GameData( -- For games
ID int not null,
Type char( 1 ) not null,
... <game-only data>,
constraint PK_GameData primary key( ID ),
constraint CK_GameData_Type check( Type = 'G',
constraint FK_GameData_Media foreign key( ID, Type )
references Media( ID, Type )
);
Now if you are looking for a movie, you search the AVData table, then join with the Media table for the rest of the information and so on with software or games. If you have an ID value but don't know what kind it is, search the Media table and the Type value will tell you which of the three (or more) data tables to join with. The point is that the FK is referring to the generic table, not from it.
Of course, a movie or game or software can be released on more than one media type, so you can have intersection tables between the Media table and the respective data tables. Otoh, those are generally labeled with different SKUs so you may want to also treat them as different items.
The code, as you might expect, can get fairly complicated, though not too bad. Otoh, our design goal is not code simplicity but data integrity. This makes it impossible to mix, for instance, game data with a movie item. And you get rid of having a set of fields where only one must have a value and the others must be null.
My opinion: Get rid of the FOREIGN KEYs; just be sure you have suitable INDEXes.
Related
This is a question about database design. Say I have several tables, some of which each have a common expiry field.
CREATE TABLE item (
id INT PRIMARY KEY
)
CREATE TABLE coupon (
id INT PRIMARY KEY FOREIGN KEY (`item.id`),
expiry DATE NOT NULL
)
CREATE TABLE subscription (
id INT PRIMARY KEY FOREIGN KEY (`item.id`),
expiry DATE NOT NULL
)
CREATE TABLE product(
id INT PRIMARY KEY FOREIGN KEY (`item.id`),
name VARCHAR(32)
)
The expiry column does need to be indexed so I can easily query by expiry.
My question is, should I pull the expiry column into another table like so?
CREATE TABLE item (
id INT PRIMARY KEY
)
CREATE TABLE expiry(
id INT PRIMARY KEY,
expiry DATE NOT NULL
)
CREATE TABLE coupon (
id INT PRIMARY KEY FOREIGN KEY (`item.id`),
expiry_id INT NOT NULL FOREIGN KEY(`expiry.id`)
)
CREATE TABLE subscription (
id INT PRIMARY KEY FOREIGN KEY (`item.id`),
expiry_id INT NOT NULL FOREIGN KEY(`expiry.id`)
)
CREATE TABLE product(
id INT PRIMARY KEY FOREIGN KEY (`item.id`),
name VARCHAR(32)
)
Another possible solution is to pull the expiry into another base "class" table.
CREATE TABLE item (
id INT PRIMARY KEY
)
CREATE TABLE expiring_item (
id INT PRIMARY KEY FOREIGN KEY(`item.id`),
expiry DATE NOT NULL
)
CREATE TABLE coupon (
id INT PRIMARY KEY FOREIGN KEY (`expiring_item .id`),
)
CREATE TABLE subscription (
id INT PRIMARY KEY FOREIGN KEY (`expiring_item .id`),
)
CREATE TABLE product(
id INT PRIMARY KEY FOREIGN KEY (`item.id`),
name VARCHAR(32)
)
Given the nature of databases in that refactoring the table structure is difficult once they are being used, I am having trouble weighing the pros and cons of each approach.
From what I see, the first approach uses the least number of table joins, however, I will have redundant data for each expiring item. The second approach seems good, in that any time I need to add an expiry to an item I simply add a foreign key to that table. But, if I discover expiring items (or a subset of expiring items) actually share another attribute then I need to add another table for that. I like the third approach best, because it brings me closest to an OOP like hierarchy. However, I worry that is my personal bias towards OOP programming, and database tables do not use composition in the same way OOP class inheritance does.
Sorry for the poor SQL syntax ahead of time.
I would stick with the first design as 'redundant' data is still valid data if only as a record of what was valid at a point in time and it also allows for renewal with minimum impact. Also the second option makes no great sense as the expiry is an arbritrary item that has no real context outside of the table referencing, in other words unless it is associated with a coupon or a subscription it is an orphan value. Finally the third option makes no more sense in that at what point does a item become expiring? as soon as it is defined? at a set period before expiry...at the end of the day the expiry is an distinct attribute which happens to have the same name and purpose for both the coupon and the subscription but which isn't related to each other or as such the item.
Do not normalize "continuous" values such as datetime, float, int, etc. It makes it very inefficient to do any kind of range test on expiry.
Anyway, a DATE takes 3 bytes; an INT takes 4, so the change would increase the disk footprint for no good reason.
So, use the first, not the second. But...
As for the third, you say "expirations are independent", yet you propose having a single expiry?? Which is it??
If they are not independent, then another principle comes into play. "Don't have redundant data in a database." So, if the same expiry really applies to multiple connected tables, it should be in only one of the tables. Then the third schema is the best. (Exception: There may be a performance issue, but I doubt it.)
If there are different dates for coupon/subscription/etc, then you must not use the third.
I have two tables: Student & User
the Student table has a primary key of INT(9)
the User table has a primary key of MEDIUMINT
Now please take a look at this picture
(source: imgh.us)
Now the problem is: in the messages table i've the messageFrom and messageTo cols, I don't know weather the sender or the receiver is a Student or a User.
I can't reference the two tables because of different primary key types, however, am trying to avoid major changes as possible.
the same issue is everywhere reportedPosts table, comment table. Everywhere.
HOW to get around this issue or a possible solutions to fix this ?
AND please feel free to feedback the database structure, i would like to know and learn from your advices.
thanks in advance.
Both Users and Students (the entities, not the tables) are both examples of People. There are attributes of Users that don't belong to Students and there are attributes of Students that don't belong to Users. Also, there are actions Users may take that Students cannot take and vice versa. However, there are attributes common to both (name, address, phone number, etc.) and actions both may take (send/receive messages, post comments, etc.). This strongly implies a separate table to contain the common attributes and allow the common actions.
create table People(
ID MediumInt auto_generating primary key,
PType char( 1 ) not null check( PType in( 'U', 'S' )) -- User or Student
Name varchar( 64 ) not null,
Address varchar( 128 ),
..., -- other common attributes
constraint UQ_PeopleIDType unique( ID, PType ) -- create anchor for FKs
);
create table Users(
UserID MediumInt not null primary key,
UType char( 1 ) check( UType = 'U' ),
..., -- attributes for Users
constraint FK_Users_People foreign key( UserID, UType )
references People( ID, PType )
);
create table Students(
StudentID MediumInt not null primary key,
SType char( 1 ) check( SType = 'S' ),
..., -- attributes for Students
constraint FK_Students_People foreign key( StudentID, SType )
references People( ID, PType )
);
Notice that if a Person is created with a type of 'S' (Student), the ID value for that Person can only be inserted into the Student table.
Now all tables that must refer to Users may FK to the Users table and those that must refer to Students may FK to the Students table. When tables can refer to either, they may FK to the People table.
Foreign Keys do not solve all problems. Add a suitable index instead of depending on the FK. Then:
Plan A: Do the equivalent of FK checks in the application code, or
Plan B: Forgo any FK checks.
I'm making this database with superheroes for one of my school projects and I have a superheroes table (obviously) and an enemies table. So the enemies table have two foreign keys:
bad_superhero_id
and
good_superhero_id
The purpose of this table is to link the good superheroes with the bad superheroes (their enemies) from the characters table (superheroes). The both foreign keys are taking values from the id of the superheroes table. The problem is that my teacher doesn't like this and I don't know why. I mean, I saw this example in a book called Beginning PHP5, Apache, and MySQL Web Development and I also asked my coworkers that have good experience in creating database structure. They said it's not a problem, but my teacher wanted me to give her example where this is used, because she doesn't think it's good relationship and wants me to create a stupid workaround that she thought of. I still think this is not a bad way to create this kind of relationship so I wanted to ask here to get third opinion on this problem. I will be grateful if you give your opinion so that I can understand is it bad, good or doesn't matter practice to use relationship like this.
EDIT:
CREATE TABLE superhero (
id INT NOT NULL AUTO_INCREMENT,
nick_name VARCHAR,
align ENUM ('good', 'bad'),
PRIMARY KEY(id)
) ENGINE=INNODB;
CREATE TABLE enemies_link (
id INT NOT NULL AUTO_INCREMENT,
good_sh_id INT NOT NULL,
bad_sh_id INT NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (good_sh_id, bad_sh_id)
REFERENCES superheroes(id)
ON UPDATE CASCADE ON DELETE RESTRICT
) ENGINE=INNODB;
EDIT2: Yes, I forgot to add that I want that n to n connection. Let's say spider-man have venom and green goblin for his enemies and on the other hand venom has some other good superheroes as enemies and so on.
Your teacher may be right: you very likely should define both superhero and enemy ids as separate foreign keys:
FOREIGN KEY good_sh_id REFERENCES superheroes(id),
FOREIGN KEY bad_sh_id REFERENCES superheroes(id)
The syntax you specified, would instead specify superhero references as a composite foreign key. I have to admit I'm not sure what this even means. The only way composite foreign keys make sense to me is when you use them to reference a composite primary key.
Your design is not intrinsically a bad design but it needs work. You are using a cross/intersection table which defines an n-to-n relationship. These are used all the time in production databases, like the relationship between Student and Course where a student can be taking several courses and a course will have many students signed up. Yours is just referring both sides to the same table. That's fine too. A table of parts, for example, can contain both components and modules with a component used to make many modules and a module made up of many components.
In your particular instance, you have a flag which designates if the superhero is bad or good. That's good (although the concept of "bad hero" is somewhat jolting -- wouldn't "superbeing" be a better designation?), but the flag and the id must be defined together in a unique constraint/index. That may seem superfluous, since the id is a primary key and therefore unique all by itself. But a foreign key can only refer to a unique field or set of fields.
As for the cross table, you really don't need a separate id field. In fact, that opens up a possible chink in data integrity. When modeling, always try to make data integrity a prime factor. Make it as close to impossible as you can to get bogus data into the table. The table key will all be the foreign key fields as one big composite key. If a separate key is required by foolish design standards, then be sure to define the foreign key fields together in a unique index. Then you must enforce the values of the good/bad flags to insure the 'good' FK can only point to a 'good' superhero, and so forth.
CREATE TABLE superhero(
id INT NOT NULL AUTO_INCREMENT,
nick_name VARCHAR( 20 ),
align ENUM( 'good', 'bad' ) not null default 'good',
PRIMARY KEY( id ),
constraint unique id_align_uq( id, align )
) ENGINE=INNODB;
CREATE TABLE enemies_link(
good_sh_id INT NOT NULL,
good_align enum( 'good', 'bad' ) not null check( good_align = 'good' ),
bad_sh_id INT NOT NULL,
bad_align enum( 'good', 'bad' ) not null check( bad_align = 'bad' ),
PRIMARY KEY( good_sh_id, good_align, bad_sh_id, bad_align ),
FOREIGN KEY( good_sh_id, good_align )
REFERENCES superhero( id, align )
ON UPDATE CASCADE ON DELETE RESTRICT,
FOREIGN KEY( bad_sh_id, bad_align )
REFERENCES superhero( id, align )
ON UPDATE CASCADE ON DELETE RESTRICT
) ENGINE=INNODB;
I think you have the correct approach. I clearify and probably repeat what you have already designed with tis example.
table hero (in other words person)
person_id, name, good_bad
(by the way good_bad could vary, so consider if that is in the right place)
table opponent
person_id, opponent_person_id, battlefield
So you can have different opponents on different battlefields
The only problem you have is to assure double entries or a concept how to handle this: e.g.;
person_id = 7, opponent_person_id = 11, battlefield = Rome
person_id = 11, opponent_person_id =7, battlefield = Rome
in business this could be a realistic use:
table employment
chief_person_id,employe_person_id, department
chief_person_id = 7, employe_person_id = 10, department= 1
chief_person_id = 7, employe_person_id = 11, department= 1
chief_person_id = 9, employe_person_id = 12, department= 2
chief_person_id = 9, employe_person_id = 15, department= 2
chief_person_id = 12, employe_person_id = 16, department= 2 (even sub-hierarchies can be shown. see id=12)
The following is the JSON file...
{
"name":"Magic 2014 Core Set",
"code":"M14",
"releaseDate":"2013-07-19",
"border":"black",
"type":"core",
"cards":
[
{
"layout":"normal",
"type":"Creature - Human Warrior",
"types":["Creature"],
"colors":["Red"],
"multiverseid":370735,
"name":"Academy Raider",
"subtypes":["Human","Warrior"],
"cmc":3,
"rarity":"Common",
"artist":"Karl Kopinski",
"power":"1",
"toughness":"1",
"manaCost":"{2}{R}",
"text":"Intimidate (This creature can't be blocked except by artifact creatures and/or creatures that share a color with it.)\n\nWhenever Academy Raider deals combat damage to a player, you may discard a card. If you do, draw a card.",
"number":"124",
"imageName":"academy raider"
},
{
"layout":"normal",
"type":"Artifact - Equipment",
"types":["Artifact"],
"colors":[],
"multiverseid":370581,
"name":"Accorder's Shield",
"subtypes":["Equipment"],
"cmc":0,
"rarity":"Uncommon",
"artist":"Alan Pollack",
"manaCost":"{0}",
"text":"Equipped creature gets +0/+3 and has vigilance. (Attacking doesn't cause it to tap.)\n\nEquip {3} ({3}: Attach to target creature you control. Equip only as a sorcery.)",
"flavor":"An Auriok shield is polished to a mirror finish even on the inside, enabling its bearer to watch foes ahead and behind.",
"number":"204",
"imageName":"accorder's shield"
},
{
"layout":"normal",
"type":"Creature - Spirit",
"types":["Creature"],
"colors":["Black"],
"multiverseid":370811,
"name":"Accursed Spirit",
"subtypes":["Spirit"],
"cmc":4,
"rarity":"Common",
"artist":"Kev Walker",
"power":"3",
"toughness":"2",
"manaCost":"{3}{B}",
"text":"Intimidate (This creature can't be blocked except by artifact creatures and/or creatures that share a color with it.)",
"flavor":"Many have heard the slither of dragging armor and the soft squelch of its voice. But only its victims ever meet its icy gaze.",
"number":"83",
"imageName":"accursed spirit"
},
{...},
{...},
{...},
]
}
The cards data itself I think would be in a single table but I'm not sure how the...
"name":"Magic 2014 Core Set",
"code":"M14",
"releaseDate":"2013-07-19",
"border":"black",
"type":"core",
would be associated with the card data. How should I design the MySQL table(s) for easy and efficient access?
MySQL is a relational database. This means that any solution you come up with will need to include a primary key, a foreign key and normalization. Here is a simple tutorial that will show you what to do. Have fun!
http://www.dreamincode.net/forums/topic/179103-relational-database-design-normalization/
It's hard to say how data should be structured as that can depend on your application. However, as a first cut, some good rules of thumb might be:
All non-array data at the same "level" of a single JSON object is a single table. By level I mean how deeply nested the objects are. So, for example, given {"a": 100, "b": "hello", "c": {"x": 100, "y": "foo"}}, a, and b are on the same level, while x and y are on a different level.
You have a few options for handling data on different levels:
"flatten" the nesting so that, for the example above, you'd have a single table containing a, b, x, and y.
Create new tables for each nesting level. Given the example above, that's one table containing a and b, and one containing x and y. There's clearly a relationship between these two tables which tells you if you how to construct linking keys. See https://stackoverflow.com/a/7296873/1431244 for details on that.
Arrays pretty clearly indicate a one-to-many relationship so those go in their own table as described by the post linked above.
The JSON file above is pretty big, so I'm not going to construct all tables with all fields, but here's a sample that hopefully explains the rough idea:
create table card_pack (
# Primary key to uniquely identify the pack
id integer autoincrement primary key,
name TEXT,
# foreign key that links to the codes table
code_id integer,
# etc, etc...
);
create table codes (
# This is what the code_id field in the card_pack table refers to
id integer autoincrement primary key,
name CHAR(10)
);
create table cards (
# unique key for each card
id integer autoincrement primay key,
# Refers to the card_pack table for the card pack
# containing this card
pack_id integer,
name TEXT,
# This should probably be a foreign key referring to a layouts table
# which contains one row per layout
layout TEXT,
# etc, etc.
)
# Table with one row for every possible card color
create table colors {
id integer autoincrement primay key,
name TEXT,
)
# table that defines a many-to-many relationship
# indicating which cards are which colors, so a row with
# card_id = 7 and color_id = 11 means that card 7 is color 11.
# Note that another row might have card_id 7 and color_id 18
# so that card 7 is two colors, both color 11 and color 18.
create table cards_colors (
card_id integer,
color_id integer
)
In the above there's a lot of details missing. For example, you probably don't really want a generic TEXT type for all string fields. Some should probably be CHAR and some VARCHAR depending on field sizes, space vs. performance considerations, etc. Similarly where I have integer you might want bigint, mediumint, etc. depending on the number of values you expect, etc. There's also index considerations, foreign key constraints, and so on, but the above hopefully gives you the right idea and provides enough information to get started.
I think you must have 2 tables to store such data.
create table tbl_card (
card_id int primary key auto_increment,
name varchar(50) not null,
code varchar(10) not null,
release_date datetime not null,
border varchar(20) not null,
type varchar(20) not null
)
create table tbl_card_detail (
card_id int not null,
type varchar not null,
....
primary key (card_id,type)
)
Here is the Normalized Schema in Raw Form , You can alter it your needs and Update it with Null, Primary Key, Foreign Key attributes , Type with respect to database you are using
Bold (Highlighted) are table Names, PK = Primary key, FK = Foreign Key, u can alter as per your needs
Template (TABLE)
1- Name
2- Code
3- Release Date
4- Border
5- Type
6- Id (PK)
Template Cards (TABLE)
1- Template Id (FK) (Template Table )
2- Card Id (FK) (Cards Table)
Cards ( Has M-M relationship with Types, Cards ,Subtypes Table) (TABLE)
1- layout
2- type
3- mutiverseid
4- name
5- Card Id (PK)
6- Card Detail Id
Cards Detail
1- Card detail Id
2- Card Id
2- Object Type ( 0 = Types , 1 = Color , 2 = Subtypes )
3- Object Id ( This id corresponds to Types, Color , Subtypes Table with respect to Object Type )
Types (TABLE)
1- type id (PK)
2- type Detail/Code
Color (TABLE)
1- Color id (PK)
2- Color Detail/Code
SubTypes (TABLE)
1- Subtype id (PK)
2- Subtype Detail/Code
I think you should go with a table cardSet that would contain (name, code, releaseDate, border, type) and another table for cards with a foreign key referring to cardSet
you would also need to make type, color, subtype tables that would have a many to many relationship with the card table since you can have a card with more than one type, color or subtype
CREATE TABLE `card` (
`id` INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
CREATE TABLE `type` (
`id` INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
CREATE TABLE `cardType` (
`card` INT,
`type` INT
);
CREATE TABLE `cardSet` (
`id` INT NOT NULL AUTO_INCREMENT,
`` INT,
PRIMARY KEY (`id`)
);
CREATE TABLE `color` (
`id` INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
CREATE TABLE `cardColor` (
`card` INT,
`color` INT
);
CREATE TABLE `subType` (
`id` INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
CREATE TABLE `cardSubType` (
`card` INT,
`subType` INT
);
ALTER TABLE `cardType` ADD CONSTRAINT `cardType_fk1` FOREIGN KEY (`card`) REFERENCES card(`id`);
ALTER TABLE `cardType` ADD CONSTRAINT `cardType_fk2` FOREIGN KEY (`type`) REFERENCES type(`id`);
ALTER TABLE `cardSet` ADD CONSTRAINT `cardSet_fk1` FOREIGN KEY (``) REFERENCES cardSet(`id`);
ALTER TABLE `cardColor` ADD CONSTRAINT `cardColor_fk1` FOREIGN KEY (`card`) REFERENCES card(`id`);
ALTER TABLE `cardColor` ADD CONSTRAINT `cardColor_fk2` FOREIGN KEY (`color`) REFERENCES color(`id`);
ALTER TABLE `cardSubType` ADD CONSTRAINT `cardSubType_fk1` FOREIGN KEY (`card`) REFERENCES card(`id`);
ALTER TABLE `cardSubType` ADD CONSTRAINT `cardSubType_fk2` FOREIGN KEY (`subType`) REFERENCES subType(`id`);
I have following comments table in my app:
comments
--------
id INT
foreign_id INT
model TEXT
comment_text TEXT
...
the idea of this table is to store comments for various parts of my app - it can store comments for blog post i.e.:
1|34|blogpost|lorem ipsum...
user picture:
2|12|picture|lorem ipsum...
and so on.
now, is there a way to force FOREIGN KEY constraint on such data?
i.e. something like this in comments table:
FOREIGN KEY (`foreign_id`) REFERENCES blogposts (`id`)
-- but only when model='blogpost'
You're attempting to do a design that is called Polymorphic Associations. That is, the foreign key may reference rows in any of several related tables.
But a foreign key constraint must reference exactly one table. You can't declare a foreign key that references different tables depending on the value in another column of your Comments table. This would violate several rules of relational database design.
A better solution is to make a sort of "supertable" that is referenced by the comments.
CREATE TABLE Commentable (
id SERIAL PRIMARY KEY
);
CREATE TABLE Comments (
comment_id SERIAL PRIMARY KEY,
foreign_id INT NOT NULL,
...
FOREIGN KEY (foreign_id) REFERENCES Commentable(id)
);
Each of your content types would be considered a subtype of this supertable. This is analogous to the object-oriented concept of an interface.
CREATE TABLE BlogPosts (
blogpost_id INT PRIMARY KEY, -- notice this is not auto-generated
...
FOREIGN KEY (blogpost_id) REFERENCES Commentable(id)
);
CREATE TABLE UserPictures (
userpicture_id INT PRIMARY KEY, -- notice this is not auto-generated
...
FOREIGN KEY (userpicture_id) REFERENCES Commentable(id)
);
Before you can insert a row into BlogPosts or UserPictures, you must insert a new row to Commentable to generate a new pseudokey id. Then you can use that generated id as you insert the content to the respective subtype table.
Once you do all that, you can rely on referential integrity constraints.
In MySQL 5.7 you can have a single polymorphic table AND enjoy something like a polymorphic foreign key!
The caveat is that technically you will need to implement it as multiple FKs on multiple columns (one per each entity that has comments), but the implementation can be limited to the DB side (i.e. you will not need to worry about these columns in your code).
The idea is to use MySQL's Generated Columns:
CREATE TABLE comments (
id INT NOT NULL AUTO_INCREMENT,
foreign_id INT,
model TEXT,
commented_text TEXT,
generated_blogpost_id INT AS (IF(model = 'blogpost', foreign_id, NULL)) STORED,
generated_picture_id INT AS (IF(model = 'picture', foreign_id, NULL)) STORED,
PRIMARY KEY (id) ,
FOREIGN KEY (`generated_blogpost_id`) REFERENCES blogpost(id) ON DELETE CASCADE,
FOREIGN KEY (`generated_picture_id`) REFERENCES picture(id) ON DELETE CASCADE
)
You can ignore the generated_* columns; they will be populated automatically by MySQL as comments are added or modified, and the FKs defined for them will ensure data consistency as expected.
Obviously it would impact both the size requirements and performance, but for some (most?) systems it would be negligible, and a price worth paying for achieving data consistency with a simpler design.