Why is MySQL not properly naming my foreign key? - mysql

Please consider the following SQL code that I run in MySQL 8.0.22 (in an InnoDB database):
CREATE TABLE `person` (
`person_id` smallint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL,
PRIMARY KEY (`person_id`)
);
CREATE TABLE `pet` (
`pet_id` smallint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL,
PRIMARY KEY (`pet_id`)
);
ALTER TABLE `pet`
ADD COLUMN `owner_id` smallint unsigned;
ALTER TABLE `pet`
ADD CONSTRAINT `fk_pet_person`
FOREIGN KEY `idx_fk_pet_person` (`owner_id`)
REFERENCES `person` (`person_id`);
SHOW CREATE TABLE pet;
The output of SHOW CREATE TABLE pet is:
CREATE TABLE `pet` (
`pet_id` smallint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL,
`owner_id` smallint unsigned DEFAULT NULL,
PRIMARY KEY (`pet_id`),
KEY `fk_pet_person` (`owner_id`),
CONSTRAINT `fk_pet_person` FOREIGN KEY (`owner_id`) REFERENCES `person` (`person_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
In the output above, why is the KEY named fk_pet_person when I specified its name as idx_fk_pet_person in my ALTER TABLE command? How can I get it to be named so?

You are confusing the FOREIGN KEY and the INDEX that makes it work. Pay attention - there is NO expression name (which looks like an index definition but is not) in the constraint definition displayed in your code.
When there is no suitable index for the constraint to work, then this index is auto-created, and the constraint name is used as the index name. If a suitable index exists, then auto-creation does not occur.
If you want the index to have a defined name then you must create this index in a separate ALTER TABLE (sub)statement before the constraint creation:
ALTER TABLE `pet`
ADD KEY `idx_fk_pet_person` (`owner_id`),
ADD CONSTRAINT `fk_pet_person`
FOREIGN KEY (`owner_id`)
REFERENCES `person` (`person_id`);
or
ALTER TABLE `pet`
ADD KEY `idx_fk_pet_person` (`owner_id`);
ALTER TABLE `pet`
ADD CONSTRAINT `fk_pet_person`
FOREIGN KEY (`owner_id`)
REFERENCES `person` (`person_id`);
DEMO

MySQL documentation specifies this syntax for creating a foreign key:
[CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (col_name, ...)
REFERENCES tbl_name (col_name,...)
[ON DELETE reference_option]
[ON UPDATE reference_option]
reference_option:
RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT
There are two reasons why my code does not set the [index_name] and instead the index name is automatically made the same as the constraint name. Both are evident from this paragraph taken from the MySQL 8.0 Reference Manual, section 13.1.20.5 FOREIGN KEY Constraints:
Prior to MySQL 8.0.16, if the CONSTRAINT symbol clause was not defined, or a symbol was not included following the CONSTRAINT keyword, both InnoDB and NDB storage engines would use the FOREIGN_KEY index_name if defined. In MySQL 8.0.16 and higher, the FOREIGN_KEY index_name is ignored.
The first reason is that I am running the code in MySQL 8.0.22.
The second reason is that I should have omitted the constraint name.
The workarounds in #akina's answer indeed enable me to specify a name for the foreign key (index) that is different from the constraint's name.

Related

MySQL inconsistently altering name of indexes associated with foreign keys on InnoDB tables

I seem to have encountered an inconsistency in the way an ALTER TABLE statement behaves when dropping and adding a foreign key. Sometimes the associated index will be renamed and other times it isn't. I have identified the situations under which this occurs:
APPROACH #1
A simple person table with an auto-incrementing primary key id and a foreign key column to itself self_id. Note: the behaviour would be the same for two separate tables, I have used one table to simplify the example.
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`self_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `self_id_fk` (`self_id`),
CONSTRAINT `self_id_fk` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Next I rename the foreign key by dropping the existing one and then adding a new one. This is done in a single statement but the behaviour is the same if split into multiple ALTER TABLE statements.
ALTER TABLE `person`
DROP FOREIGN KEY `self_id_fk`,
ADD CONSTRAINT `a_new_fk_name` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`);
After this statement the table is as follows:
CREATE TABLE `person` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`self_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `self_id_fk` (`self_id`),
CONSTRAINT `a_new_fk_name` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Notice that the foreign key has been renamed but the index has not.
APPROACH #2
An alternative way of doing this would be to first create the table without any foreign keys or indexes:
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`self_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Next add the foreign key constraint:
ALTER TABLE `person` ADD CONSTRAINT `self_id_fk` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`);
This results in the following table:
CREATE TABLE `person` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`self_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `self_id_fk` (`self_id`),
CONSTRAINT `self_id_fk` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Note that an index is automatically created due to the behaviour described in the documentation
... an index is created on the referencing table automatically if it
does not exist
Next I rename the foreign key in the same way as APPROACH #1:
ALTER TABLE `person`
DROP FOREIGN KEY `self_id_fk`,
ADD CONSTRAINT `a_new_fk_name` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`);
But this time both the foreign key and the index have been renamed:
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`self_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a_new_fk_name` (`self_id`),
CONSTRAINT `a_new_fk_name` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Is there any explanation into what's going on? It's almost as if MySQL is tracking which indexes are "auto created" and then renaming them when the foreign key is changed. The Table DDL is identical for both approaches before the ALTER TABLE statement is run so there must be some "internal engine state" that MySQL is tracking.
Looking at the DDL alone there is no way to predict in which way MySQL will behave when the ALTER TABLE statement is run. This means that two "schema identical" databases could end up with mismatched schema once a simple ALTER TABLE statement has run.
I have noticed the same thing, but I've never seen any official documentation that explains this.
I agree it seems like InnoDB "knows" which indexes were created implicitly and which were created explicitly. But I don't know where it tracks this information. InnoDB exposes much of the metadata in INFORMATION_SCHEMA tables, but it must store more information in the internal data dictionary.
This is the only documentation about the internal DD in MySQL 5.7 and earlier: https://dev.mysql.com/doc/refman/5.7/en/innodb-data-dictionary.html
The only suggestion I have is that if you need the index name to be predictable, you need to create the index explicitly, then create the foreign key constraint. Don't rely on implicit creation of indexes by foreign keys.
MySQL 8.0 has totally redesigned the data dictionary, so the behavior you observe might change yet again. https://dev.mysql.com/doc/refman/8.0/en/data-dictionary.html

