MySQL - Conditional Foreign Key Constraints - mysql

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.

Related

When to use foreign key as a primary key at the same time?

I have get an intermediate table ArticleLanguage
idArticleLanguage
ArticleId
LanguageId
Name
Foreign keys are:
ArticleId
LanguageId
Should I use primary keys for:
ArticleId
LanguageId
Because these fields are primary keys in related tables?
Link / Junction Tables
Assuming the linked tables are defined as:
CREATE TABLE Article
(
ArticleId INT PRIMARY KEY
-- ... other columns
);
CREATE TABLE Language
(
LanguageId INT PRIMARY KEY
-- ... other columns
);
As per #JulioPĂ©rez Option 1, the link table could be created as:
CREATE TABLE ArticleLanguage
(
ArticleId INT NOT NULL,
LanguageId INT NOT NULL,
Name VARCHAR(50),
-- i.e. Composite Primary Key, consisting of the two foreign keys.
PRIMARY KEY(ArticleId, LanguageId),
FOREIGN KEY(ArticleId) REFERENCES Article(ArticleId),
FOREIGN KEY(LanguageId) REFERENCES Language(LanguageId)
);
i.e. with a composite primary key consisting of the two foreign keys used in the "link" relationship, and with no additional Surrogate Key (idArticleLanguage) at all.
Pros of this approach
Enforces uniqueness of the link, i.e. the same ArticleId and LanguageId cannot be linked more than once.
Saves an unnecessary additional surrogate key column on the link table.
Cons of this approach:
Any downstream tables which needs to reference this link table, would need to repeat both keys (ArticleId, LanguageId) as a composite foreign key, which would again consume space. Queries involving downstream tables which reference ArticleLanguage would also be able to join directly to Article and Language, potentially bypassing the link table (it is often easy to 'forget' that both keys are required in the join when using foreign composite keys).
SqlFiddle of option 1 here
The alternative (#JulioPĂ©rez Option 2), would be to to keep your additional surrogate PK on the reference table.
CREATE TABLE ArticleLanguage
(
-- New Surrogate PK
idArticleLanguage INT NOT NULL AUTO_INCREMENT,
ArticleId INT NOT NULL,
LanguageId INT,
Name VARCHAR(50),
PRIMARY KEY(idArticleLanguage),
-- Can still optionally enforce uniqueness of the link
UNIQUE(ArticleId, LanguageId),
FOREIGN KEY(ArticleId) REFERENCES Article(ArticleId),
FOREIGN KEY(LanguageId) REFERENCES Language(LanguageId)
);
Pros of this Approach
The Primary Key idArticleLanguage is narrower than the composite key, which will benefit any further downstream tables referencing table ArticleLanguage. It also requires downstream tables to join through the ArticleLanguage link table in order to get ArticleId and LanguageId, for further joins to the Language and Article tables.
The approach allows for an additional use case, viz that if it IS possible to add the same link to Language and Article more than once (e.g. two revisions or two reprints etc), then the UNIQUE key constraint can be removed
Cons of this Approach
If only one unique link per Article and Language is possible, then the additional surrogate key is redundant
SqlFiddle of option 2 here
If you're asking for an opinion, I would stick with option 1, unless you do require non-unique links in your ArticleLanguage table, or unless you have many further downstream tables which reference ArticleLanguage (this would be unusual, IMO).
Table per Type / per Class Inheritance
Unrelated to OP's post, but another common occurrence where a Foreign Key can be used as a Primary Key in the referencing table is when the Table per Type approach is taken when modelling an object oriented class hierarchy with multiple subclasses. Because of the 0/1 to 1 relationship between subclass and base class tables, the base class table's primary key can also be used as the primary key for the subclass tables, for instance:
CREATE TABLE Animal
(
AnimalId INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-- Common Animal fields here
);
CREATE TABLE Shark
(
AnimalId INT NOT NULL PRIMARY KEY,
-- Subclass specific columns
NumberFins INT,
FOREIGN KEY(AnimalId) REFERENCES Animal(AnimalId)
);
CREATE TABLE Ewok
(
AnimalId INT NOT NULL PRIMARY KEY,
-- Subclass specific columns
Fleas BOOL,
FOREIGN KEY(AnimalId) REFERENCES Animal(AnimalId)
);
More on TPT and other OO modelling in tables here
You have 2 ways:
1) Put "ArticleId + LanguageId" as your only primary key in "intermediate table" and you can name it as "idArticleLanguage". This is called a "composite" primary key because it is composed by 2 (in other case more than 2) fields, in this case 2 foreign keys (PK= FK + FK).
2) Create "idArticleLanguage" that has no relation to the other two "id" and set it as primary key.It can be a simple auto-increment integer.
Both alternatives are accepted. Your election will depend on the goal you want to achieve because what happens if you need to add in this intermediate table the same Article with the same language (Wilkommen German for example) because you have 2 different editions of the article? if you choose alternative 1 it will throw an error because you will have the same composite primary key for 2 rows then you must choose alternative 2 and create a completely different primary key for this table
In any other case (or purpose) you can choose alternative 1 and it will work perfectly
About the change of your question title:
When use foreign key as primary key in the same time?
I will explain it with this example:
You have 2 tables: "country" and "city". "country" have all the countries of the world, "city" have all the cities of the world. But you need to know every capital in the world. What you should do?
You must create an "intermediate table" (named as "capital") that will have every capital on the world. So, we know that country have it's primary key "idcountry" and city have it's primary key is "idcity" you need to bring both as foreign keys to the table "capital" because you will need data of "city" and "country" tables to fill "capital" table
Then "capital" will have it's own primary key "idcapital" that can be a composite one "idcity+idcountry" or it can be an auto-increment integer in both cases you must have "idcity" and "idcountry" as foreign keys on your "capital" table.

