Foreign key and the primary key in the same table - mysql

I need to create a relationship between two attributes in the same table. So both primary key and the foreign keys are in the same table. Here I have a table called User_Type. Primary key is User_ID. It should be the foreign key of Parent_ID.
Ex:
User_Type
User_ID
User_Name
Parent_ID
User_Type_Division
But when I'm creating the relationship I get an error like this.
Cannot add or update a child row: a foreign key constraint fails
(mydb.user_type, CONSTRAINT Parent_User_Type FOREIGN KEY
(Parent_ID) REFERENCES user_type (User_ID) ON DELETE NO ACTION
ON UPDATE NO ACTION)").
Is there any way available to avoid this error. Please someone let me know.
And Here I have given the Query of the table.
CREATE TABLE IF NOT EXISTS `user_type` (
`User_ID` int(11) NOT NULL AUTO_INCREMENT,
`User_Name` varchar(45) NOT NULL,
`Parent_ID` int(11) DEFAULT NULL,
`User_Type_Division` varchar(45) DEFAULT NULL,
`User_ID_Format` varchar(45) DEFAULT NULL,
`Data_Entered_Person` varchar(45) DEFAULT NULL,
`Entered_Time` varchar(45) DEFAULT NULL,
PRIMARY KEY (`User_ID`),
UNIQUE KEY `User_Name_UNIQUE` (`User_Name`),
KEY `ParentUserType` (`Parent_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

There is a way to add and enforce foreign key on existing data.
Step 1: You need to stop foreign key checks for the current session.
SET FOREIGN_KEY_CHECKS=0;
Step 2: Add foreign key to your table.
ALTER TABLE myTable
ADD CONSTRAINT fk_name FOREIGN KEY ( columnName ) REFERENCES ...
Step 3: Enable foreign key checks.
SET FOREIGN_KEY_CHECKS=1;
Working Example:
mysql> create table fkchk( i int not null primary key auto_increment, n int );
Query OK, 0 rows affected (0.26 sec)
mysql> insert into fkchk(n) values ( 0 );
Query OK, 1 row affected (0.10 sec)
mysql> show variables like '%fore%';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| foreign_key_checks | ON |
+--------------------+-------+
1 row in set (0.00 sec)
mysql> alter table fkchk
> add constraint fk_n foreign key (n) references fkchk(i)
> on delete no action;
ERROR 1452 (23000): Cannot add or update a child row:
a foreign key constraint fails
(`test`.<result 2 when explaining filename '#sql-6fc_14'>,
CONSTRAINT `fk_n` FOREIGN KEY (`n`)
REFERENCES `fkchk` (`i`) ON DELETE NO ACTION)
mysql> SET FOREIGN_KEY_CHECKS=0;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like '%fore%';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| foreign_key_checks | OFF |
+--------------------+-------+
1 row in set (0.00 sec)
mysql> alter table fkchk
> add constraint fk_n foreign key (n) references fkchk(i)
> on delete no action;
Query OK, 0 rows affected (0.50 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> insert into fkchk(n) values ( 0 );
Query OK, 1 row affected (0.32 sec)
mysql> select * from fkchk;
+---+------+
| i | n |
+---+------+
| 1 | 0 |
| 2 | 0 |
+---+------+
2 rows in set (0.00 sec)
mysql> SET FOREIGN_KEY_CHECKS=1;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like '%fore%';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| foreign_key_checks | ON |
+--------------------+-------+
1 row in set (0.00 sec)
mysql> insert into fkchk(n) values ( 1 );
Query OK, 1 row affected (0.12 sec)
mysql> insert into fkchk(n) values ( 0 );
ERROR 1452 (23000): Cannot add or update a child row:
a foreign key constraint fails
(`test`.`fkchk`,
CONSTRAINT `fk_n` FOREIGN KEY (`n`)
REFERENCES `fkchk` (`i`) ON DELETE NO ACTION)
mysql>
mysql> select * from fkchk;
+---+------+
| i | n |
+---+------+
| 1 | 0 |
| 2 | 0 |
| 3 | 1 |
+---+------+
3 rows in set (0.00 sec)
mysql>

Double check that the User_ID column and the Parent_ID column are of the same datatype.
Also, your User_ID key column is set to NOT NULL, but your Parent_ID column is set to null. that could be the problem. Set Parent_ID to NOT NULL and then try creating the FK relationship again. This is one of the options I was talking about. However, if you already have data in your table, this could be an issue too.

Related

MySQL ENUM to VARCHAR conversion issue

I have a table "temp_enum_test1" with data type "enum('IE','IS')". When I try to ALTER the table by changing data type into VARCHAR from 'ENUM', getting duplicate entry error. It is accepting records in ENUM type. Even when I query the table I am getting unique rows. Can anyone please help me here. Below are the schema and my approach.
mysql> CREATE TABLE temp_enum_test1 (
-> r_id int(11) NOT NULL,
-> r_type enum('IE','IS'),
-> UNIQUE KEY uk_temp_enum_test1 (r_id,r_type)
-> );
Query OK, 0 rows affected (0.38 sec)
mysql> insert into temp_enum_test1 values(1,'IE');
Query OK, 1 row affected (0.07 sec)
mysql> insert into temp_enum_test1 values(1,'IS');
Query OK, 1 row affected (0.05 sec)
mysql> select * from temp_enum_test1;
+------+--------+
| r_id | r_type |
+------+--------+
| 1 | IE |
| 1 | IS |
+------+--------+
2 rows in set (0.00 sec)
mysql> alter table temp_enum_test1 change column r_type r_type varchar(30);
ERROR 1062 (23000): Duplicate entry '1-I' for key 'uk_temp_enum_test1'
mysql>
The problem seems to be with the create table query. The table has been created with 'UNIQUE KEY' that is causing the error while altering the table. If you can use
PRIMARY KEY (`r_id`)
instead of ...
UNIQUE KEY uk_temp_enum_test1 (r_id,r_type)
Complete CREATE command as below;
CREATE TABLE IF NOT EXISTS temp_enum_test1 (
`r_id` int(11) NOT NULL AUTO_INCREMENT,
`r_type` enum('IE','IS') NOT NULL,
PRIMARY KEY (`r_id`)
)

MySQL won't allow update despite ON UPDATE CASCADE in foreign key

I have a really weird problem with MySQL where ON UPDATE CASCADE is behaving like ON UPDATE RESTRICT.
I have two MySQL servers, an old Windows Server 2012 and a new Ubuntu 16.04 LTS.
On both servers, I have three tables -- item, invoice and invoice_box
CREATE TABLE `item` (
`id` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`idInvoice` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`boxNumber` tinyint(4) NULL DEFAULT NULL,
CONSTRAINT `fk_gauge_item_invoice` FOREIGN KEY (`idInvoice`) REFERENCES `invoice` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_gauge_item_invoice_box` FOREIGN KEY (`idInvoice`, `boxNumber`) REFERENCES `invoice_box` (`idInvoice`, `boxNumber`) ON DELETE SET NULL ON UPDATE CASCADE
)
CREATE TABLE `invoice_box` (
`idInvoice` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`boxNumber` tinyint(4) NOT NULL,
CONSTRAINT `fk_gauge_box_invoice` FOREIGN KEY (`idInvoice`) REFERENCES `invoice` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
)
CREATE TABLE `invoice` (
`id` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL
)
On my old Windows server, if I update the invoice id, the update cascades to the item and invoice_box table successfully.
On the Ubuntu server, it works only if
If there are no entries in item table, then it cascades to invoice_box table correctly.
If there are no entries in invoice_box table, then it cascades to item table correctly.
If there are entries in both item and invoice_box table, then it fails with the following message.
1452 - Cannot add or update a child row; a foreign key constraint fails (product.item CONSTRAINT fk_item_invoice FOREIGN KEY (idInvoice) REFERENCES invoice(id) ON DELETE SET NULL ON UPDATE CASCADE)
I don't understand why this won't work exactly the same on both servers.
I did a dump from the Windows server to migrate onto the Ubuntu server, if that makes any difference.
Code works as coded with the removal of fk_gauge_item_invoice
drop table if exists i;
drop table if exists ib;
drop table if exists inv;
CREATE TABLE `inv` (
`id` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
primary key (id)
);
CREATE TABLE `ib` (
`idInvoice` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`boxNumber` tinyint(4) NOT NULL,
key ib1(idinvoice,boxnumber),
CONSTRAINT `fk_gauge_box_invoice` FOREIGN KEY (`idInvoice`) REFERENCES `inv` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE `i` (
`id` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`idInvoice` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`boxNumber` tinyint(4) NULL DEFAULT NULL,
#CONSTRAINT `fk_gauge_item_invoice` FOREIGN KEY (`idInvoice`) REFERENCES `inv` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_gauge_item_invoice_box` FOREIGN KEY (`idInvoice`, `boxNumber`) REFERENCES `ib` (`idInvoice`, `boxNumber`) ON DELETE SET NULL ON UPDATE CASCADE
);
insert into inv values (1),(2),(3);
insert into ib values (1,1),(1,2),(2,1),(2,2),(2,3);
insert into i values (1,1,1),(1,1,2),(2,1,1),(2,2,2),(2,2,3);
MariaDB [sandbox]> update inv set id = 10 where id = 1;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
MariaDB [sandbox]>
MariaDB [sandbox]> select * from inv;
+----+
| id |
+----+
| 10 |
| 2 |
| 3 |
+----+
3 rows in set (0.00 sec)
MariaDB [sandbox]> select * from ib;
+-----------+-----------+
| idInvoice | boxNumber |
+-----------+-----------+
| 10 | 1 |
| 10 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
+-----------+-----------+
5 rows in set (0.00 sec)
MariaDB [sandbox]> select * from i;
+----+-----------+-----------+
| id | idInvoice | boxNumber |
+----+-----------+-----------+
| 1 | 10 | 1 |
| 1 | 10 | 2 |
| 2 | 10 | 1 |
| 2 | 2 | 2 |
| 2 | 2 | 3 |
+----+-----------+-----------+
5 rows in set (0.00 sec)
MariaDB [sandbox]>
MariaDB [sandbox]> update ib set idinvoice = 20 where idinvoice = 10;
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`sandbox`.`ib`, CONSTRAINT `fk_gauge_box_invoice` FOREIGN KEY (`idInvoice`) REFERENCES `inv` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)
So I'm not clear what your problem is - if you can set up a sql fiddle to demonstrate your issue then that would help.

How to do unique constraint works with NULL value in MySQL

I am looking for how to implement unique constraints with NULL check.
MySQL shouldn't allow multiple null value.
Employee:
id | name
---|-----
1 | null
2 | null -> should give error during inserting 2nd row.
No, MySQL is doing the right thing, according to the SQL-99 specification.
https://mariadb.com/kb/en/sql-99/constraint_type-unique-constraint/
A UNIQUE Constraint makes it impossible to COMMIT any operation that
would cause the unique key to contain any non-null duplicate values.
(Multiple null values are allowed, since the null value is never equal
to anything, even another null value.)
If you use a UNIQUE constraint but don't want multiple rows with NULL, declare the columns as NOT NULL and prohibit any row from having NULL.
MySQL 5.7 does allow for a workaround:
mysql> CREATE TABLE `null_test` (
-> `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-> `const` varchar(255) NOT NULL DEFAULT '',
-> `deleted_at` datetime DEFAULT NULL,
-> PRIMARY KEY (`id`)
-> ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.03 sec)
With soft deletes, it would be nice if you could have just one row with a with a deleted_at = NULL per constraint.
mysql> ALTER TABLE `null_test` ADD `vconst` int(1) GENERATED ALWAYS AS (((NULL = `deleted_at`) or (NULL <=> `deleted_at`))) VIRTUAL;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
So I created a virtual column that will flip from 1 to null when deleted_at gets set.
mysql> ALTER TABLE `null_test` ADD UNIQUE KEY `nullable_index` (`const`,`vconst`);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Instead of including deleted_at to the unique constraint add the virtual column, vconst.
mysql> INSERT INTO `null_test` SET `const` = 'Ghost';
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM `null_test` WHERE `const` = 'Ghost';
+--------+-------+------------+--------+
| id | const | deleted_at | vconst |
+--------+-------+------------+--------+
| 999901 | Ghost | NULL | 1 |
+--------+-------+------------+--------+
1 row in set (0.01 sec)
No need to insert the vconst (but you cannot, anyhow).
mysql> INSERT INTO `null_test` SET `const` = 'Ghost';
ERROR 1062 (23000): Duplicate entry 'Ghost-1' for key 'nullable_index'
Inserting it again throws the Duplicate entry error.
mysql> UPDATE `null_test` SET `deleted_at` = NOW() WHERE `const` = 'Ghost';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Same with setting delete_at, no need to touch vconst, it will flip automatically.
mysql> SELECT * FROM `null_test` WHERE `const` = 'Ghost';
+--------+-------+---------------------+--------+
| id | const | deleted_at | vconst |
+--------+-------+---------------------+--------+
| 999901 | Ghost | 2017-02-16 22:07:45 | NULL |
+--------+-------+---------------------+--------+
1 row in set (0.00 sec)
mysql> INSERT INTO `null_test` SET `const` = 'Ghost';
Query OK, 1 row affected (0.00 sec)
Now you are free to insert a new row with the same constraints!
mysql> SELECT * FROM `null_test` WHERE `const` = 'Ghost';
+--------+-------+---------------------+--------+
| id | const | deleted_at | vconst |
+--------+-------+---------------------+--------+
| 999901 | Ghost | 2017-02-16 22:07:45 | NULL |
| 999903 | Ghost | NULL | 1 |
+--------+-------+---------------------+--------+
2 rows in set (0.01 sec)
In this case, depending on how much you soft delete, setting deleted_at, you might want to include deleted_at to the index, or a new index with it, but I will let my load tests decide.
alter table yourtable add column `virtual_null` varchar(20) GENERATED ALWAYS AS (if(isnull(`your_nullable_column`),'null',`your_nullable_column`))) VIRTUAL;
alter table yourtable add constraint unique(virtual_null);
Make this and be happy, behind the scenes mysql's null is a hash value. Because that its impossible compare two null values...
Sorry by poor english, good luck

MySQL ON UPDATE CASCADE not CASCADEing

Suppose i have two below table:
CREATE TABLE post (
id bigint(20) NOT NULL AUTO_INCREMENT,
text text ,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1;
CREATE TABLE post_path (
ancestorid bigint(20) NOT NULL DEFAULT '0',
descendantid bigint(20) NOT NULL DEFAULT '0',
length int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (ancestorid,descendantid),
KEY descendantid (descendantid),
CONSTRAINT f_post_path_ibfk_1
FOREIGN KEY (ancestorid) REFERENCES post (id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT f_post_path_ibfk_2
FOREIGN KEY (descendantid) REFERENCES post (id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB;
And inserted these rows:
INSERT INTO
post (text)
VALUES ('a'); #// inserted row by id=1
INSERT INTO
post_path (ancestorid ,descendantid ,length)
VALUES (1, 1, 0);
When i want to update post row id:
UPDATE post SET id = '10' WHERE post.id =1
MySQL said:
#1452 - Cannot add or update a child row: a foreign key constraint fails (test.post_path, CONSTRAINT f_post_path_ibfk_2 FOREIGN KEY (descendantid) REFERENCES post (id) ON DELETE CASCADE ON UPDATE CASCADE)
Why? what is wrong?
Edit:
When i inserted these rows:
INSERT INTO
post (text)
VALUES ('b'); #// inserted row by id=2
INSERT INTO
post_path (ancestorid, descendantid, length)
VALUES (1, 2, 0);
And updated:
UPDATE post SET id = '20' WHERE post.id =2
Mysql updated successfully both child and parent row.
so Why i can not update first post (id=1)?
Ok, I ran your schema and queries through a test database I have access too and noticed the following; after inserting both rows to both tables, and before any updates the data looks like:
mysql> select * from post;
+----+------+
| id | text |
+----+------+
| 1 | a |
| 2 | b |
+----+------+
2 rows in set (0.00 sec)
mysql> select * from post_path;
+------------+--------------+--------+
| ancestorid | descendantid | length |
+------------+--------------+--------+
| 1 | 1 | 0 |
| 1 | 2 | 0 |
+------------+--------------+--------+
2 rows in set (0.00 sec)
After I issue the update statement, to update post.id to 20:
mysql> UPDATE `post` SET `id` = '20' WHERE `post`.`id` =2;
Query OK, 1 row affected (0.08 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from post_path;
+------------+--------------+--------+
| ancestorid | descendantid | length |
+------------+--------------+--------+
| 1 | 1 | 0 |
| 1 | 20 | 0 |
+------------+--------------+--------+
2 rows in set (0.00 sec)
Notice that the ancestorid is still 1, this appears to be an issue with MySQL:
If you use a multiple-table UPDATE 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, update a single table and rely on the ON UPDATE capabilities that InnoDB provides to cause the other tables to be modified accordingly. See Section 14.3.5.4, “InnoDB and FOREIGN KEY Constraints”.
The reason why your first query is failing, is because ancestorid is not updating to 10, but descendantid is, and because you are trying to set post.id to 10, and ancestorid in post_path table is still referencing the value 1, which would no longer exist.
You should consider altering your schema to avoid this, and to also avoid updating an auto_increment column so you avoid collisions.
I believe the solution to your problem is to remove descendantid as a constraint and use a trigger to perform an update on the field.
delimiter $$
CREATE TRIGGER post_trigger
AFTER UPDATE ON post
FOR EACH ROW
BEGIN
UPDATE post_path SET post_path.descendantid = NEW.id WHERE post_path.descendantid = OLD.id
END$$
The main reason why the second one worked is that you have kept different values for ancestorid and descendantid. When you are making two different constraints on the basis of a change on a particular attributes. only the first constraint will work, not the second one. Which is the case in your first update try.
The reason the first update fails and second does not is because in the second instance your ancestorid and descendantid reference different rows in your post table,
ancestorid = 1
descendantid = 2
The first update fails when it attempts to update post_path.ancestorid as in doing so the constraint between post.id and post_path.descendantid fails as these values would no longer match (1 !== 10).
Assuming that any given post cannot be both an ancestor and a descendant then the issue here is only in the execution of the first insert:
INSERT INTO `post_path` (`ancestorid` ,`descendantid` ,`length`) VALUES (1, 1, 0);

Self referencing foreign key - mysql not set to null

Indexes :
Keyname Type Unique Packed Column Cardinality Collation Null
parent_id BTREE No No parent_id 1 A YES
Table : (comments)
Column Type Null Default Extra
id int(11) No None AUTO_INCREMENT
parent_id int(11) Yes NULL
Relation view:
Column Foreign key constraint (INNODB)
parent_id 'test_site'.'comments'.'id' ON DELETE CASCADE ON UPDATE NO ACTION
Is it possible to have parent_id not set to NULL. I have tried setting the default value to '0' and inserting the value '0' but I get the following error.
Error:
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update
a child row: a foreign key constraint fails (`test_site`.`comments`,
CONSTRAINT `comments_ibfk_2` FOREIGN KEY (`parent_id`) REFERENCES `comments`
(`id`) ON DELETE CASCADE ON UPDATE NO ACTION)
Any help on this would be much appreciated, Thank you.
Yes, it is possible, although you have to circumvent the foreign key constraint just once to insert a dummy record for the default value. Here's my workflow:
Here's the table creation:
root#localhost:playground > create table comments(id int auto_increment primary key, parent_id int not null default 0, constraint fk_parent_id foreign key (parent_id) references comments(id) on delete cascade on update cascade)engine=innodb;
Query OK, 0 rows affected (0.01 sec)
root#localhost:playground > show create table comments\G
*************************** 1. row ***************************
Table: comments
Create Table: CREATE TABLE `comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `fk_parent_id` (`parent_id`),
CONSTRAINT `fk_parent_id` FOREIGN KEY (`parent_id`) REFERENCES `comments` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
Now circumvent foreign key and insert dummy record.
root#localhost:playground > set session foreign_key_checks=0;
Query OK, 0 rows affected (0.00 sec)
root#localhost:playground > insert into comments (id) values (null); Query OK, 1 row affected (0.00 sec)
root#localhost:playground > set session foreign_key_checks=1;
Query OK, 0 rows affected (0.00 sec)
root#localhost:playground > select * from comments;
+----+-----------+
| id | parent_id |
+----+-----------+
| 1 | 0 |
+----+-----------+
1 row in set (0.00 sec)
root#localhost:playground > update comments set id = 0 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root#localhost:playground > select * from comments;
+----+-----------+
| id | parent_id |
+----+-----------+
| 0 | 0 |
+----+-----------+
1 row in set (0.00 sec)
To make things neat and tidy I reset auto_increment (this is not necessary):
root#localhost:playground > alter table comments auto_increment=0;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
And from now on your foreign key constraint is working properly and your column is no longer nullable and has a default value:
root#localhost:playground > insert into comments (id) values (null);
Query OK, 1 row affected (0.00 sec)
root#localhost:playground > select * from comments;
+----+-----------+
| id | parent_id |
+----+-----------+
| 0 | 0 |
| 1 | 0 |
+----+-----------+
2 rows in set (0.00 sec)