MySQL Foreign Key "ON DELETE CASCADE" across 3 tables - mysql

I have 3 tables in my DB (there are more, but there are no connections to these yet)
table "molecule" with column id
table "descriptor" with columns "id" and "molecule_id" and a foreign key referencing "molecule.id"
table "tDepDescriptor" with columns "id" and "descriptor_id" and a foreign key referencing "descriptor.id "
(each table has more columns, but none of these act as foreign keys or anything like that)
All foreign keys have "on delete cascade" specified, all ids are unsigned int(5).
Now, if I try to delete an entry in "molecule" for which there are referencing entries in "descriptor" and "tDepDescriptor" nothing happens as if the foreign keys were set to "on update restrict", no error is given.
If I delete an entry in "descriptor", all referencing entries in "tDepDescriptor" are deleted like they should.
The same happens if I try to delete an entry in "molecule" for which there are referencing entries in "descriptor", but no referencing entries to those "descriptor"-entries in "tDepDescriptor".
So "on delete cascade" works for two tables, but the "cascade" does not seem to be passed on when three tables are involved.
What the tables are supposed to do is:
When I want to delete an entry in "molecule", all referencing entries in "descriptor" are deleted. And therefore all entries in "tDepDescriptor" that have a reference to one of the deleted entries in "descriptor" are also deleted.
mysql server version is 5.1, engine is InnoDB
Is hope someone could follow this complicated explanation and can help me.
//EDIT:
Found the problem.
Seems to be a problem with phpMyAdmin, not with the database. clicking on delete in PMA did not work, but coding the query by hand did, cascading through all three tables. Strange, but at least I know my tables work correctly.

It is enough to have ON DELETE CASCADE option. Have a look at this example:
Create and fill tables:
CREATE TABLE molecule (
id INT(11) NOT NULL,
PRIMARY KEY (id)
)
ENGINE = INNODB;
CREATE TABLE descriptor (
id INT(11) NOT NULL,
molecule_id INT(11) DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT FK_descriptor_molecule_id FOREIGN KEY (molecule_id)
REFERENCES molecule(id) ON DELETE CASCADE ON UPDATE RESTRICT
)
ENGINE = INNODB;
CREATE TABLE tdepdescriptor (
id INT(11) NOT NULL,
descriptor_id INT(11) DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT FK_tdepdescriptor_descriptor_id FOREIGN KEY (descriptor_id)
REFERENCES descriptor(id) ON DELETE CASCADE ON UPDATE RESTRICT
)
ENGINE = INNODB;
INSERT INTO molecule VALUES
(1),
(2),
(3);
INSERT INTO descriptor VALUES
(1, 1),
(2, 1),
(3, 2);
INSERT INTO tdepdescriptor VALUES
(1, 1),
(2, 2),
(3, 3);
Delete one molecule and all its descriptor and all its tdepdescriptor:
DELETE FROM molecule WHERE id = 1;
SELECT * FROM molecule;
+----+
| id |
+----+
| 2 |
| 3 |
+----+
SELECT * FROM descriptor;
+----+-------------+
| id | molecule_id |
+----+-------------+
| 3 | 2 |
+----+-------------+
SELECT * FROM tdepdescriptor;
+----+---------------+
| id | descriptor_id |
+----+---------------+
| 3 | 3 |
+----+---------------+

Please make sure that you are using the Engine InnoDB in all the tables chained to wach other in foreign keys with on update/delete cascade, you may use:
create table test1 (
#column definitions, including fk with on delete/update cascade
) engine = "InnoDB";
My exprience:
I had the same problem on one of my projects. An already developed database was given to me for adding some more parts. The tables did not have on delete/update cascade on foreign keys. I added them, also I added aother table from "query" tab in phpMyAdmin of cPanel. so I had a relation as:
(priorities) 1-m (packages) 1-m (standards)
All with on delete/update cascade. But when I used to delete "priority" , "standard" was not deleted.
"priorities" and "packages" existed with "InnoDB" engine and when I created "standards" table, I did not specify the ENGINE, so it used "MyISAM" by default, that caused the problem. When I recreated the table with the ENGINE="InnoDB" explicitly mentioned, the problem was solved.

