I have three tables in MySQL v5.6 created by:
CREATE TABLE tm (
id INT AUTO_INCREMENT,
PRIMARY KEY (id)
);
CREATE TABLE th (
id INT AUTO_INCREMENT,
PRIMARY KEY (id)
);
CREATE TABLE tr (
id INT AUTO_INCREMENT,
tm_id INT,
th_id INT,
PRIMARY KEY (id),
CONSTRAINT fk_1 FOREIGN KEY (tm_id) REFERENCES tm (id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT fk_2 FOREIGN KEY (th_id) REFERENCES th (id) ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO tm (id) VALUES (101);
INSERT INTO tm (id) VALUES (102);
INSERT INTO th (id) VALUES (1);
INSERT INTO tr (tm_id, th_id) VALUES (101,1), (102,1);
So in the end I have three tables with following data:
th
| id |
|----|
| 1 |
tr
| id | tm_id | th_id |
|-----|---------|-------|
| 11 | 101 | 1 |
| 12 | 102 | 1 |
tm
| id |
|------|
| 101 |
| 102 |
And what I am trying to do is to delete all of them with one SQL query:
DELETE th, tr, tm
FROM th
LEFT JOIN tr ON tr.th_id = th.id
LEFT JOIN tm ON tr.tm_id = tm.id
WHERE th.id = 1;
As a result th and tr tables will be blank but tm will be left with id=102
+------+
| id |
+------+
| 102 |
+------+
And I am not searching for better query but trying to understand why tm.id=101 was deleted but tm.id=102 was left in the table?
This is the result of the InnoDB storage engine which is used (as default) to create the tables and the foreign key constraints.
Check the demo without foreign keys and the result is that all rows of all tables are deleted.
You would get the same result, even with the foreign key constraints if you created the tables with the MyISAM storage engine.
Check the demo.
But for the InnoDB storage engine and the foreign key constraints, as it is mentioned in 13.2.2 DELETE Statement:
If you use a multiple-table DELETE statement involving InnoDB tables
for which there are foreign key constraints, the MySQL optimizer might
process tables in an order that differs from that of their
parent/child relationship. In this case, the statement fails and rolls
back. Instead, you should delete from a single table and rely on the
ON DELETE capabilities that InnoDB provides to cause the other tables
to be modified accordingly.
so what you get may be the result of a rollback for the deletion of id = 102 of tm, because the order of the tables processed is such that it would violate a foreign key constraint.
Check the demo.
Related
I want to use foreign keys to keep the integrity and avoid orphans (I already use innoDB).
How do I make a SQL statment that DELETE ON CASCADE?
If I delete a category then how do I make sure that it would not delete products that also are related to other categories.
The pivot table "categories_products" creates a many-to-many relationship between the two other tables.
categories
- id (INT)
- name (VARCHAR 255)
products
- id
- name
- price
categories_products
- categories_id
- products_id
If your cascading deletes nuke a product because it was a member of a category that was killed, then you've set up your foreign keys improperly. Given your example tables, you should have the following table setup:
CREATE TABLE categories (
id int unsigned not null primary key,
name VARCHAR(255) default null
)Engine=InnoDB;
CREATE TABLE products (
id int unsigned not null primary key,
name VARCHAR(255) default null
)Engine=InnoDB;
CREATE TABLE categories_products (
category_id int unsigned not null,
product_id int unsigned not null,
PRIMARY KEY (category_id, product_id),
KEY pkey (product_id),
FOREIGN KEY (category_id) REFERENCES categories (id)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (product_id) REFERENCES products (id)
ON DELETE CASCADE
ON UPDATE CASCADE
)Engine=InnoDB;
This way, you can delete a product OR a category, and only the associated records in categories_products will die alongside. The cascade won't travel farther up the tree and delete the parent product/category table.
e.g.
products: boots, mittens, hats, coats
categories: red, green, blue, white, black
prod/cats: red boots, green mittens, red coats, black hats
If you delete the 'red' category, then only the 'red' entry in the categories table dies, as well as the two entries prod/cats: 'red boots' and 'red coats'.
The delete will not cascade any farther and will not take out the 'boots' and 'coats' categories.
comment followup:
you're still misunderstanding how cascaded deletes work. They only affect the tables in which the "on delete cascade" is defined. In this case, the cascade is set in the "categories_products" table. If you delete the 'red' category, the only records that will cascade delete in categories_products are those where category_id = red. It won't touch any records where 'category_id = blue', and it would not travel onwards to the "products" table, because there's no foreign key defined in that table.
Here's a more concrete example:
categories: products:
+----+------+ +----+---------+
| id | name | | id | name |
+----+------+ +----+---------+
| 1 | red | | 1 | mittens |
| 2 | blue | | 2 | boots |
+---++------+ +----+---------+
products_categories:
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1 | 1 | // red mittens
| 1 | 2 | // blue mittens
| 2 | 1 | // red boots
| 2 | 2 | // blue boots
+------------+-------------+
Let's say you delete category #2 (blue):
DELETE FROM categories WHERE (id = 2);
the DBMS will look at all the tables which have a foreign key pointing at the 'categories' table, and delete the records where the matching id is 2. Since we only defined the foreign key relationship in products_categories, you end up with this table once the delete completes:
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1 | 1 | // red mittens
| 2 | 1 | // red boots
+------------+-------------+
There's no foreign key defined in the products table, so the cascade will not work there, so you've still got boots and mittens listed. There's just no 'blue boots' and no 'blue mittens' anymore.
I got confused by the answer to this question, so I created a test case in MySQL, hope this helps
-- Schema
CREATE TABLE T1 (
`ID` int not null auto_increment,
`Label` varchar(50),
primary key (`ID`)
);
CREATE TABLE T2 (
`ID` int not null auto_increment,
`Label` varchar(50),
primary key (`ID`)
);
CREATE TABLE TT (
`IDT1` int not null,
`IDT2` int not null,
primary key (`IDT1`,`IDT2`)
);
ALTER TABLE `TT`
ADD CONSTRAINT `fk_tt_t1` FOREIGN KEY (`IDT1`) REFERENCES `T1`(`ID`) ON DELETE CASCADE,
ADD CONSTRAINT `fk_tt_t2` FOREIGN KEY (`IDT2`) REFERENCES `T2`(`ID`) ON DELETE CASCADE;
-- Data
INSERT INTO `T1` (`Label`) VALUES ('T1V1'),('T1V2'),('T1V3'),('T1V4');
INSERT INTO `T2` (`Label`) VALUES ('T2V1'),('T2V2'),('T2V3'),('T2V4');
INSERT INTO `TT` (`IDT1`,`IDT2`) VALUES
(1,1),(1,2),(1,3),(1,4),
(2,1),(2,2),(2,3),(2,4),
(3,1),(3,2),(3,3),(3,4),
(4,1),(4,2),(4,3),(4,4);
-- Delete
DELETE FROM `T2` WHERE `ID`=4; -- Delete one field, all the associated fields on tt, will be deleted, no change in T1
TRUNCATE `T2`; -- Can't truncate a table with a referenced field
DELETE FROM `T2`; -- This will do the job, delete all fields from T2, and all associations from TT, no change in T1
I think (I'm not certain) that foreign key constraints won't do precisely what you want given your table design. Perhaps the best thing to do is to define a stored procedure that will delete a category the way you want, and then call that procedure whenever you want to delete a category.
CREATE PROCEDURE `DeleteCategory` (IN category_ID INT)
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
BEGIN
DELETE FROM
`products`
WHERE
`id` IN (
SELECT `products_id`
FROM `categories_products`
WHERE `categories_id` = category_ID
)
;
DELETE FROM `categories`
WHERE `id` = category_ID;
END
You also need to add the following foreign key constraints to the linking table:
ALTER TABLE `categories_products` ADD
CONSTRAINT `Constr_categoriesproducts_categories_fk`
FOREIGN KEY `categories_fk` (`categories_id`) REFERENCES `categories` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `Constr_categoriesproducts_products_fk`
FOREIGN KEY `products_fk` (`products_id`) REFERENCES `products` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE
The CONSTRAINT clause can, of course, also appear in the CREATE TABLE statement.
Having created these schema objects, you can delete a category and get the behaviour you want by issuing CALL DeleteCategory(category_ID) (where category_ID is the category to be deleted), and it will behave how you want. But don't issue a normal DELETE FROM query, unless you want more standard behaviour (i.e. delete from the linking table only, and leave the products table alone).
I have two tables (A, B) with a foreign key from B to A.
So A is my parent table and B my child.
I now insert a row in B before the parent row exists in A. So I set the foreign key to an id I know parent A will have, but which is not existing right know. To achieve that I use the option 'SET foreign_key_checks = 0', which allows to set a foreign key in the child B without the existens of the key in the parent A.
My question is, what will happen, if I add the row in A with the missing primary key. Will the foreign key <-> primary key connection work and will it be as fast as normal? Or do I have to drop the fk key and rebuild it?
I use InnoDB and MySQL 5.5.
... and I know that is probably very bad practice...
Or short:
I have a parent and a child table, linked by a foreign key. I add the child first, what happens if I add the parent later?
My question is, what will happen, if I add the row in A with the
missing primary key. Will the foreign key <-> primary key connection
work and will it be as fast as normal? Or do I have to drop the fk key
and rebuild it?
If you will add missing record into parent table, the FK constraint will work as it should be. You will actually solve the data inconsistency.
There is no need to recreate FK.
I tried it myself by creating an example.
CREATE TABLE `parent` (
`idparent` int(11) NOT NULL,
PRIMARY KEY (`idparent`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `parent` (
`idparent` int(11) NOT NULL,
PRIMARY KEY (`idparent`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
SET foreign_key_checks = 0;
INSERT INTO child (idchild, parentid) VALUES (1,1),(2,2),(3,3),(4,4),(5,5);
SET foreign_key_checks = 1;
INSERT INTO parent (idparent) VALUES (1),(2),(3),(4),(5);
Next, I used explain to get an idea, if the index is used:
EXPLAIN SELECT * from parent p
join child c on c.parentid = p.idparent;
+----+-------------+-------+-------+-----------------+-----------------+---------+----------------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+-----------------+-----------------+---------+----------------------+------+--------------------------+
| 1 | SIMPLE | p | index | PRIMARY | PRIMARY | 4 | NULL | 5 | Using index |
| 1 | SIMPLE | c | ref | fk_parentid_idx | fk_parentid_idx | 5 | remove_me.p.idparent | 1 | Using where; Using index |
+----+-------------+-------+-------+-----------------+-----------------+---------+----------------------+------+--------------------------+
So it looks like it uses the index, altough at first the foreign key was not set. Therefore, it should be at least speedwise no problem.
INSERT INTO `area` (
`area_id` PRIMARY KEY,
`area_name`,
`chemist_id`FOREIGN KEY
)
VALUES (
[value-1],
[value-2],
[value-3]
)
I want to use foreign keys to keep the integrity and avoid orphans (I already use innoDB).
How do I make a SQL statment that DELETE ON CASCADE?
If I delete a category then how do I make sure that it would not delete products that also are related to other categories.
The pivot table "categories_products" creates a many-to-many relationship between the two other tables.
categories
- id (INT)
- name (VARCHAR 255)
products
- id
- name
- price
categories_products
- categories_id
- products_id
If your cascading deletes nuke a product because it was a member of a category that was killed, then you've set up your foreign keys improperly. Given your example tables, you should have the following table setup:
CREATE TABLE categories (
id int unsigned not null primary key,
name VARCHAR(255) default null
)Engine=InnoDB;
CREATE TABLE products (
id int unsigned not null primary key,
name VARCHAR(255) default null
)Engine=InnoDB;
CREATE TABLE categories_products (
category_id int unsigned not null,
product_id int unsigned not null,
PRIMARY KEY (category_id, product_id),
KEY pkey (product_id),
FOREIGN KEY (category_id) REFERENCES categories (id)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (product_id) REFERENCES products (id)
ON DELETE CASCADE
ON UPDATE CASCADE
)Engine=InnoDB;
This way, you can delete a product OR a category, and only the associated records in categories_products will die alongside. The cascade won't travel farther up the tree and delete the parent product/category table.
e.g.
products: boots, mittens, hats, coats
categories: red, green, blue, white, black
prod/cats: red boots, green mittens, red coats, black hats
If you delete the 'red' category, then only the 'red' entry in the categories table dies, as well as the two entries prod/cats: 'red boots' and 'red coats'.
The delete will not cascade any farther and will not take out the 'boots' and 'coats' categories.
comment followup:
you're still misunderstanding how cascaded deletes work. They only affect the tables in which the "on delete cascade" is defined. In this case, the cascade is set in the "categories_products" table. If you delete the 'red' category, the only records that will cascade delete in categories_products are those where category_id = red. It won't touch any records where 'category_id = blue', and it would not travel onwards to the "products" table, because there's no foreign key defined in that table.
Here's a more concrete example:
categories: products:
+----+------+ +----+---------+
| id | name | | id | name |
+----+------+ +----+---------+
| 1 | red | | 1 | mittens |
| 2 | blue | | 2 | boots |
+---++------+ +----+---------+
products_categories:
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1 | 1 | // red mittens
| 1 | 2 | // blue mittens
| 2 | 1 | // red boots
| 2 | 2 | // blue boots
+------------+-------------+
Let's say you delete category #2 (blue):
DELETE FROM categories WHERE (id = 2);
the DBMS will look at all the tables which have a foreign key pointing at the 'categories' table, and delete the records where the matching id is 2. Since we only defined the foreign key relationship in products_categories, you end up with this table once the delete completes:
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1 | 1 | // red mittens
| 2 | 1 | // red boots
+------------+-------------+
There's no foreign key defined in the products table, so the cascade will not work there, so you've still got boots and mittens listed. There's just no 'blue boots' and no 'blue mittens' anymore.
I got confused by the answer to this question, so I created a test case in MySQL, hope this helps
-- Schema
CREATE TABLE T1 (
`ID` int not null auto_increment,
`Label` varchar(50),
primary key (`ID`)
);
CREATE TABLE T2 (
`ID` int not null auto_increment,
`Label` varchar(50),
primary key (`ID`)
);
CREATE TABLE TT (
`IDT1` int not null,
`IDT2` int not null,
primary key (`IDT1`,`IDT2`)
);
ALTER TABLE `TT`
ADD CONSTRAINT `fk_tt_t1` FOREIGN KEY (`IDT1`) REFERENCES `T1`(`ID`) ON DELETE CASCADE,
ADD CONSTRAINT `fk_tt_t2` FOREIGN KEY (`IDT2`) REFERENCES `T2`(`ID`) ON DELETE CASCADE;
-- Data
INSERT INTO `T1` (`Label`) VALUES ('T1V1'),('T1V2'),('T1V3'),('T1V4');
INSERT INTO `T2` (`Label`) VALUES ('T2V1'),('T2V2'),('T2V3'),('T2V4');
INSERT INTO `TT` (`IDT1`,`IDT2`) VALUES
(1,1),(1,2),(1,3),(1,4),
(2,1),(2,2),(2,3),(2,4),
(3,1),(3,2),(3,3),(3,4),
(4,1),(4,2),(4,3),(4,4);
-- Delete
DELETE FROM `T2` WHERE `ID`=4; -- Delete one field, all the associated fields on tt, will be deleted, no change in T1
TRUNCATE `T2`; -- Can't truncate a table with a referenced field
DELETE FROM `T2`; -- This will do the job, delete all fields from T2, and all associations from TT, no change in T1
I think (I'm not certain) that foreign key constraints won't do precisely what you want given your table design. Perhaps the best thing to do is to define a stored procedure that will delete a category the way you want, and then call that procedure whenever you want to delete a category.
CREATE PROCEDURE `DeleteCategory` (IN category_ID INT)
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
BEGIN
DELETE FROM
`products`
WHERE
`id` IN (
SELECT `products_id`
FROM `categories_products`
WHERE `categories_id` = category_ID
)
;
DELETE FROM `categories`
WHERE `id` = category_ID;
END
You also need to add the following foreign key constraints to the linking table:
ALTER TABLE `categories_products` ADD
CONSTRAINT `Constr_categoriesproducts_categories_fk`
FOREIGN KEY `categories_fk` (`categories_id`) REFERENCES `categories` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `Constr_categoriesproducts_products_fk`
FOREIGN KEY `products_fk` (`products_id`) REFERENCES `products` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE
The CONSTRAINT clause can, of course, also appear in the CREATE TABLE statement.
Having created these schema objects, you can delete a category and get the behaviour you want by issuing CALL DeleteCategory(category_ID) (where category_ID is the category to be deleted), and it will behave how you want. But don't issue a normal DELETE FROM query, unless you want more standard behaviour (i.e. delete from the linking table only, and leave the products table alone).
I want to create a references to foreign table. but i'm getting the following error:
query:
CREATE TABLE category_ids (id INT, post_id INT,
INDEX par_ind (post_id),
FOREIGN KEY (post_id) REFERENCES post(id)
ON DELETE CASCADE
) ENGINE=INNODB;
SHOW ENGINE INNODB STATUS\G:
------------------------
LATEST FOREIGN KEY ERROR
------------------------
2013-08-23 00:11:06 7f6f49e7b700 Error in foreign key constraint of table fun/category_ids:
FOREIGN KEY (post_id) REFERENCES post(id)
ON DELETE CASCADE
) ENGINE=INNODB:
Cannot resolve table name close to:
(id)
ON DELETE CASCADE
) ENGINE=INNODB
posts table structure
mysql> describe post;
+-------------+-----------------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-----------------------+------+-----+---------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
...
+-------------+-----------------------+------+-----+---------------------+----------------+
22 rows in set (0.00 sec)
Only InnoDB supports Foreign keys, MyISAM doesn't.
Even if it would, you cannot create relations between tables of different type.
Therefore you need to convert the table post into InnoDB. ALTER TABLE post ENGINE = InnoDB;
This error can also come when parent table is partitioned. Removing partitioning from the parent table allows foreign constraint to be created without any problem.
For me works with this.
CREATE TABLE category_ids (id INT, post_id INT references post(id),
INDEX par_ind (post_id)
) ENGINE=INNODB;
CREATE TABLE parent (id INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
CREATE TABLE child (id INT, parent_id INT,
INDEX par_ind (parent_id),
FOREIGN KEY (parent_id) REFERENCES parent(id)
ON DELETE CASCADE
) ENGINE=INNODB;
I dont understand the meaning of putting ENGINE = INNODB here, and why do we use ON DELETE CASCADE?
engine=innodb will ensure you get foreign key support. The default MyISAM engine doesn't support foreign keys. On delete cascade will remove the child row if the referenced row in the parent table is removed.
MySQL is the DB engine. It can use multiple storage engines. MyISAM is the default storage engine for MySQL and it does not support foreign keys. InnoDB is another storage engine that does support foreign keys. You must specify ENGINE=InnoDB because MySQL will use MyISAM by default.
ON DELETE CASCADE will delete all rows in a table that have a foreign key that references a key that is deleted. I think it is dangerous and defeats a lot of the purpose of foreign key restriction, so I would avoid using it, but this is just my personal opinion.
Say you have:
+-------+-------+
| ordID | proID |
+-------+-------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 2 |
+-------+-------+
And on OrdersItems it has FOREIGN KEY (proID) REFERENCES Products (proID) ON DELETE CASCADE.
Then if someone runs
DELETE FROM Products WHERE proID = 2
Then rows with ordID 4 and 5 will also be deleted (it cascades).