Odd Cannot add foreign key constraint

I have an odd one. I cannot create a table using the following:
The table Users already exists in the DB, only UserTimeZones is to be added, and it fails.
CREATE TABLE `Users` (
`AccessFailedCount` int(11) NOT NULL,
`EmailConfirmed` bit(1) NOT NULL,
`Id` char(36) NOT NULL,
`NormalizedUserName` varchar(256) DEFAULT NULL,
`NormalizedEmail` varchar(256) DEFAULT NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `UserNameIndex` (`NormalizedUserName`),
KEY `EmailIndex` (`NormalizedEmail`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `UserTimeZones` (
`Id` char(36) NOT NULL,
`UserId` char(36) NOT NULL,
`TimeZoneOffsetInSeconds` int NOT NULL,
`LastUpdatedAt` datetime(6) NOT NULL,
CONSTRAINT `PK_UserTimeZones` PRIMARY KEY (`Id`),
CONSTRAINT `FK_UserTimeZones_Users_UserId` FOREIGN KEY (`UserId`) REFERENCES `Users` (`Id`) ON DELETE CASCADE
);
SHOW ENGINE INNODB STATUS;
Here is what the status shows:
------------------------ LATEST FOREIGN KEY ERROR
2018-11-09 11:26:44 0x7f832c523700 Error in foreign key constraint of
table fifty/UserTimeZones:
FOREIGN KEY (UserId) REFERENCES Users (Id) ON DELETE 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.
Please refer to
http://dev.mysql.com/doc/refman/5.7/en/innodb-foreign-key-constraints.html
for correct foreign key definition.
So I have the classic "Cannot add foreign key constraint".
What I have tried:
Placing the Users.Id column as first column : doesn't change anything
The column types are the same, the engines too...
Applying the migration without data in the DB -> it works
Running the script in a DB without data -> still doesn't work...
What is the problem?
Not sure it matters but I use entity framework core.
https://dev.mysql.com/doc/refman/8.0/en/innodb-foreign-key-constraints.html
Foreign key definitions for InnoDB tables are subject to the following
conditions:
InnoDB permits a foreign key to reference any index column or group of
columns. However, in the referenced table, there must be an index
where the referenced columns are listed as the first columns in the
same order.
But oddly the index seems to be needed on the referencing table:
https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html#foreign-keys-examples
So try adding an index on your table
CREATE TABLE `UserTimeZones` (
`Id` char(36) NOT NULL,
`UserId` char(36) NOT NULL,
`TimeZoneOffsetInSeconds` int NOT NULL,
`LastUpdatedAt` datetime(6) NOT NULL,
CONSTRAINT `PK_UserTimeZones` PRIMARY KEY (`Id`),
INDEX userid_ind (UserId),
CONSTRAINT `FK_UserTimeZones_Users_UserId` FOREIGN KEY (`UserId`) REFERENCES `Users` (`Id`) ON DELETE CASCADE
);
Note: this is what the error message says.

Making foreign key to unique index column in mysql

Products
CREATE TABLE if NOT EXISTS `PRODUCTS` (
`ID` INT unsigned NOT NULL AUTO_INCREMENT,
`COMPANY_ID` INT(10) unsigned NOT NULL,
`PRODUCT_CODE` VARCHAR(5) NOT NULL,
`PRODUCT_NAME` VARCHAR(15) NOT NULL,
`UNIT_TYPE` VARCHAR(1) NULL,
UNIQUE INDEX UNIQUE_COMAPNY_PRODUCT_CODE (`COMPANY_ID`, `PRODUCT_CODE`),
CONSTRAINT `PRODUCT_COMPANY_ID_FK` FOREIGN KEY (`COMPANY_ID`) REFERENCES `companies` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE
)
and i want to make a foreign key to the upper 'PRODUCT_CODE' ......
here is my code
ALTER TABLE services
ADD COLUMN `PRODUCT_CODE` VARCHAR(5),
// ADD foreign `PRODUCT_CODE` that refrences to `PRODUCT_CODE` in PRODUCTS table
So how to do the commented line above in mysql ??
The column referenced in a foreign key may be either a Primary Key or Unique. You need to first add an index on PRODUCT_CODE. I have taken it as UNIQUE
ALTER TABLE PRODUCTS ADD UNIQUE INDEX (PRODUCT_CODE);
Then, you can use ALTER TABLE .. ADD FOREIGN KEY syntax:
ALTER TABLE services
ADD FOREIGN KEY (PRODUCT_CODE) REFERENCES PRODUCTS(PRODUCT_CODE);
Combining this to your existing ALTER TABLE query, a single query would look as follows:
ALTER TABLE services
ADD COLUMN `PRODUCT_CODE` VARCHAR(5),
ADD FOREIGN KEY (PRODUCT_CODE) REFERENCES PRODUCTS(PRODUCT_CODE);
If you already have some data in the services table, which has certain PRODUCT_CODE value, which do not exist in the PRODUCTS table. You will get the following error:
Error Code: 1215. Cannot add foreign key constraint
In that case, you will need to fix the data in the tables. You may check this answer for the tips: https://stackoverflow.com/a/53099922/2469308

Need help creating foreign keys sql error 1215

Hello my appologies for asking aid but i'm having a little issue in a database adding references
so i got a table
CREATE TABLE `kooien` (
`kooiid` INT(11) NOT NULL AUTO_INCREMENT,
`kooidlistppg` INT(11) NULL,
`quarantaine` BIT(1) NOT NULL,
`idvogelsoort` INT(11) NULL DEFAULT NULL,
`idvogelondersoort` INT(11) NULL DEFAULT NULL,
`vasteoflossekooie` BIT(1) NOT NULL,
`bezetofniet` BIT(1) NOT NULL,
`idlistsponsor` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`kooiid`),
INDEX `idvogelsoort` (`idvogelsoort`),
INDEX `idvogelondersoort` (`idvogelondersoort`),
INDEX `kooien_ibfk_4_idx` (`idlistsponsor`),
CONSTRAINT `kooien_ibfk_2` FOREIGN KEY (`idvogelsoort`) REFERENCES `vogelsoort` (`id`),
CONSTRAINT `kooien_ibfk_3` FOREIGN KEY (`idvogelondersoort`) REFERENCES `ondersoort` (`id`),
CONSTRAINT `kooien_ibfk_4` FOREIGN KEY (`idlistsponsor`) REFERENCES `personen` (`id`),
CONSTRAINT `FK_kooien_kooientoppg` FOREIGN KEY (`kooidlistppg`) REFERENCES `kooientoppg` (`id`))
COLLATE='utf8_general_ci'
ENGINE=InnoDB
Which appears like
kooiid|kooidlistppg|quarantaine|idvogelsoort|idvogelondersoort|Vasteoflossekooien|bezetofniet|lijstlist sponsor
Now my problem is making kooidlistppg references to kooidlistppg(id)
i do so with this query
ALTER TABLE `kooien`
DROP INDEX `FK_kooien_kooientoppg`,
ADD CONSTRAINT `FK_kooien_kooientoppg` FOREIGN KEY (`kooidlistppg`) REFERENCES `kooientoppg` (`id`);
/* SQL Fout (1215): Cannot add foreign key constraint */
ill add the create query for the table
CREATE TABLE `kooientoppg` (
`id` INT(11) NOT NULL,
`idpapegaaien` INT(11) NULL DEFAULT NULL
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
please know that its lack of a primary key is to convert a list to a mysql database
As explained in the MySQL manual, details of FOREIGN KEY errors with InnoDB can be checked with the following SQL command:
SHOW ENGINE INNODB STATUS
For example:
------------------------
LATEST FOREIGN KEY ERROR
------------------------
2017-01-22 23:43:46 0x7fc9fc0f1700 Error in foreign key constraint of table test/kooien:
FOREIGN KEY (`kooidlistppg`) REFERENCES `kooientoppg` (`id`))
COLLATE='utf8_general_ci'
ENGINE=InnoDB:
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.
Please refer to http://dev.mysql.com/doc/refman/8.0/en/innodb-foreign-key-constraints.html for correct foreign key definition.
As far as I can tell, adding an index to the referenced column would help:
ALTER TABLE kooientoppg ADD INDEX id_idx (id);

Mysql Query Error #1005 150

I have a query but its showing error #1005 and 150 in details. Some innodb
How to get it right?
CREATE TABLE `iars` ( `id` int(10) unsigned NOT NULL auto_increment,
`SessionId` int(10) unsigned NOT NULL,
`TeacherId` int(10) unsigned NOT NULL,
`courseid` int(4) unsigned NOT NULL,
`semester` int(4) unsigned NOT NULL,
`PaperId` int(4) unsigned NOT NULL,
`groupid` int(4) unsigned NOT NULL,
`Type` varchar(4) default 1,
PRIMARY KEY (`id`),
UNIQUE KEY `Constrint_Index`
(`SessionId`,`TeacherId`,`courseid`,`semester`,`PaperId`,`groupid`),
KEY `FK_tid` (`TeacherId`),
KEY `FK_gid` (`GroupId`),
KEY `FK_pid` (`PaperId`),
CONSTRAINT `FK_gid` FOREIGN KEY (`GroupId`) REFERENCES `groups` (`id`) ON DELETE
CASCADE ON UPDATE CASCADE,
CONSTRAINT `FK_pid` FOREIGN KEY (`PaperId`) REFERENCES `papers` (`p_id`) ON DELETE
CASCADE ON UPDATE CASCADE,
CONSTRAINT `FK_ssessionid` FOREIGN KEY (`SessionId`) REFERENCES `sessions` (`id`) ON
DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `FK_tid` FOREIGN KEY (`TeacherId`) REFERENCES `teachers` (`id`) ON DELETE
CASCADE ON UPDATE CASCADE )
From the MySQL manual:
You cannot issue DROP TABLE for an InnoDB table that is referenced by a FOREIGN KEY constraint, unless you do SET foreign_key_checks = 0. When you drop a table, the constraints that were defined in its create statement are also dropped.
If you re-create a table that was dropped, it must have a definition that conforms to the foreign key constraints referencing it. It must have the right column names and types, and it must have indexes on the referenced keys, as stated earlier. If these are not satisfied, MySQL returns error number 1005 and refers to error 150 in the error message.
This means one of your other tables is probably referencing a column in iars which you are not re-creating. The solution, then, is to weed through all of your tables (via describe queries) and see where the reference is and either add the referenced column to your CREATE TABLE here, or remove the reference, as appropriate for your schema.
edit a very helpful gem from later on that referenced page:
SHOW ENGINE INNODB STATUS;
reveals loads of good info on the foreign-key error which just occurred. It should point you exactly where you need to look.