Related

How to handle association table and cascade constraints in MYSQL

I think this is a common problem but I was not able to find correct keywords to perform a search on StackOverflow.
In mysql, I have two entites : organization and scale.
One organization can only have one scale but a scale can belong to many organizations.
So I created a third entity organization_scale.
for instance :
organization
------------
org_id | name
1 | Mickey
2 | Donald
3 | Dingo
scale
----------
sc_id | name
1 | miniScale
2 | maxiScale
organization_scale
--------------
org_id | sc_id
1 | 1
2 | 1
3 | 2
Both fields of the organization_scale entity are foreign keys + cascade on update and delete
The problem is the following:
If I delete from organization where org_id = 3,the third line from organization_scale are properly deleted (since org_id = 3 does not exist anymore)
BUT
the scale with sc_id = 2 is not deleted. And I do not understand why. No one references to this scale, and this should be removed otherwise, with a big amount of data, a lot of "scales" will be orphean.
(same thing happens if I do not use an extra table, and I add a scale_id column directly in organization)
I think the problem is this: The ON DELTE CASCADE in the definition of the organization_scale table only works in one direction: when deleting something in scale or organization then don't ask and delete the rows in organization_scale.
But you seem to want the other direction: When deleting something in organization_scale delete the row in scale if there is no reference to it anymore.
This works for me: I added a trigger on organization.
DROP DATABASE IF EXISTS test;
CREATE DATABASE test;
USE test;
CREATE TABLE organization (org_id INT UNSIGNED PRIMARY KEY, name VARCHAR(255));
CREATE TABLE scale (sc_id INT UNSIGNED PRIMARY KEY, name VARCHAR(255));
CREATE TABLE organization_scale (org_id INT UNSIGNED, sc_id INT UNSIGNED,
FOREIGN KEY (org_id)
REFERENCES organization(org_id)
ON UPDATE CASCADE ON DELETE CASCADE,
FOREIGN KEY (sc_id)
REFERENCES scale(sc_id)
ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE TRIGGER delete_unused_sc_id AFTER DELETE ON organization FOR EACH ROW DELETE FROM scale WHERE NOT EXISTS (SELECT * FROM organization_scale WHERE organization_scale.sc_id = scale.sc_id);
INSERT INTO organization VALUES
(1,"Mickey"),
(2,"Donald"),
(3,"Dingo");
INSERT INTO scale VALUES
(1,"miniScale"),
(2,"maxiScale");
INSERT INTO organization_scale VALUES
(1,1),
(2,1),
(3,2);
SELECT * FROM organization_scale;
SELECT * FROM scale;
DELETE FROM organization WHERE org_id=3;
SELECT * FROM organization_scale;
SELECT * FROM scale;
DROP DATABASE test;

Delete record with foreign key to itself [duplicate]

I have a MySQL table whose definition is as follows:
CREATE TABLE `guestbook` (
`Id` int(10) unsigned NOT NULL,
`ThreadId` int(10) unsigned NOT NULL,
PRIMARY KEY (`Id`),
KEY `ThreadId` (`ThreadId`),
CONSTRAINT `guestbook_ibfk_1` FOREIGN KEY (`ThreadId`) REFERENCES `guestbook` (`Id`)
) ENGINE=InnoDB;
and currently there's only 1 row in the table:
mysql> select * from guestbook;
+-----+----------+
| Id | ThreadId |
+-----+----------+
| 211 | 211 |
+-----+----------+
The problem is that there's no way to delete this row without breaking the constraint.
mysql> delete from guestBook;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`polaris`.`guestbook`, CONSTRAINT `guestbook_ibfk_1` FOREIGN KEY (`ThreadId`) REFERENCES `guestbook` (`Id`))
As the ThreadId column was defined not null, it is also impossible to set the ThreadId to a different value temporarily to delete the row. Is there a way to delete the row without changing the definition of the table or dropping the entire table?
You can temporarily disable foreign key constraints with this query:
SET foreign_key_checks = 0;
If you put an ON DELETE CASCADE action on your foreign key, you should be able to delete rows that are self-referencing.
CONSTRAINT `guestbook_ibfk_1` FOREIGN KEY (`ThreadId`) REFERENCES `guestbook` (`Id`) ON DELETE CASCADE
The benefit this has over using ON DELETE SET NULL is that you don't have to alter your schema to make the "ThreadId" column nullable.
There are several workarounds. The approach suggested by others ...
SET foreign_key_checks = 0;
... will disable the foreign keys of every table. This is not suitable for use in a shared environment.
Another approach is to drop the foreign key using
ALTER TABLE `guestbook`
DROP FOREIGN KEY `guestbook_ibfk_1`
/
We can sort out the data using DML, and then reinstate the foreign key using:
ALTER TABLE `guestbook`
ADD CONSTRAINT `guestbook_ibfk_1` FOREIGN KEY (`ThreadId`)
REFERENCES `guestbook` (`Id`)
/
But is there a way to change the data without executing any DDL? Well, we can insert a new record and change the current record to reference it:
INSERT INTO `guestbook` VALUES (212, 211)
/
UPDATE `guestbook`
SET `ThreadId` = 212
WHERE `Id` = 211
/
Astute observers will have noticed that we have still ended up with a co-dependency, only between records. So we haven't really advanced; we now have two records we cannot delete, instead of one. (Incidentally this applies to whatever DML we might execute while the foreign key is dropped or disabled). So, perhaps we need to reconsider of the data model. Are we modelling a graph with circular dependencies or a hierarchy?
A hierarchical data structure needs at least one root node, a record on which other records can depend but which itself depends on no record. The usual way of implementing this is to make the foreign key column optional. At the toppermost level of the hierarchy the record must have a NULL in that column. Whether there should be only one such root node or whether several would be allowed is a matter for your business rules.
ALTER TABLE `guestbook` MODIFY `ThreadId` int(10) unsigned
/
In modelling terms this is not different from a record which is its own master, but it is a more intuitive solution.
The inability to delete a self-referencing row is a longstanding known bug/outstanding feature request in MySQL.
In many situations where you rub up against this problem you can NULL the foreign key before executing the delete, so your workaround affects only the rows you intend (uses the same WHERE clause).
Ya temporarily disable the foreign key
set foreign_key_checks=0;
If you set an ON DELETE SET NULL on my Foreign Key, it let's me delete a self-referencing. If I don't specify an ON DELETE, MySQL defaults to RESTRICT.
Of course, make sure the column is NULLABLE. You may also try SET DEFAULT depending on what the default is. But remember NO ACTION is just an alias to RESTRICT in MySQL!
Only tested on MySQL 5.6 (which was not released when this question was originally posted).