Confused about foreign key constraint

I have a general question about constraint.
What are the difference between the following examples?
CREATE TABLE Orders (
OrderID int NOT NULL PRIMARY KEY,
OrderNumber int NOT NULL,
PersonID int FOREIGN KEY REFERENCES Persons(PersonID)
);
CREATE TABLE Orders (
OrderID int NOT NULL,
OrderNumber int NOT NULL,
PersonID int,
PRIMARY KEY (OrderID),
CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID)
REFERENCES Persons(PersonID)
);
Thank you!
There is no logical difference.
Standard SQL supports both forms of declaring constraints: at the column level, as in your first example, and at the table level, in your second example.
Table level constraint syntax is needed if you have a primary key or foreign key that involves more than one column.
MySQL supports both column-level and table-level syntax for PRIMARY KEY. But if you subsequently run SHOW CREATE TABLE Orders you will see that MySQL reports it back as if it was declared as a table-level constraint.
MySQL supports only table-level syntax for FOREIGN KEY.
It has been a long-time feature request to support column-level FOREIGN KEY syntax, but so far it has not been implemented. https://bugs.mysql.com/bug.php?id=4919
In the first example, The database will name the constraints implicitly.
In the second example, the create table statement sets the name of the foreign key constraint explicitly. (the primary key should also be named but it's not in this example)
As best practice, you should always give your constraints meaningful names.

MySql: Composite Unique Key

I want to make composite key of 2 column id & code,the both columns altogether should act like Unique key for the table. while I have browsed and tried to create a table as follows,
Create table test (
`test_no` int not null AUTO_INCREMENT,
`code` varchar(5) NOT NULL,
`name` varchar(255),
`UPDBy` varchar(255),
PRIMARY KEY (`test_no`),
FOREIGN KEY (code) REFERENCES other_table(code)
// CONSTRAINT `K_test_1` Unique KEY (`test_no`,`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Just a second thought, can i make both the column as PK ? I think it will serve my purpose, RIght?
CONSTRAINT `K_test_1` Primary KEY (`test_no`,`code`) OR Primary KEY (`test_no`,`code`)
You seem to be on the wrong track somehow. Your table has an ID which is auto incremented. This is not supposed to be the primary key? Why do you call it ID then?
There are two ways to build a database: Either use the natural values a user is used to, such as an employee number a department number and so on. Or use IDs (which are usually hidden from the user). Than you would have an employee table with primary key "id" or "employee_id" or whatever, and the employee number just as a field. But as it must be unique, you would have an additional unique index on that field.
Having said that; you have a table "other_table" with primary key "code" it seems. So you are not using an ID concept here. Then why do you use it on table test? If this is a detail table on other_table, then I'd expect the composite key to be something like code + test_no (thus showing numbered tests per code) for isntance.
So the essence is: 1. Think about what your table contains. 2. Think about wether to use IDs or natural keys. The answer to these questions should help you find the correct key for your table. (And sometimes a table even doesn't have a primary key and needs none.)
You sure can make them both as PRIMARY KEY. If you don't want to, just use UNIQUE instead of UNIQUE KEY.
To set both as PRIMARY KEY, do as it follows:
...
PRIMARY KEY (`id`, `code`);
...
To set a UNIQUE CONSTRAINT, do as it follows:
...
CONSTRAINT `K_test_1` UNIQUE (`id`,`code`);
...

MYSQL Error # 1005

I have been trying to create a foregin key with nbrseats but I i get the error 1005 all the time.
CAn someone help me!?
create table theater (
name varchar(30) primary key,
nbrseats int not null
) ENGINE=INNODB;
create table reservation (
nbr integer auto_increment,
users_username varchar(30),
cinemashow_showdate date,
movies varchar(30),
nbrseats int not null,
primary key (nbr),
foreign key (nbrseats) references theater(nbrseats),
foreign key (users_username) REFERENCES users(username)
on delete cascade,
foreign key (cinemashow_showdate, movies) references cinemashow(showdate, movie_title)
on delete cascade
) ENGINE=INNODB;
In order to be a FOREIGN KEY in another table, you must have an index created on theater.nbrseats. And in order to be able to reference a specific row reliably, it should therefore be a UNIQUE index. Otherwise, if you have duplicate values, the referencing table won't be able to discern which row it references. Even though InnoDB will allow you to create the relationship on a non-unique index, it is likely not the behavior you are looking for.
See this question for more info on that bit.
create table theater (
name varchar(30) primary key,
nbrseats int not null,
UNIQUE INDEX `idx_nbrseats` (nbrseats)
) ENGINE=INNODB;
The same will be true of the other FOREIGN KEY definitions in your table reservation, though we do not see their referenced tables posted here. The rules are:
The referenced column must be indexed (independently of any other compound indexes on it)
The referencing column must have exactly the same data type.
This kind of calls into question your design, however. If you are attaching a number of seats to a reservation, will the reservation number of seats exactly match the number available in the theater? Also this means that you could not have 2 theaters with the same number of seats.
You may need to reconsider your design here, and perhaps create a FOREIGN KEY that references theater.name instead of theater.nbrseats.

Class Table Inheritance (CTI) and Insert Select statements

I am working to build an inheritance database model within MySQL, such that all tables inherit from one base type (object), represented by the object table. This allows for Notes to be linked to any object from any table within the database while retaining referential integrity. The design looks something like this (there are a lot more child tables with similar structures):
CREATE TABLE object
(
object_id INT(10) AUTO_INCREMENT,
object_type VARCHAR(80),
PRIMARY KEY (object_id)
);
CREATE TABLE person
(
person_id INT(10),
name_first VARCHAR(80),
name_last VARCHAR(80),
email_address VARCHAR(80),
PRIMARY KEY (person_id),
CONSTRAINT fk_person FOREIGN KEY (person_id)
REFERENCES object (object_id)
);
CREATE TABLE note
(
note_id INT(10),
not_text TEXT,
note_subject_id INT(10),
PRIMARY KEY (note_id),
CONSTRAINT fk_note FOREIGN KEY (note_id)
REFERENCES object (object_id),
CONSTRAINT fk_note_subject FOREIGN KEY (note_subject_id)
REFERENCES object (object_id)
);
With this design, I am able to make a note with a person as the subject, a note with another note as the subject, or a note with one of the many other tables inheriting from object as a subject (these tables are not listed for brevity). Although it cannot be enforced through referential integrity, a presupposition of this design is that each object_id is used in only one row of one child table, so that there are no notes where the note_id is also a person_id.
The problem occurs when I want to perform INSERT... SELECT statement on person or note. Let's say that I have a user table and I would like to insert all users in to person. First I have to insert the number of new person rows I am creating into object, then I need to insert the new rows into person, but I have no way of matching each user row to an object row in order to populate the person_id column.
My first thought was to create a BEFORE INSERT TRIGGER on person that would create the new object record and update the NEW.person_id value accordingly. Unfortunately, the foreign key constraint is being evaluated before the trigger is allowed to fire, catching my orphaned row before I can correct it.
What I am looking for is either a way to change the order of constraint execution so that BEFORE INSERT triggers precede constraints, or for a more elegant way to achieve an Class Table Inheritance database structure within MySQL.
This is not object orientation, you just want to implement inheritance in your RDBMS. You have three choices: Horizontal Mapping, Vertical Mapping, Filtered Mapping.
Reference: http://modeling.sourceforge.net/UserGuide/design-inheritance.html
You may not need inheritance though, if you relax a little on reference integrity. Your Note table may contain multiple nullable foreign keys, one for each table you want to add the note to.