Referencing table with generated primary keys - mysql

I am trying to increase the constraint of my MySQL database schema adding foreign key constraint to each table.
Table 1: USERS
+---------+----------+-------------
| id | username | Other fields
+---------+----------+-------------
| 1 | John |
| 2 | Mark |
+---------+----------+-------------
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT
username` VARCHAR(50) NOT NULL
PRIMARY KEY (id)
Table 2: DISKS (This has a one to many relationship with USERS)
+---------+----------+-----------+-------------
| id | id_user | disk_name | Other fields
+---------+----------+-----------+-------------
| 1 | 1 | disk A |
| 2 | 2 | disk B |
+---------+----------+-----------+-------------
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT
id_user INT(11) NOT NULL,
PRIMARY KEY (id,id_user)
INDEX fk_disks_idx (id ASC)
CONSTRAINT fk_disks
FOREIGN KEY (id)
REFERENCES database.USERS (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
Table 3: FILES (This has a one to many relationship with DISKS)
+---------+----------+----------+-----------+-------------
| id | id_disk | id_user | file_name | Other fields
+---------+----------+----------+-----------+-------------
| 1 | 1 | 1 | |
| 2 | 2 | 2 | |
+---------+----------+----------+-----------+-------------
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT
id_user INT(11) NOT NULL
id_disk INT(11) NOT NULL
PRIMARY KEY (id,id_disk,id_user )
INDEX fk_files_idx (id ASC, id_user ASC)
CONSTRAINT fk_files
FOREIGN KEY (id_disk, id_user, id_user)
REFERENCES database.DISKS (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
Table 2: FILES_ON_NAS (This has a one to one relationship with FILES)
+-------+----------+----------+----------+-----------+-------------
| id | id_files | id_user | id_disk | file_name | Other fields
+-------+----------+----------+----------+-----------+-------------
| 1 | 1 | 1 | 1 | |
| 2 | 1 | 2 | 2 | |
+-------+----------+----------+----------+-----------+-------------
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT
id_files INT(11) NOT NULL,
id_user INT(11) NOT NULL,
id_disk INT(11) NOT NULL,
PRIMARY KEY (id,id_files,id_user,id_disk )
INDEX fk_files_on_nas_idx (id ASC)
CONSTRAINT fk_files_on_nas
FOREIGN KEY (id_files,id_user,id_disk)
REFERENCES database.FILES (id,id_user, id_disk)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
Question:
As you can see the more I reference table in cascade the more primary keys I get. How can I design the database to avoid the replication of primary keys and therefore data duplication as well? Should I delete the auto incremented key for each table? Is it a good practice?
Thanks

The ID of the disk is sufficient to uniquely identify a disk. So there's no reason to include the ID of the user into the disk's primary key. It would even be an extremely bad idea, because that means that if a disk's user changes, you would need to modify the primary key.
Same for a file. A file ID uniquely identifies a file. So there's no reason to add the disk ID into the primary key of a file.

I would strongly advise against deleting the auto-incremented keys.
However you don't need to make a new primary key everytime :
If you want that multiple users share one disk, just put a foreign key of id_disk in USERS
If you want that one user can have multiple disks, then put a foreign key of id_user in DISKS instead.
Only use primary keys like that when you face a Many-to-Many relationship. In this case, you need to create a new table to join both tables; with the primary keys of both tables as foreign keys making a coupled primary key like you did.

You might want to read a little about Database Normalization. In your case, i would make the surrogate key id the only primary key in the tables. Something like:
create table users (
id integer not null auto_increment,
username varchar(50),
...,
primary key (id)
);
create table disks (
id integer not null auto_increment,
user_id integer,
diskname varchar(50),
....,
primary key (id),
foreign key (user_id) references users (id)
);
For files you are going to have to answer the question: does file ownership depend on the file directly, or transitively on the disk ownership, or are the ownerships independent? A file owned by John on a disk owned by Jack? Seems ok to me, but your domain might have different rules. In that case, drop the user_id from the files table (otherwise your database won't be in Third normal form).
create table files (
id integer not null auto_increment,
disk_id integer,
user_id integer, -- you have to decide whether this is necessary
filename varchar(50),
....,
primary key (id),
foreign key (disk_id) references disks (id),
foreign key (user_id) references users (id)
);

Related

Enforcing foreign key constraints based on additional column

I have the following business rules:
An account can have zero or more licenses.
Each license has an unique guid.
Each license is preassigned to a given account.
All installations belong to a given account.
All commercial_installations require a license.
No two commercial_installations can use the same license.
A commercial_installation can be deleted and the license can be then used on a new commercial_installation.
A commercial_installation can only use a license which has been assigned to the installation's account.
How can I enforce the last rule? A commercial_installation can only use a license which has been assigned to the installation's account. Or in other words, for a given guid to be stored in commercial_installations, licenses.accounts_id must equal installations.accounts_id. See sample data at the bottom.
CREATE TABLE IF NOT EXISTS accounts (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(45) NOT NULL,
otherData VARCHAR(45) NULL,
PRIMARY KEY (id))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS licenses (
guid CHAR(36) NOT NULL,
accounts_id INT NOT NULL,
otherData VARCHAR(45) NULL,
PRIMARY KEY (guid),
INDEX fk_licenses_accounts1_idx (accounts_id ASC),
CONSTRAINT fk_licenses_accounts1
FOREIGN KEY (accounts_id)
REFERENCES accounts (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS installations (
id INT NOT NULL AUTO_INCREMENT,
accounts_id INT NOT NULL,
otherData VARCHAR(45) NULL,
PRIMARY KEY (id),
INDEX fk_installations_accounts1_idx (accounts_id ASC),
CONSTRAINT fk_installations_accounts1
FOREIGN KEY (accounts_id)
REFERENCES accounts (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS open_installations (
installations_id INT NOT NULL,
otherData VARCHAR(45) NULL,
PRIMARY KEY (installations_id),
CONSTRAINT fk_open_installations_installations1
FOREIGN KEY (installations_id)
REFERENCES installations (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS commercial_installations (
installations_id INT NOT NULL,
licenses_guid CHAR(36) NOT NULL,
otherData VARCHAR(45) NULL,
PRIMARY KEY (installations_id),
UNIQUE INDEX fk_commercial_installations_licenses1_idx (licenses_guid ASC),
CONSTRAINT fk_commercial_installations_installations1
FOREIGN KEY (installations_id)
REFERENCES installations (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT fk_commercial_installations_licenses1
FOREIGN KEY (licenses_guid)
REFERENCES licenses (guid)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
Given the following sample data:
b5060518-f87e-4acc-82c8-adb5750685a9 and d6f23460-0d77-400e-ae96-13f436e40245 can only exist in commercial_installations where commercial_installations.id is 1 or 2.
Similarly, 8739ef62-7fff-4913-81de-3d00e8f50ecb for 3.
Similarly, 36cc0787-5cb9-4c3a-b79d-1dcfb83d2794 for 4.
accounts
+----+-----------+
| id | name |
+----+-----------+
| 1 | Account 1 |
| 2 | Account 2 |
| 3 | Account 3 |
| 4 | Account 4 |
+----+-----------+
licenses
+--------------------------------------+-------------+
| guid | accounts_id |
+--------------------------------------+-------------+
| b5060518-f87e-4acc-82c8-adb5750685a9 | 1 |
| d6f23460-0d77-400e-ae96-13f436e40245 | 1 |
| 36cc0787-5cb9-4c3a-b79d-1dcfb83d2794 | 2 |
| 8739ef62-7fff-4913-81de-3d00e8f50ecb | 3 |
+--------------------------------------+-------------+
installations
+----+-------------+
| id | accounts_id |
+----+-------------+
| 1 | 1 |
| 2 | 1 |
| 3 | 3 |
| 4 | 2 |
+----+-------------+
Of course you can enforce it in the logic of your application, but I guess you want to do it at the database level.
The only option I see here is to add triggers for INSERT, UPDATE, and DELETE operations. Normal foreign keys will not do this for you.
And I like this question, since I've had this problem before and didn't really take the time of formally asking about it here in SO.
Simply propagate account_ID to commercial_installations. It also takes few more unique constraints, marked as SK (superkey, superset of a key) to be used as targets for FKs in the model.
accounts {account_ID, account_name}
PK {account_ID}
licenses {license_ID, account_ID}
PK {license_ID}
SK {license_ID, account_ID}
FK {account_ID} REFERENCES accounts {account_ID}
installations {installation_ID, account_ID, installation_TYPE}
PK {installation_ID}
SK {installation_ID, account_ID}
FK {account_ID} REFERENCES accounts {account_ID}
open_installations {installation_ID}
PK {installation_ID}
FK {installation_ID} REFERENCES installations {installation_ID}
commercial_installations {installation_ID, account_ID, license_ID}
PK {installation_ID}
AK {license_ID}
FK1 {installation_ID, account_ID}
REFERENCES installations {installation_ID, account_ID}
FK2 {license_ID, account_ID}
REFERENCES licenses {license_ID, account_ID}
Notes:
All attributes (columns) NOT NULL
PK = Primary Key
SK = Superkey (Unique)
AK = Alternate Key (Unique)
FK = Foreign Key

Mysql create table with two foreign keys referencing primary key

I have two mysql tables. The first one is created using the following code:
create table project(
project_id int not null auto_increment,
project_name varchar(30)
primary key(project_id));
The second table:
create table matches(
match_id int not null auto_increment,
match_name varchar(30),
project_id int(4) foreign key (project_id) references projects(project_id));
Both these commands works fine. I want to add the project_name column from the first table to the second table. I tried using
alter table projects drop primary key, add primary key(project_id, project_name);
and then
alter table matches add column project_name varchar(30), add foreign key (project_name) references projects(project_name);
But got the following error:
ERROR 1005 (HY000): Can't create table 'matches.#sql-55e_311' (errno: 150)
How do i include both the columns from the first table into the second table.
The current structure of my second table is as follows:
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| match_id | int(11) | NO | PRI | NULL | auto_increment |
| match_name | varchar(30) | YES | | NULL | |
| project_id | int(4) | NO | MUL | NULL | |
+------------+-------------+------+-----+---------+----------------+
I want to add the project_name as the fourth column in my second table.
To use a compound Primary Key as Foreign Key, you'll have to add the
same number of columns (that compose the PK) with same datatypes to
the child table and then use the combination of these columns in the
FOREIGN KEY definition.
see related post here https://stackoverflow.com/a/10566463/4904726
so try this way
alter table matches add foreign key (project_id, project_name) references projects(project_id, project_name);
Do you understand what a FK constraint says? It says a list of values for some columns in a table must appear as a list of values for some columns forming a (declared) PK or UNIQUE NOT NULL in the referenced table. So what you are writing doesn't make sense. If you wanted a PK (project_id, project_name) then that should also be the form of your FK.
But (project_id, project_name) is not two 1-column PKs, it is one 2-column PK, and it is probably not what you want since presumably in projects it isn't just pairs that are unique it is each column. Presumably you want two 1-column PKs and two one-column FKs, one referencing each PK.
If projects project_id was NOT NULL you could write:
alter table projects add primary key(project_name);
alter table matches add column project_name varchar(30),
add foreign key (project_name) references projects(project_name);
But if project_name can be NULL in projects then you cannot make it a PK and you cannot sensibly have a FK to it. You can make it UNIQUE. Unfortunately MySQL lets you write such a FK declaration to a non-NULL UNIQUE column while it also tells you not to do it:
The handling of foreign key references to non-unique keys or keys that contain NULL values is not well defined for operations such as UPDATE or DELETE CASCADE. You are advised to use foreign keys that reference only keys that are both UNIQUE (or PRIMARY) and NOT NULL.
So if you want projects project_name to be nullable then should declare it UNIQUE but you should enforce the logical matches project_name match with a trigger.

MySQL: is primary key unique by default?

If I define a column as a primary key in MySQL, is it also unique key by default or do I need to also define it as unique key (in case I want it to be unique)?
I saw this question What is the difference b/w Primary Key and Unique Key that explain the difference between the two, but doesn't exactly answer my question.
Does PK is UK by default or I need to explicitly define it?
Primary key is always unique in every SQL. You dont have to explicitly define it as UNIQUE.
On a side note: You can only have onePrimary key in a table and it never allows null values. Also you can have only one primary key constraint in the table(as the point of creating a primary key is to uniquely identify the row in your table) but you can more than one unique key constraint in your table.
Example:
An employee details table having EmpID as Primary key and EmpPhoneNo as unique key.
Primary key is always unique by definition. Not only in MySQL.
So you don't need any additional unique key.
Note that composite keys may lead to confusion : indeed a primary key can be a composite key, and DESCRIBE will show all of the composite key components as primary keys :
> DESCRIBE foobar;
+----------------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+------------------+------+-----+---------+-------+
| column_A | int(10) unsigned | NO | PRI | NULL | |
| column_B | int(10) unsigned | NO | PRI | NULL | |
+----------------------+------------------+------+-----+---------+-------+
However SHOW CREATE TABLE will show the reality :
> SHOW CREATE TABLE foobar;
+--------+---------------------------…+
| Table | Create Table …|
+--------+---------------------------…+
| foobar | CREATE TABLE `foobar` (
`column_A` int(10) unsigned NOT NULL,
`column_B` int(10) unsigned NOT NULL,
PRIMARY KEY (`column_A`,`column_B`),
KEY `column_B` (`column_B`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+--------+---------------------------…+

Mysql errno 150 trying to create table with foreign key references

I'm trying to create a table in mysql with a foreign key reference, like this:
In database A:
CREATE TABLE replication (
id varchar(255) NOT NULL PRIMARY KEY,
uid varchar(255) NOT NULL,
value int(11) NOT NULL,
FOREIGN KEY (uid) REFERENCES databaseB.users(username)
);
In database B i have a table named users like this:
+-----------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+-------+
| id | varchar(255) | NO | | NULL | |
| username | varchar(255) | NO | PRI | NULL | |
+-----------------+--------------+------+-----+---------+-------+
When i try to create table replication, gives me the following error:
ERROR 1005 (HY000): Can't create table 'databaseA.replication' (errno: 150)
Any idea? Thanks in advance!
You need to add an index to uid or you will not be able to set it up to reference anything.
Also, typically in your DatabaseB you would have your users table have a Primary Key on ID and a unique index on username. Then you would set up a foreign key from DatabaseA.replication REFERENCES databaseB.users(id)
you cant make reference key with two different types
in databaseA is integer uid and databaseB is varchar
try this
FOREIGN KEY (uid) REFERENCES databaseB.users(id)
Are both databases of the same type (MyISAM, INNODB)? You cannot point a foreign key to a MyISAM DB.
This error can also occur when you are mixing types for the field type (which is the same in this case, varchar(255)), or when the encodings of the fields are different.
Another reason could be that you don't have access to the other database.
Thetype of the field in a foreign key must be the same as the type of the column they're referencing.I think this problem error because type of the field in a foreign key is different from the column they're referencing.
Your foreign key field is uid and referencing table your field is username.Here is the problem.Try to use same type of field in both table like
FOREIGN KEY (uid) REFERENCES databaseB.users(id)
Hope you understand and works for you.

PHP MySQL Delete parent and child rows

I have 1 MySQL Table. It looks like this:
+---------+-------------+--------+
| item_id | parent_id | Name |
+---------+-------------+--------+
| 1 | 0 | Home |
+---------+-------------+--------+
| 2 | 1 | Sub |
+---------+-------------+--------+
| 3 | 2 | SubSub |
+---------+-------------+--------+
If I DELETE item_id 1, I want to delete the rest of the sub also but how can I do it?
I have tried the Foreign Key but it works only if you have 2 tables??
I hope someone can help me in MySQL maybe PHP?
You can, most definitely, use self-referencing foreign keys with MySQL (you don't need multiple tables). However, for any kind of foreign key support, you need to use the InnoDB engine. And my guess is, that you are using the MyISAM engine.
With InnoDB you could create a table, similar to what you have already, including the self-referencing foreign key, like this:
CREATE TABLE `yourTable` (
`item_id` int(10) unsigned NOT NULL auto_increment,
`parent_id` int(10) unsigned default NULL,
`Name` varchar(50) NOT NULL,
PRIMARY KEY (`item_id`),
KEY `FK_parent_id` (`parent_id`),
CONSTRAINT `FK_parent_id` FOREIGN KEY (`parent_id`) REFERENCES `yourTable` (`item_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Then, when you issue a DELETE statement, like:
DELETE FROM `yourTable` WHERE `item_id` = 1;
... it would delete each 'child' row, that has a parent_id of 1 as well. If any of those 'child' rows have children of their own, they'd be deleted too, etc. (that's what the ON DELETE CASCADE means).
Easier actually than thought:
DELETE FROM table WHERE id = # OR parent_id = #; //where # is the same in both places.
Example:
DELETE FROM table WHERE id = 1 OR parent_id = 1;