Can we refer to two different tables for one foreign key?

I have these tables:
table1
-----------------------
tb1_id
name
other stuff
table2
-------------------------------
tb2_Id
other stuff
table 3
--------------------------------
id
ref Id ->either tb2_id or tb1_id
Can this be achieved from the below code ?
CREATE TABLE `eloan`.`table3` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`refId` VARCHAR(45) NOT NULL DEFAULT '',
PRIMARY KEY(`id`),
CONSTRAINT `refId` FOREIGN KEY `refId` (`refId`, `refId`)
REFERENCES `table2` (`tb2_id`, `tb1_id`)
ON DELETE RESTRICT
ON UPDATE RESTRICT
)
ENGINE = InnoDB;
This code returned a "duplicate redid" error.
No. That's not possible.
If you want to use just a single refId column, as you show, you will not be able to declare/define foreign key constraint(s) to reference more than one table.
You may be able to define BEFORE INSERT, BEFORE UPDATE and BEFORE DELETE triggers on the three tables, to perform some checks of integrity, and have the trigger throw an exception/error to prevent some changes.
Obviously, you could define two separate columns in table3, one can be a foreign key reference to table1 the other can reference table2. You can define foreign key constraints.
You can allow both of the columns to be NULL.
If you want to enforce only one or the other column to be populated (at least one of the columns has to be NULL and the other column has to be NOT NULL), you can enforce that in BEFORE INSERT and BEFORE UPDATE triggers.

