MySQL many-to-many schema setup - mysql

I'm setting up a mysql db with posts and tags that looks like this:
posts
+-------------+--------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
[...]
tags
+-------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| tag | varchar(255) | NO | UNI | NULL | |
+-------+--------------+------+-----+---------+----------------+
post_tag_map
+------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------+------+-----+---------+-------+
| post_id | int(11) | NO | PRI | NULL | |
| tag_id | int(11) | NO | PRI | NULL | |
+------------+---------+------+-----+---------+-------+
The tags will be shared between multiple posts; 'red' may be used by post 5 and 10.
My question is: how do I prevent the deletion of a tag if it is being used by more than one post and delete it if it isn't?
Note: I am using Foreign Keys which I thought would take care of this issue but it doesn't seem to be working:
CREATE TABLE `post_tag_map` (
`post_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`post_id`,`tag_id`),
FOREIGN KEY (`post_id`) REFERENCES posts(`id`),
FOREIGN KEY (`tag_id`) REFERENCES tag(`id`)
)

You can delete all tables in one go using a delete statement like this.
DELETE FROM post_tag_map, posts, tags
WHERE post.id = post_tag_map.post_id
AND tags.id = post_tag_map.tag_id
AND tags.id = 256;
However MySQL makes no guarantees about the order that the deletes will take place in. If you have foreign keys, those may prevent the delete from taking place.
So either do not use FOREIGN KEY **or** declare them with aON DELETE CASCADE` clause.
Remember MyISAM does not support foreign keys, so there you only have the multitable delete.
More about multitable deletes here: http://dev.mysql.com/doc/refman/5.1/en/delete.html

You'd want to declare the foreign keys like this:
FOREIGN KEY (post_id) REFERENCES posts(id)
ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tag(id)
ON DELETE CASCADE
the 'on delete cascade' is what will initiate the auto-delete. Note that the cascade will NOT propagate "up" the other side of the link table. If you delete a tag, only the matching records in post_tag_map will vanish, but leave the posts they were attached to alone.

You might need to add to your FOREIGN KEY declarations:
http://dev.mysql.com/doc/refman/5.5/en/innodb-foreign-key-constraints.html

Related

How to drop unique constraint from mysql table on a foreign key column

I want to drop just the UNIQUE Constraint from my MySQL table column and keep the Foreign Key Constraint on the column as-is. work_id is the foreign key. Initially, the column was supposed to be unique (one-to-one relationship) which is now not needed.
I'm using MySQL Ver 15.1 Distrib 5.5.64-MariaDB.
DESCRIBE requests;
+---------------------+---------------------------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+---------------------------------------+------+-----+---------+-------+
| request_id | char(32) | NO | PRI | NULL | |
| owner | varchar(100) | NO | | NULL | |
| status | enum('PENDING','ACCEPTED','REJECTED') | YES | | NULL | |
| work_id | char(32) | NO | UNI | NULL | |
| response_message | varchar(3000) | YES | | NULL | |
| created_date | datetime | NO | | NULL | |
| last_modified_date | datetime | NO | | NULL | |
+---------------------+---------------------------------------+------+-----+---------+-------+
CREATE TABLE `requests` (
`request_id` char(32) NOT NULL,
`owner` varchar(100) NOT NULL,
`status` enum('PENDING','ACCEPTED','REJECTED') DEFAULT NULL,
`work_id` char(32) NOT NULL,
`response_message` varchar(3000) DEFAULT NULL,
`created_date` datetime NOT NULL,
`last_modified_date` datetime NOT NULL,
PRIMARY KEY (`request_id`),
UNIQUE KEY `work_id` (`work_id`),
CONSTRAINT `requests_ibfk_1` FOREIGN KEY (`work_id`) REFERENCES `work` (`work_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
I want to remove UNIQUE Constraint from the work_id. I did some search and executed the following commands.
SHOW INDEX FROM requests;
+-----------------+------------+----------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------------+------------+----------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| requests | 0 | PRIMARY | 1 | request_id | A | 16 | NULL | NULL | | BTREE | | |
| requests | 0 | work_id | 1 | work_id | A | 16 | NULL | NULL | | BTREE | | |
+-----------------+------------+----------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
And then executed
ALTER TABLE requests DROP INDEX work_id;
I get an error
ERROR 1553 (HY000): Cannot drop index 'work_id': needed in a
foreign key constraint
So, your problem is you are trying to drop a index which is used in Foreign Key Constraint. So you can not do it directly. Follow below steps:
Drop the constraint requests_ibfk_1 which is your foreign key.
alter table requests drop foreign key requests_ibfk_1
Then Drop the UNIQUE KEY on column work_id.
alter table requests drop index work_id
Again Add Foreign Key on Column work_id.
alter table requests add CONSTRAINT `requests_ibfk_1` FOREIGN KEY (`work_id`) REFERENCES `work` (`work_id`)
DEMO
The problem is that the definition of the unique constraint drops the index which is normally created for the foreign key. But there is another way, without recreating the foreign key constraint or (temporarily) disabling the checks (which can lead to consistency errors).
First, add another index for the same column (for logical reasons, I would name it exactly as the foreign key):
CREATE INDEX requests_ibfk_1 ON requests(work_id);
Now you can safely drop the unique constraint/index (since there is still an index available for the foreign key constraint):
DROP INDEX work_id ON requests;
I hope this solves the problem.
As described in #Rick James's comment , the alternative is to temporarily disable key constraints of a table, immediately drop the unique index, and then enable the keys of the table afterwards. Here is an example for mysql / mariadb :
ALTER TABLE `<YOUR_TABLE_NAME>` DISABLE KEYS;
ALTER TABLE `<YOUR_TABLE_NAME>` DROP INDEX `<YOUR_UNIQUE_INDEX_NAME>`;
ALTER TABLE `<YOUR_TABLE_NAME>` ENABLE KEYS;

Adding Foreign Key to Foreign Key Constrained Table

I have this tables:
quotes
+---------------------+-----------------------+------+-----+---------+---+
| Field | Type | Null | Key | Default
+---------------------+-----------------------+------+-----+---------+---+
| id | int(11) unsigned | NO | PRI | NULL
... (More Columns)
+---------------------+-----------------------+------+-----+-------------+
quotesPackagesInfo
+---------------+---------------------+------+-----+---------+------------+
| Field | Type | Null | Key | Default
+---------------+---------------------+------+-----+---------+------------+
| id | int(11) unsigned | NO | PRI | NULL
| quoteId | int(11) unsigned | NO | | NULL
... (More Columns)
+---------------+---------------------+------+-----+---------+------------+
quotesFlightsInfo
+---------------------------+---------------------+------+-----+----------+
| Field | Type | Null | Key | Default
+---------------------------+---------------------+------+-----+----------+
| id | int(11) unsigned | NO | PRI | NULL
| quoteId | int(11) unsigned | NO | MUL | NULL
| packageId | int(11) unsigned | YES | MUL | NULL ... (More Columns)
+---------------------------+---------------------+------+-----+----------+
So basically there're quotes which are the main key, after there're packages within a quote, and a package can contain flights, so i need that when a package is removed, all flights related get deleted, so i added a foreign key to flights as follows:
ALTER TABLE quotesFlightsInfo
ADD CONSTRAINT fk_quotesFlightsInfo_packageId
FOREIGN KEY (packageId) REFERENCES quotesPackagesInfo(id)
ON DELETE CASCADE
now im trying to add a foreign key to packages so when a quote gets deleted also the package deletes and so the flights but isnt working :(, here the query:
ALTER TABLE quotesPackagesInfo
ADD CONSTRAINT fk_quotesPackagesInfo_quoteId
FOREIGN KEY (quoteId) REFERENCES quotes(id)
ON DELETE CASCADE
Error: Cannot add or update a child row: a foreign key constraint fails (??????????.#sql-312_2, CONSTRAINT fk_quotesPackagesInfo_quoteId FOREIGN KEY (quoteId) REFERENCES quotes (id) ON DELETE CASCADE)
i deleted the quotesPackagesInfo table and created it again adding the foreign key from start and it worked, but still dont know whats was wrong before

How to set a foreign key to point to an autoincrement int?

I am trying to set up a MySQL database where a column is a foreign key pointing to another table that auto increments. When I try to create the table with the foreign key, I get the following error:
Can't create table '{DATABASE}' . '{TABLE}' (errorno: 150 "Foreign key constraint is incorrectly formed")
An answer found here says that the problem could be that I am trying to set a normal int as a foreign key pointing to an autoincrement int. How should I go about setting up my table structure?
Here is the statement that produces the error:
create table test_scores(id INT NOT NULL AUTO_INCREMENT,
student_id INT,
FOREIGN KEY (id) REFERENCES students(student_id) ON DELETE CASCADE,
test_id INT NOT NULL,
FOREIGN KEY (test_id) REFERENCES tests(id) ON DELETE CASCADE,
score INT NOT NULL,
PRIMARY KEY (id));
Parent tables:
students
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(50) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
tests
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(50) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
On this line:
FOREIGN KEY (id) REFERENCES students(student_id) ON DELETE CASCADE,
you probably meant to do this:
FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
Otherwise you're trying to make id the foreign key, which isn't going to work because you can't know what id is even going to be until you insert a record. Additionally, students has no student_id column, so the attempt to use it as the relationship for the foreign key would fail.

Create table of unique foreign keys

I am using MySQL and I have two tables of the form :
mysql> describe ing_categories;
+-------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+----------------+
| ID_Category | int(10) unsigned | NO | PRI | NULL | auto_increment |
| category | varchar(64) | NO | UNI | NULL | |
+-------------+------------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
mysql> describe ing_titles;
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| ID_Title | int(10) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(128) | NO | UNI | NULL | |
+----------+------------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
I have a third table containing foreign key records, the foreign keys are to the above tables:
mysql> describe ing_title_categories;
+-------------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------------+------+-----+---------+----------------+
| ID_Title_Category | int(10) unsigned | NO | PRI | NULL | auto_increment |
| ID_Title | int(10) unsigned | NO | MUL | NULL | |
| ID_Category | int(10) unsigned | NO | MUL | NULL | |
+-------------------+------------------+------+-----+---------+----------------+
How do I create the ing_titles_categories table so that the pair of foreign keys is unique?
Here is my SQL statement:
CREATE TABLE ing_title_categories
(
ID_Title_Category INTEGER UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
ID_Title INTEGER UNSIGNED NOT NULL,
ID_Category INTEGER UNSIGNED NOT NULL,
FOREIGN KEY fk_title(ID_Title)
REFERENCES ing_titles(ID_Title)
ON UPDATE CASCADE
ON DELETE RESTRICT,
FOREIGN KEY fk_category(ID_Category)
REFERENCES ing_categories(ID_Category)
ON UPDATE CASCADE
ON DELETE RESTRICT
) ENGINE=InnoDB;
I've searched other questions and they involve making an index for the foreign keys; but I don't want to index by the foreign keys, I want the constraint to be unique for the pair of foreign keys.
Tools:
MySQL Server version: 5.6.26
Notes:
I am not using PHP, but C++ via MySQL C++ Connector, so please no PHP
examples.
You add a unique or primary key constraint. Here is an example:
CREATE TABLE ing_title_categories
(
ID_Title_Category INTEGER UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
ID_Title INTEGER UNSIGNED NOT NULL,
ID_Category INTEGER UNSIGNED NOT NULL,
FOREIGN KEY fk_title(ID_Title)
REFERENCES ing_titles(ID_Title)
ON UPDATE CASCADE
ON DELETE RESTRICT,
FOREIGN KEY fk_category(ID_Category)
REFERENCES ing_categories(ID_Category)
ON UPDATE CASCADE
ON DELETE RESTRICT,
UNIQUE (ID_Title, ID_Category)
) ENGINE=InnoDB;
The implementation of the unique constraint does create an index. However, that index is needed to enforce the constraint. It will also be used for queries, where appropriate.
Note that this is equivalent to:
create unique index idx_ing_title_categories_2 on ing_title_categories(ID_Title, ID_Category);
One advantage of putting the constraint in the CREATE TABLE statement is that you can use the CONSTRAINT keyword to give the constraint a meaningful name -- useful when you want to figure out what is happening when the constraint is violated.
You can define UNIQUE KEY constraint on both columns ID_Title and ID_Category as composite unique (UNIQUE(ID_Title , ID_Category)) key which will make sure that combination of the said key columns are always unique.
ALTER TABLE ing_title_categories
ADD CONSTRAINT uniqueconstraint UNIQUE(`ID_Title` , `ID_Category`);

MYSQL can't add a foreign key

I really can't get a clue, as everything seems to be ok. This is the command I'm trying:
ALTER TABLE dashboard ADD CONSTRAINT FOREIGN KEY (app_name, app_parent, client)
REFERENCES apps(app_name, app_parent, client) ON DELETE CASCADE ON UPDATE CASCADE;
This is the error I get:
ERROR 1215 (HY000): Cannot add foreign key constraint
Using an explicit name for the FK doesn't help. This is the referenced table:
mysql> SHOW COLUMNS IN apps;
+------------+------------------+------+-----+------------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+------------+-------+
| app_name | varchar(20) | NO | PRI | | |
| app_parent | varchar(20) | NO | PRI | | |
| client | varchar(12) | NO | PRI | NULL | |
| order_idx | int(10) unsigned | YES | | 100 | |
...
And this is the referencing table:
mysql> SHOW COLUMNS IN dashboard;
+------------+----------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+----------------------+------+-----+---------+-------+
| app_name | varchar(20) | NO | | | |
| app_parent | varchar(20) | NO | | | |
| client | varchar(12) | NO | | NULL | |
| widget | varchar(20) | YES | | NULL | |
...
Both tables have InnoDB as the engine. And this is the (rather laconic) clue I'm getting from SHOW ENGINE InnoDB STATUS:
LATEST FOREIGN KEY ERROR
------------------------
2014-06-20 11:49:57 10ec Error in foreign key constraint of table the_db/#sql-128c_2:
FOREIGN KEY (app_name, app_parent, client)
REFERENCES apps(app_name, app_parent, client) ON DELETE CASCADE ON UPDATE CASCADE:
Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types
in the table and the referenced table do not match for constraint.
Note that the internal storage type of ENUM and SET changed in
tables created with >= InnoDB-4.1.12, and such columns in old tables
cannot be referenced by such columns in new tables.
See http://dev.mysql.com/doc/refman/5.6/en/innodb-foreign-key-constraints.html
for correct foreign key definition.
What does it mean it doesn't find an index on apps? I'm referencing primary keys! And the types are clearly the same.
EDIT: this is the CREATE command for apps:
CREATE TABLE `apps` (
`app_name` varchar(20) NOT NULL DEFAULT '',
`app_parent` varchar(20) NOT NULL DEFAULT '',
`client` varchar(12) NOT NULL,
`order_idx` int(10) unsigned DEFAULT '100',
...,
PRIMARY KEY (`app_name`,`app_parent`,`client`),
KEY `client` (`client`),
CONSTRAINT `apps_ibfk_1` FOREIGN KEY (`client`) REFERENCES `clients` (`name`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `apps_ibfk_2` FOREIGN KEY (`app_name`, `app_parent`) REFERENCES `app_list` (`name`, `parent`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1
I must add that I actually managed to create a foreign key on the same columns on another table:
mysql> SHOW COLUMNS IN user_apps;
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| app_name | varchar(20) | NO | PRI | | |
| app_parent | varchar(20) | NO | PRI | | |
| username | varchar(20) | NO | PRI | | |
| client | varchar(12) | YES | | NULL | |
...
Here, app_name, app_parent and client reference to the same named columns in apps.
This is probably the sneakiest of the mistakes: the problem was the default character set, which is latin1 for apps and utf8 for dashboard.
This solved my problem:
ALTER TABLE dashboard CONVERT TO CHARACTER SET latin1;
You better have posted output of
show create table apps
I am not sure if the referenced parent columns are indexed.
But for them to be eligible for referring from other child tables, they MUST have INDEXed
As per documentation on foreign key constraints:
REFERENCES tbl_name (index_col_name,...)
Define an INDEXes on columns app_name, app_parent, client of apps table.
If not already defined, then use following command:
ALTER TABLE apps
ADD INDEX ix_app_name( app_name )
, ADD INDEX ix_app_parent( app_parent )
, ADD INDEX ix_app_client( client )
And other primary constraints are that,
The child column definition MUST match with that of parent column.
Database ENGINE type must be same.
Refer to:
MySQL ALTER TABLE Syntax
MySQL Using FOREIGN KEY Constraints
[CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (index_col_name, ...)
REFERENCES tbl_name (index_col_name,...)
[ON DELETE reference_option]
[ON UPDATE reference_option]
reference_option:
RESTRICT | CASCADE | SET NULL | NO ACTION