You are here

MariaDB Foreign Key Constraint example

Foreign Key Constraints are used to model a parent/child relation in an entity relationship (ER) model:

SQL> CREATE TABLE team (
  id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT
, name VARCHAR(48) NOT NULL
, PRIMARY KEY (id)
);

SQL> CREATE TABLE employee (
  id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT
, first_name VARCHAR(32) NOT NULL
, last_name VARCHAR(64) NOT NULL
, team_id BIGINT UNSIGNED NOT NULL
, PRIMARY KEY (id)
, CONSTRAINT `fk_employee_team` FOREIGN KEY (team_id)
  REFERENCES team (id) ON DELETE CASCADE ON UPDATE RESTRICT
);
ERROR 1005 (HY000): Can't create table `test`.`employee` (errno: 150 "Foreign key constraint is incorrectly formed")

The same errors looks a bit different in MySQL 8.0:

ERROR 3780 (HY000): Referencing column 'team_id' and referenced column 'id' in foreign key constraint 'fk_team' are incompatible.

To see more details about this error you can look in the InnoDB status output:

SQL> SHOW ENGINE INNODB STATUS\G
...
------------------------
LATEST FOREIGN KEY ERROR
------------------------
2021-08-19 09:45:39 0x7f06221be700 Error in foreign key constraint of table `test`.`employee`:
Create  table `test`.`employee` with foreign key `fk_team` constraint failed. Field type or character set for column 'team_id' does not mach referenced column 'id'.
...

In MySQL 8.0 there seems to be no more output for this error message in InnoDB status. :-(

After fixing the column type it works.

If you want to add a Foreign Key constraint to an existing table you can do it like this:

SQL> ALTER TABLE room
  ADD CONSTRAINT `fk_room_team` FOREIGN KEY (team_id)
  REFERENCES team (id) ON DELETE CASCADE ON UPDATE RESTRICT;

If wee look at the table rooms we can see that the indexes are at least partially redundant:

SQL> CREATE TABLE `room` (
  `id` int(11) DEFAULT NULL,
  `room_number` varchar(10) DEFAULT NULL,
  `team_id` tinyint(3) unsigned DEFAULT NULL,
  UNIQUE KEY `team_id` (`team_id`,`room_number`),
  CONSTRAINT `fk_room_team` FOREIGN KEY (`team_id`) REFERENCES `team` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB;

SQL> SELECT table_name, constraint_name, column_name, ordinal_position
  FROM information_schema.KEY_COLUMN_USAGE
 WHERE constraint_schema = 'test' AND table_name = 'room'
;
+------------+-----------------+-------------+------------------+
| table_name | constraint_name | column_name | ordinal_position |
+------------+-----------------+-------------+------------------+
| room       | team_id         | team_id     |                1 |
| room       | team_id         | room_number |                2 |
| room       | fk_room_team    | team_id     |                1 |
+------------+-----------------+-------------+------------------+

Dropping the Foreign Key Constraint again:

SQL> ALTER TABLE room DROP CONSTRAINT fk_room_team;

and creating it again with a hint to use the index:

SQL> ALTER TABLE room
  ADD CONSTRAINT `fk_room_team` FOREIGN KEY `team_id` (team_id)
  REFERENCES team (id) ON DELETE CASCADE ON UPDATE RESTRICT;

does NOT end in the expected result. At least not if the index is a composite index. So we have to try with a non-composite index:

SQL> ALTER TABLE room
  ADD CONSTRAINT FOREIGN KEY (team_id)
  REFERENCES team (id) ON DELETE CASCADE ON UPDATE RESTRICT;

SQL> CREATE TABLE `room` (
  `id` int(11) DEFAULT NULL,
  `room_number` varchar(10) DEFAULT NULL,
  `team_id` tinyint(3) unsigned DEFAULT NULL,
  KEY `team_id` (`team_id`),
  CONSTRAINT `room_ibfk_1` FOREIGN KEY (`team_id`) REFERENCES `team` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB
;

SQL> ALTER TABLE room DROP CONSTRAINT room_ibfk_1, ADD CONSTRAINT `fk_room_team` FOREIGN KEY (team_id) REFERENCES team (id) ON DELETE CASCADE ON UPDATE RESTRICT;

SQL> CREATE TABLE `room` (
  `id` int(11) DEFAULT NULL,
  `room_number` varchar(10) DEFAULT NULL,
  `team_id` tinyint(3) unsigned DEFAULT NULL,
  KEY `team_id` (`team_id`),
  CONSTRAINT `fk_room_team` FOREIGN KEY (`team_id`) REFERENCES `team` (`id`) ON DELETE CASCADE,
  CONSTRAINT `room_ibfk_1` FOREIGN KEY (`team_id`) REFERENCES `team` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB
;

This is not what we want:

+------------+-----------------+-------------+------------------+
| table_name | constraint_name | column_name | ordinal_position |
+------------+-----------------+-------------+------------------+
| room       | fk_room_team    | team_id     |                1 |
| room       | room_ibfk_1     | team_id     |                1 |
+------------+-----------------+-------------+------------------+

Fixing it:

SQL> ALTER TABLE room DROP CONSTRAINT room_ibfk_1, DROP CONSTRAINT fk_room_team;
SQL> ALTER TABLE room ADD CONSTRAINT `fk_room_team` FOREIGN KEY (team_id) REFERENCES team (id) ON DELETE CASCADE ON UPDATE RESTRICT;

does work as expected.