Error altering primary key column in mysql table

I am very new to SQL and MySQL. I am trying to modify a primary key column in a table so that it auto-increments. This primary key is also a foreign key in another table. I am not able to modify this column due to an error related to the foreign key in the other table. Here is the error:
mysql> desc favourite_food;
+-----------+----------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+----------------------+------+-----+---------+-------+
| person_id | smallint(5) unsigned | NO | PRI | 0 | |
| food | varchar(20) | NO | PRI | | |
+-----------+----------------------+------+-----+---------+-------+
2 rows in set (0.09 sec)
mysql> alter table person modify person_id smallint unsigned auto_increment;
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id: 22
Current database: bank
ERROR 1833 (HY000): Cannot change column 'person_id': used in a foreign key cons
traint 'fk_fav_food_person_id' of table 'bank.favourite_food'
mysql>
I'm sure it is something simple, but I can't figure out why and teh book I am following does not indicate why. Thanks.
Do it something like this
--Drop fk
ALTER TABLE favourite_food DROP FOREIGN KEY fk_fav_food_person_id;
--Alter your pk
ALTER TABLE person modify person_id smallint unsigned auto_increment;
--Recreate fk
ALTER TABLE favourite_food ADD CONSTRAINT fk_fav_food_person_id FOREIGN KEY (person_id) REFERENCES person (person_id) ON DELETE CASCADE;
Haven't checked syntax exactly, but should be close
Execute your SQL again and then run
show engine innodb status
Type the above command onto your MySQL command prompt. It should help you with more info on why the SQL failed to execute.
Try this otherwise:
show innodb status
Take a look here:
Error code 1005, SQL state HY000: Can't create table errno: 150
You cannot alter a primary key as it is referenced as a foreign key in other table. This is because of the referential integrity constraint.
Referential Integrity Although the main purpose of a foreign key constraint is to control the data that can be stored in the foreign
key table, it also controls changes to data in the primary key table.The constraint enforces referential integrity by guaranteeing that changes cannot be made to data in the primary key table if those changes invalidate the link to data in the foreign key table. If an attempt is made to delete the row in a primary key table or to change a primary key value, the action will fail when the deleted or changed primary key value corresponds to a value in the foreign key constraint of another table. To successfully change or delete a row in a foreign key constraint, you must first either delete the foreign key data in the foreign key table or change the foreign key data in the foreign key table, which links the foreign key to different primary key data.
Cascading Referential Integrity
By using cascading referential integrity constraints, you can define
the actions that the Database Engine takes when a user tries to delete
or update a key to which existing foreign keys point. The following
cascading actions can be defined.
NO ACTION
The Database Engine raises an error and the delete or update action on
the row in the parent table is rolled back.
CASCADE
Corresponding rows are updated or deleted in the referencing table
when that row is updated or deleted in the parent table. CASCADE
cannot be specified if a timestamp column is part of either the
foreign key or the referenced key. ON DELETE CASCADE cannot be
specified for a table that has an INSTEAD OF DELETE trigger. ON UPDATE
CASCADE cannot be specified for tables that have INSTEAD OF UPDATE
triggers.
SET NULL
All the values that make up the foreign key are set to NULL when the
corresponding row in the parent table is updated or deleted. For this
constraint to execute, the foreign key columns must be nullable.
Cannot be specified for tables that have INSTEAD OF UPDATE triggers.
SET DEFAULT
All the values that make up the foreign key are set to their default
values if the corresponding row in the parent table is updated or
deleted. For this constraint to execute, all foreign key columns must
have default definitions. If a column is nullable, and there is no
explicit default value set, NULL becomes the implicit default value of
the column. Cannot be specified for tables that have INSTEAD OF UPDATE
triggers.
CASCADE, SET NULL, SET DEFAULT and NO ACTION can be combined on tables
that have referential relationships with each other. If the Database
Engine encounters NO ACTION, it stops and rolls back related CASCADE,
SET NULL and SET DEFAULT actions. When a DELETE statement causes a
combination of CASCADE, SET NULL, SET DEFAULT and NO ACTION actions,
all the CASCADE, SET NULL and SET DEFAULT actions are applied before
the Database Engine checks for any NO ACTION.

