Foreign key for multiple tables and columns? - mysql

I have been learning about Foreign keys and I wanted to know if the way I used it is correct in the example below:
CREATE TABLE user(
id INT(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(20) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE items(
i_id INT(11) NOT NULL AUTO_INCREMENT,
name TINYTEXT NOT NULL,
price DECIMAL(8,2) NOT NULL,
PRIMARY KEY (i_id)
);
CREATE TABLE user_purchase(
i_id INT(11) NOT NULL,
name TINYTEXT NOT NULL,
id INT(11) NOT NULL,
FOREIGN KEY (i_id) REFERENCES items(i_id),
FOREIGN KEY (name) REFERENCES items(name),
FOREIGN KEY (id) REFERENCES user(id)
);
Thanks
Now, how can I get maximum information from just the Foreign Key if I use, let's say, PHP?

You don't have to include item name in both tables. This is called a denormalized solution.
You should have it only in the items table and only refer to the id, then if you need the name also you can join it based on the primary key(id).
Otherwise it is totally OK in my opinion.
CREATE TABLE user(
id INT(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(20) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE items(
i_id INT(11) NOT NULL AUTO_INCREMENT,
name TINYTEXT NOT NULL,
price DECIMAL(8,2) NOT NULL,
PRIMARY KEY (i_id)
);
CREATE TABLE user_purchase(
i_id INT(11) NOT NULL,
name TINYTEXT NOT NULL,
id INT(11) NOT NULL,
FOREIGN KEY (i_id) REFERENCES items(i_id),
FOREIGN KEY (id) REFERENCES user(id)
);
Sometimes when performance is critical you have to use denormalized tables. It can be much faster.
Normalization is important to avoid different anomalies.
If you have tables in a high level normal form then your tables won't be redundant and wont have these anomalies. For example if you have something stored in multiple locations you have to look after to keep all the redundant data up to date. This gives you a chance to do this incorrectly and end up having different anomalies.
In your situation having a foreign key helps you to keep data integrity but without a foreign key for the name you would be able to have items with names in the purchases that are not present in the items table.
This is a kind of anomaly.
There are many kinds of this, best to avoid them as long as you can.
Read more here about anomalies
In some cases you must denoramalize. So store some data redundantly because of performance issues. This way you can save some join operations that could consume much time.
Details of normalization are covered by topics of different normal forms:
NF0, NF1, NF2, NF3 and BCNF
Normal forms in detail
For further details about the mathematical foundations of loseless decomposition to higher normal forms see "Functional dependencies". This is going to help you understand why you can keep the ids "redundant". Virtually they are necessary redundancy, since you need them in order to be able to later rebuild the original dataset. This is going to be the definition for the different normal forms. What level of this redundancy is allowed?
Functional Dependencies

you've got an i_id as a primary key, you don't need to set up a name as a foreign key. and btw foreign key has to reference to unique attribute.
CREATE TABLE `user`(
id INT(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(20) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE items(
i_id INT(11) NOT NULL AUTO_INCREMENT,
`name` TINYTEXT UNIQUE NOT NULL,
price DECIMAL(8,2) NOT NULL,
PRIMARY KEY (i_id)
);
CREATE TABLE user_purchase(
i_id INT(11) NOT NULL,
`name` TINYTEXT NOT NULL,
id INT(11) NOT NULL,
FOREIGN KEY (i_id) REFERENCES items(i_id),
FOREIGN KEY (id) REFERENCES `user`(id)
);

Related

Is it required to specify a Constraint Name when adding a Foreign Key in MySQL?

So far I've created the tables manually like this and I didn't have any issues.
CREATE TABLE users (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
email VARCHAR(100) NOT NULL,
cityId BIGINT(20) UNSIGNED NULL DEFAULT NULL,
PRIMARY KEY (id),
FOREIGN KEY (cityId)
REFERENCES cities (id));
Now, I'm using MySQL Workbench that generates the SQL Script for me, and the SQL script looks a bit different.
CREATE TABLE users (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
email VARCHAR(100) NOT NULL,
cityId BIGINT(20) UNSIGNED NULL DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT cityId
FOREIGN KEY (cityId)
REFERENCES locations (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION);
I'm wondering if there is any difference between these two queries.
You are not required to provide a name for a foreign key, and if you don't, an autogenerated name will be assigned to it. Having said that, it's a good practice and will make your maintenance work easier.

MySQL Error - Cannot add foreign key constraint

I know this question has been asked several times but it seemed like the problem was due to different data types between parent and child rows.
In my case, the data types are the same but I'm still getting the error. Here's my code
CREATE TABLE STUDENT_2(
StudentNumber INT NOT NULL AUTO_INCREMENT,
StudentName VARCHAR(50) NULL,
Dorm VARCHAR(50) NULL,
RoomType VARCHAR(50) NOT NULL,
CONSTRAINT STUDENTPK PRIMARY KEY(StudentNumber)
);
CREATE TABLE DORM_COST(
RoomType VARCHAR(50) NOT NULL,
DormCost DECIMAL(7,2) NULL,
CONSTRAINT DORM_COSTPK PRIMARY KEY(RoomType),
CONSTRAINT DORM_COST_FK FOREIGN KEY(RoomType)
REFERENCES STUDENT_2(RoomType)
ON UPDATE CASCADE
ON DELETE CASCADE
);
Where DORM_COSTS' foreign key cannot be added.
Thanks!
You want the foreign key reference on the table that has the foreign key, not the primary key. So, that would be:
CREATE TABLE DORM_COST (
RoomType VARCHAR(50) NOT NULL,
DormCost DECIMAL(7,2) NULL,
CONSTRAINT DORM_COSTPK PRIMARY KEY(RoomType)
);
CREATE TABLE STUDENT_2(
StudentNumber INT NOT NULL AUTO_INCREMENT,
StudentName VARCHAR(50) NULL,
Dorm VARCHAR(50) NULL,
RoomType VARCHAR(50) NOT NULL,
CONSTRAINT STUDENTPK PRIMARY KEY(StudentNumber),
CONSTRAINT fk_student2_roomtype FOREIGN KEY (RoomType) REFERENCES DORM_COST(RoomType)
);
Here is a db<>fiddle that shows that this works.
That said your data model seems quite strange.
I would expect a table called RoomTypes to have a primary key of RoomTypeId.
I would expect dorm_cost to have dates, because costs can vary from year to year.
I would expect different dorms to have similar room types -- singles, doubles, and so on.
I would expect those room types to vary, perhaps, by dorm.

How to Use One General Table for Relationships

What I have now is table that is "hardcoded" to have a relationship between two of the same table with two foreign keys to that table.
Before 'Relationship' table:
CREATE TABLE IF NOT EXISTS `Item_To_Item` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`item1_id` INT(11) NOT NULL,
`item2_id` INT(11) NOT NULL,
`relationship` ENUM('requires', 'mutually_requires', 'required_by', 'relates', 'excludes') NULL DEFAULT NULL,
`description` VARCHAR(1000) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_Item1_Id`
FOREIGN KEY (`item1_id`)
REFERENCES `Item` (`id`)
CONSTRAINT `fk_Item2_Id`
FOREIGN KEY (`item2_id`)
REFERENCES `Item` (`id`)
So before this had a double many to one reference on the item table to fill the two foreign keys.
There is a need now to expand this relationship to be more general between tables in the db (Enum, Tag, feature etc). So that now Items can relate to items, items can relate to tags, tags can relate to tags etc with the enum relationship value.
What I am thinking for the general table is adding a type table, so each item, tag, etc can be identified, and then restructuring the relationship table to be something like this:
CREATE TABLE IF NOT EXISTS `Relationship` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`relater_id` INT(11) NOT NULL,
`relatee_id` INT(11) NOT NULL,
`relationship` ENUM('requires', 'mutually_requires', 'required_by', 'relates', 'excludes') NULL DEFAULT NULL,
`description` VARCHAR(1000) NULL DEFAULT NULL,
`relater_type_id` INT(11) NULL,
`relatee_type_id` INT(11) NULL,
PRIMARY KEY (`id`),
INDEX `fk_Relatee_Id` (`relatee_id` ASC),
INDEX `fk_Relater_Id` (`relater_id` ASC),
CONSTRAINT `fk_Relater_Id`
FOREIGN KEY (`relater_id`)
CONSTRAINT `fk_Relatee_Id`
FOREIGN KEY (`relatee_id`)
So that now you can identify what type of items are being related by the type_id and table and this can be opened up so any two table ids can go into the Relater and Relatee foreign key columns.
The problem is that I do not know how to have such generality with foreign keys. I believe they can only reference one table so I am not sure how to do what I want with a general key reference. Also, I can see a problem with bidirectional relationships where A mutually Requires B and B mutually Requires A being redundant data. I could block this redundancy in my application, but I would constantly have to check for two sided A to B || B to A. I was wondering the best way to accomplish what I am trying to do. Thank you.
Edit: Maybe using some kind of base type for my (item, feature, tag) could help me?
Edit: I don't think the answer is as simple as inheritance. At least from what I can tell. My problem is that I want to relate two general items no matter the type. I don't want 20 columns that have to be null because it is not that specific type. I just want to be able to pass two ids and therefore two type_ids into the relationship so I can relate any two objects.
One potential solution is to implement object_type and object_index tables:
CREATE TABLE object_type (
`object_type_id` int(11) NOT NULL AUTO_INCREMENT,
`object_type` varchar(30) NOT NULL,
PRIMARY KEY (`object_type_id`),
UNIQUE (`object_type`));
CREATE TABLE object_index (
`object_id` int(11) NOT NULL AUTO_INCREMENT,
`object_type_id` int(11) NOT NULL,
PRIMARY KEY (`object_id`),
UNIQUE (`object_type_id`, `object_id`));
and define your relations against that table only.
CREATE TABLE IF NOT EXISTS `Relationship` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`relater_id` INT(11) NOT NULL,
`relatee_id` INT(11) NOT NULL,
`relationship` ENUM('requires', 'mutually_requires', 'required_by', 'relates', 'excludes') NULL DEFAULT NULL,
`description` VARCHAR(1000) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `fk_Relatee_Id` (`relatee_id` ASC),
INDEX `fk_Relater_Id` (`relater_id` ASC),
CONSTRAINT `fk_Relater_Id`
FOREIGN KEY (`relater_id`)
references object_index (`object_id`),
CONSTRAINT `fk_Relatee_Id`
FOREIGN KEY (`relatee_id`)
references object_index (`object_id`));
Next each of your object tables are defined such they relate back to the object_index on the unique (object_type_id, object_id) tuple. In this example each tables default and check constrained object_type_id should be unique:
CREATE TABLE table1 (
`object_id` int(11) NOT NULL,
`object_type_id` int(11) NOT NULL DEFAULT 1 CHECK (object_type = 1),
`col1` varchar(4),
PRIMARY KEY (`object_id`),
CONSTRAINT fk_t1_ob_idx
FOREIGN KEY (`object_type_id`, `object_id`)
REFERENCES object_index (`object_type_id`, `object_id`));
In MySQL 5.6 and above you could define a virtual/computed column on each table to match object_type_id from the object_index instead of a stored physical column.
In MySQL 8.0 and above you might be able to define a function based index on each table that includes the discriminator object_type_id as an expression instead of as a physical or virtual column in the table.

errno: 150 "Foreign key constraint is incorrectly formed" Nothing helps

sorry for maybe stupid question, but I stack with my sql query. Tried out many methods to avid it but still have error 150. I've created 3 tables and one with foreighn keys:
Table users
create table users(
id int(11) primary key auto_increment,
unique_id varchar(23) not null unique,
name varchar(50) not null,
cname varchar(50) not null,
email varchar(100) not null unique,
encrypted_password varchar(80) not null,
salt varchar(10) not null,
created_at datetime,
updated_at datetime null
);
Table behaviours
CREATE TABLE behaviours (
id int(2) AUTO_INCREMENT PRIMARY KEY,
bName varchar(100) NOT NULL
);
Table severytys
CREATE TABLE severitys (
id int(2) AUTO_INCREMENT PRIMARY KEY,
severity varchar(10) NOT NULL
);
And here it shows errors:
CREATE TABLE child_behaviours(
id int(11) primary key auto_increment,
name varchar(50) not null,
cname varchar(50) not null,
bName varchar(100) NOT NULL,
severity varchar(10) NOT NULL,
start_at datetime,
stop_at datetime null,
FOREIGN KEY (id) REFERENCES users(id),
FOREIGN KEY (bName) REFERENCES behaviours(bName),
FOREIGN KEY (severity) REFERENCES severitys(severity)
);
Will be really pleasent to have an answer to this issue
You can only create a FK on a column that is both UNIQUE and NOT NULL. This is usually, but not always, the primary key of the referenced table.
If you reference anything other than the PK, you need a really really good reason.
(I'd even add to that, that you need a really really good reason to use anything other than an INT or a couple INTs as a PK...)
Now, child_behaviours has several errors:
Duplication of columns already present in users. If you reference users by its id, there is no need to duplicate the columns.
Reference to behaviors(bName) instead of using its id
same thing for reference to severity
Also, there is the generic mistake of using "id" columns. Please call them "user_id", "severity_id", etc, and then call them the same in your references. This will make your life much easier, and avoid stuff like:
SELECT foo.id AD foo_id, bar.id AS bar_id FROM foo JOIN bar ON ...
You have to refer to a primary key, when building a foreign key
CREATE TABLE child_behaviours(
userId int not null,
behaviourId int NOT NULL,
severityId int NOT NULL,
start_at datetime not null,
stop_at datetime,
FOREIGN KEY (userId) REFERENCES users(id),
FOREIGN KEY (behaviourId) REFERENCES behaviours(id),
FOREIGN KEY (severityId) REFERENCES severitys(id)
);

cakephp model has 0 or 1 relationship

In cakephp, I have Company and Profile model. I would like to establish Company has 0 or 1 Profile relationship. Is it appropriate to use Company hasOne Profile, and Profile belongsTo Company? Is there any implication I need to be aware of? The table's schema is below. Thanks.
create table companies (
id char(36) NOT NULL,
type int(10) NOT NULL,
primary key (id)
);
create table profiles (
id char(36) NOT NULL,
company_id char(36) NOT NULL,
provider_contact varchar(255) NULL,
primary key (id),
unique key (company_id),
foreign key (company_id) references companies(id)
);
Yes you can use the hasOne/belongsTo Relationship, if a Company has no Profile the sub-array will be empty.
In your profiles table you should use company_id to follow the Cake naming conventions.
To follow CakePHP's conventions, the field needs to be company_id, not companies_id.
The foreign key constraint is up to you. I typically don't add them. The unique constraint seems incorrect. This would imply that there can only be one profile per company. Which is not the same as a company can have 0 or 1 profiles.
Stick to conventions.
You need to adjust your schema to do that
create table companies (
id int(11) NOT NULL,
type int(11) NOT NULL,
primary key (id)
);
create table profiles (
id int(11) NOT NULL,
company_id int(11) NOT NULL,
provider_contact varchar(255) NULL,
primary key (id),
unique key (company_id),
foreign key (company_id) references companies(id)
);
It is company_id as others have mention. And also, ids have to be ints, and autoincremental. It's a pain to have them as chars.