Simple many-to-many sql relation - mysql

I'm trying to build the simplest MySQL db with two tables - users and many-to-many related table for friends.
So here's my initial SQL :
/*
Source Server Type : MySQL
Source Server Version : 50614
Target Server Type : MySQL
Target Server Version : 50614
File Encoding : utf-8
*/
SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for `users`
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8_polish_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_idx` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci;
SET FOREIGN_KEY_CHECKS = 1;
and now when trying to create the second table :
DROP TABLE IF EXISTS `friends`;
CREATE TABLE `friends` (
`user_id` int(10) unsigned NOT NULL,
`friend_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`user_id`,`friend_id`),
KEY `FK_FRIENDS_2` (`friend_id`),
CONSTRAINT `FK_FRIENDS_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
CONSTRAINT `FK_FRIENDS_2` FOREIGN KEY (`friend_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I'm getting the following error in Inno log :
------------------------
LATEST FOREIGN KEY ERROR
------------------------
2014-07-15 02:10:36 1341ab000 Error in foreign key constraint of table pc/friends:
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
CONSTRAINT `FK_FRIENDS_2` FOREIGN KEY (`friend_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8:
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.
How do I need to set the indexes to make this work ?

You have given your constraints names, which is unnecessary. You could just remove the names or rename one of the FK_FRIENDS_2 constraints. However, I like the more compact syntax:
CREATE TABLE `friends` (
`user_id` int(10) unsigned NOT NULL REFERENCES `users` (`id`),
`friend_id` int(10) unsigned NOT NULL REFERENCES `users` (`id`),
PRIMARY KEY (`user_id`, `friend_id`),
KEY (`friend_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The fact that the name of a key and a foreign key constraint might conflict may seem confusing. In fact, just think of keys and constraints as being different types of the same thing.

Related

MySQL Cluster - Composite foreign key error: Missing index for constraint

I want to add composite foreign key to my MySQL Cluster table. When I try locally the statement is executed, but on cluster I get the following error:
Failed to add the foreign key constraint. Missing index for constraint... in the referenced table 'y'
This is the statement:
ALTER TABLE x
ADD CONSTRAINT fk_x_y
FOREIGN KEY (y_id, tenant_id)
REFERENCES y(id, tenant_id);
I have executed SHOW FULL COLUMNS FROM for both tables and both columns in each table are the same. Also I have index in y table on id, tenant_id.
MySQL Cluster version: 8.0.25-cluster
Edit 1:
SHOW CREATE TABLE results:
Table x:
CREATE TABLE `x` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`tenant_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_y_id_tenant_id` (`tenant_id`),
CONSTRAINT `fk_x_tenant_id` FOREIGN KEY (`tenant_id`) REFERENCES `tenant` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=ndbcluster AUTO_INCREMENT=446 DEFAULT CHARSET=utf8;
Table y:
CREATE TABLE `reference_list` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`tenant_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `y_id_tenant_id` (`id`,`tenant_id`),
KEY `fk_y_id_tenant_id` (`tenant_id`),
CONSTRAINT `fk_y_id_tenant_id` FOREIGN KEY (`tenant_id`) REFERENCES `tenant` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=ndbcluster AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
I am aware that tenant_id is DEFAULT NULL in one table and NOT_NULL in other, but changing both to DEFAULT NULL didn't solve the problem.
After running: SHOW WARNINGS; I found out I had to add UNIQUE index on id, tenant_id columns.

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.

Attempting to add a foreign key to a table fails?

I have two tables -
CREATE TABLE `FOO` (
`user_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
/*Nothing to see here*/
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=380 DEFAULT CHARSET=latin1;
CREATE TABLE `BAR` (
`ID` int(11) unsigned NOT NULL,
`UserID` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `VSK_UserID_Index` (`UserID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I am attempting create a foreign key constraint on BAR.UserID referencing users.user_id -
ALTER TABLE `FOO`.`BAR`
ADD CONSTRAINT `BAR_UserID_FKey`
FOREIGN KEY (`UserID`)
REFERENCES `FOO`.`users` (`user_id`)
ON DELETE CASCADE
ON UPDATE CASCADE;
I keep getting this error -
Operation failed: There was an error while applying the SQL script to the database.
Error 1452: Cannot add or update a child row: a foreign key constraint fails
Both of these tables have data in them - could this be the reason why this is happening, or is there something wrong with how the tables are being created?
Is there something I need to alter on one of these tables to make this work?
The data already in one of the tables (in particular, `FOO`.`BAR`, since that is the one you're adding a constraint to) is not consistent with the data in `FOO`.`users` (`user_id`).
You must ensure that the values un the `FOO`.`BAR`.`UserID` column all exist in `FOO`.`users` (`user_id`). There may be null values or other values that do not exist in the other column.

unable to create constraint in mysql table

I've got following tables:
node_sharing | CREATE TABLE `node_sharing` (
`user_id` int(11) NOT NULL,
`node_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`node_id`),
KEY `fk_user_id_idx` (`user_id`),
KEY `fk_node_id_idx` (`node_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
and
users | CREATE TABLE `users` (
`id` int(11) NOT NULL,
`has_ard_access` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
I would like to create following foreign key with a constraint:
ALTER TABLE `node_sharing`
ADD CONSTRAINT `fk_user_id`
FOREIGN KEY (`user_id` )
REFERENCES `users` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION;
and MySQL returns the following error:
ERROR 1005 (HY000): Can't create table 'MY_TABLE_NAME.#sql-4d0_218' (errno: 121)
What is wrong here?
PS node_sharing has been truncated, so there are no existing records that could disable putting the constraint on.
It is a duplicate key error. Did you have a table with same name before? If so, check InnoDB internal data dictionary. If not, check if you have another constraint with same name. Constraint names should be unique.