Working with foreign keys - cannot insert

Doing my first tryouts with foreign keys in a mySQL database and are trying to do a insert, that fails for this reason: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails
Does this mean that foreign keys restrict INSERTS as well as DELETES and/or UPDATES on each table that is enforced with foreign keys relations?
Thanks!
Updated description:
Products
----------------------------
id | type
----------------------------
0 | 0
1 | 3
ProductsToCategories
----------------------------
productid | categoryid
----------------------------
0 | 0
1 | 1
Product table has following structure
CREATE TABLE IF NOT EXISTS `alpha`.`products` (
`id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT ,
`type` TINYINT(2) UNSIGNED NOT NULL DEFAULT 0 ,
PRIMARY KEY (`id`) ,
CONSTRAINT `funkyfunky`
FOREIGN KEY (`id` )
REFERENCES `alpha`.`ProductsToCategories` (`productid` )
ON DELETE CASCADE,
ON UPDATE CASCADE)
ENGINE = InnoDB;
Your insert is failing because the foreign key in the row you are inserting doesn't match a valid key in the constraint table. For example:
Assume you've got these two tables:
Employees
----------------------------
EmpID | Name
----------------------------
0 | John
1 | Jane
OfficeAssignments
----------------------------
OfficeID | EmpID
----------------------------
0 | 0
1 | 1
If you have a foreign key constraint on OfficeAssignments.EmpID -> Employees.EmpID, and you try to execute:
INSERT INTO OfficeAssignments (OfficeID, EmpID) VALUES (2,2)
The statement will fail because there is no entry in the Employees table with an EmpID of 2.
Constraints are designed to ensure that your dependent table always has valid data with regard to the parent table -- in this example, you will never have an office which is listed as assigned to an employee who doesn't exist in the system, either because they never existed (as in this case) or because they've been deleted (because the constraint will prevent the employee record from being deleted until the office assignment record has been deleted first).
Edit: Now that you've posted the constraint, it indeed looks like it might be set up backwards. By placing the constraint in the definition of the Products table, you are making it the child, and ProductsToCategories the parent. The constraint you've written can be read as, "a Product must be assigned to a category before it can be created". I suspect what you meant is the other way around: "a Product must be created before it can be assigned to a category." To get that result, you need to place the constraint on the ProductsToCategories table, setting the foreign key to productid and referencing Products.id.
You cannot delete a row from the parent table while there is a foreign key reference to it from a child table. Also you cannot insert/update in the child table with invalid id's in the foreign key column.
Edit: The "CONSTRAINT funkyfunky FOREIGN KEY (id)" must be declared in the "ProductsToCategories" table not in the "Products" table, because "ProductsToCategories" is referencing "Products" not the opposite as you have did.
Your products table is slightly wrong, as you don't need to reference anything from it. References go in the "other" tables, and point to the main, e.g:
create table products (
id int auto_increment,
type int,
primary key (id)
);
create table categories (
id int auto_increment,
name varchar(128),
primary key (id)
)
create table products_to_categories (
product_id int references products,
category_id int references categories
);
A foreign key enforces a valid relation between the rows in two tables. In order to be able to insert a row into a table containing a foreign key, there must be a row in the referenced table containing that key or the insert will fail. The same with delete, you can't delete the row in the referenced table while there are still rows in the table with the foreign key that still reference it. The prevents ending up with rows in the dependent table that have data, but don't have associated rows in the referenced table, i.e., a violation of referential integrity.