Composite foreign key constraint - mysql

The composite foreign key indexes do not work as I thought it would.
In the following example, I want only the 10 combinations to be allowed in the child table. But the last insert statement is successful even if there is no matching combination in the parent table.
Is there any other way to achieve that kind of constraint?
drop table if exists child;
drop table if exists parent;
CREATE TABLE parent(
`ID` int(11) default NULL,
`name` varchar(100) default NULL,
`city` varchar(100) default NULL,
key (name,city),
key (ID)
) ENGINE=InnoDB;
create table child(
userID int not null,
`name` varchar(100) default NULL,
`city` varchar(100) default NULL,
key (name,city),
FOREIGN KEY (name,city) REFERENCES parent(name,city),
primary key (userID)
) ENGINE=InnoDB;
insert into parent values (1, 'Amar', 'mumbai');
insert into parent values (2, 'Amar', 'Delhi');
insert into parent values (3, 'Amar', NULL);
insert into parent values (4, 'Akbar', 'mumbai');
insert into parent values (5, 'Akbar', 'Delhi');
insert into parent values (6, 'Akbar', NULL);
insert into parent values (7, 'Anthony', 'mumbai');
insert into parent values (8, 'Anthony', 'Delhi');
insert into parent values (9, 'Anthony', NULL);
insert into parent values (10, NULL, NULL);
insert into child values (2, NULL, 'mumbai');

Don't use nulls in foreign key columns; that way leads to the dark side. You should declare such columns NOT NULL.

According to the documentation
The MATCH clause in the SQL standard controls how NULL values in a
composite (multiple- column) foreign key are handled when comparing to
a primary key. InnoDB essentially implements the semantics defined by
MATCH SIMPLE, which permit a foreign key to be all or partially NULL.
In that case, the (child table) row containing such a foreign key is
permitted to be inserted, and does not match any row in the referenced
(parent) table. It is possible to implement other semantics using
triggers.

since child.name field declared nullable it can contain null values. it does not violate foreign key concept.
the solution is to decalre fk fields as NOT NULL

Related

MySQL: FKs to non-unique column

As I found out to my surprise MySQL allows FKs to non-unique columns. I am not sure if this applies to other databases as well and always thought the FK had to be unique - otherwise, how do we know the parent row of a child - but looks like that is not the case. Here is a fiddle that illustrates this. We first create 3 tables:
CREATE TABLE competitions(
cID INT UNSIGNED AUTO_INCREMENT,
title text not null,
primary key (cid)
) engine=innodb CHARACTER SET utf8mb4;
create table teams (
tID INT UNSIGNED AUTO_INCREMENT,
cid int unsigned not null,
name varchar(24) not null,
primary key (tid),
foreign key (cid) references competitions(cid)
) engine=innodb CHARACTER SET utf8mb4;
create table users (
row_id int unsigned auto_increment,
uID INT UNSIGNED not null,
tid int unsigned not null,
cid int unsigned not null,
primary key (row_id),
unique key (cid, uid),
foreign key (tid) references teams(tid),
foreign key (cid) references teams(cid) /* refers to non-unique column */
) engine=innodb CHARACTER SET utf8mb4;
Then we can run following INSERT Commands:
insert into competitions (title) values ('olympics 2020'), ('wimbledon 2021'), ('
ICC world cup 2022');
/* insert duplicate values in cid column. */
insert into teams(cid, name) values (1, 'usa'), (1, 'china'), (2, 'germany'), (2, 'france'), (3, 'india'), (3, 'england');
/* the cid is a FK and is not unique in the parent table but MySQL does not complain! */
insert into users (cid, tid, uid) values (1, 1, 1);
My question is who is the parent row of (1,1,1)? There are two rows in the teams table with cid=1.
This is a peculiarity of the implementation of InnoDB. The foreign key column(s) must reference the leftmost column(s) of any index. You can make it reference a non-unique index, as you discovered.
You can also make it reference a leftmost subset of columns in a unique index:
create table parent (id1 int, id2 int, primary key (id1, id2));
create table child (id1 int, foreign key (id1) references parent(id1) on delete cascade);
But this is nonstandard, and incompatible with other SQL databases. It brings up uncomfortable questions:
mysql> insert into parent values (1,1), (1,2);
mysql> insert into child values (1);
mysql> delete from parent where id1=1 and id2=1;
mysql> select * from child;
Empty set (0.00 sec)
It seems that if any row referenced by the foreign key is deleted, then this causes the delete to cascade. Is this what is desired? Even though there still exists a row in parent that satisfies the foreign key reference?
mysql> select * from parent;
+-----+-----+
| id1 | id2 |
+-----+-----+
| 1 | 2 |
+-----+-----+
Even though it is allowed by InnoDB, I strongly recommend you don't design your tables to depend on it. Keep making foreign keys reference only primary keys or unique keys, and only the complete set of columns of those keys.
A foreign key relationship is not defining a "parent" relationship. It is simply saying that a combination of key values is present in another table.
In practice and in the definition of SQL, the referenced value should be unique (and preferably a primary key). This is required in almost all databases.
MySQL extends this definition to allow any indexed columns.

Composite key is blank and constraint fails on inserts

Having an error when inserting my data into this code into my database. I wanted to make the EventStaff have a primary key made up from StaffID.EventID like in this diagram at the bottom. I can't see to see what I'm doing wrong because the table in StaffID is populated and in TypeID. I was trying to create a composite key of two different tables, is this the wrong way to go about it> i've used the diagram reference.
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`db`.`EventStaff`, CONSTRAINT `fStaff1` FOREIGN KEY (`StaffID`) REFERENCES `Staff` (`StaffID`))
And this is the code for the database
CREATE TABLE EventType (
TypeID INT NOT NULL,
Description VARCHAR(200) NULL,
CONSTRAINT pEventType PRIMARY KEY (TypeID)
);
CREATE TABLE Staff (
StaffID INT NOT NULL,
CONSTRAINT pStaff PRIMARY KEY (StaffID)
);
CREATE TABLE Event (
EventID INT NOT NULL,
Description VARCHAR(500) NULL,
Name VARCHAR(100) NULL,
DateStart DATE NULL,
DateEnd DATE NULL,
TimeStart TIME NULL,
TimeEnd TIME NULL,
TypeID INT NULL,
ClientID INT NULL,
NoBands BOOLEAN NOT NULL,
MaxFoodStall INT NULL,
CONSTRAINT pEvent PRIMARY KEY (EventID),
CONSTRAINT fType1 FOREIGN KEY (TypeID) REFERENCES EventType (TypeID),
);
CREATE TABLE EventStaff (
StaffID int NOT NULL,
EventID int NOT NULL,
Role VARCHAR(45) NULL,
PRIMARY KEY (StaffID, EventID),
CONSTRAINT fStaff1 FOREIGN KEY (StaffID) REFERENCES Staff (StaffID),
CONSTRAINT FEvent1 FOREIGN KEY (EventID) REFERENCES Event (EventID)
);
and here are the inserts
insert into EventType (TypeID, Description) values (1, 'Intuitive attitude-oriented hierarchy');
insert into EventType (TypeID, Description) values (2, 'Mandatory executive concept');
insert into Staff (StaffID) values (1);
insert into Staff (StaffID) values (2);
insert into Event (EventID, Description , DateStart, DateEnd, TimeStart, TimeEnd, NoBands, MaxFoodStall, Name) values (1, 'Cloned 6th generation pricing structure', '2018-03-13', '2018-07-20', '11:18 PM', '4:58 PM', false, 17, 'Swaniawski-Ankunding');
insert into Event (EventID, Description , DateStart, DateEnd, TimeStart, TimeEnd, NoBands, MaxFoodStall, Name) values (2, 'Ameliorated mission-critical throughput', '2017-10-11', '2019-01-05', '10:44 AM', '8:26 PM', true, 4, 'Langworth-Ferry');
insert into EventStaff (Role) values ('Editor');
insert into EventStaff (Role) values ('Budget/Accounting Analyst IV');
https://image.ibb.co/jwaxRx/problem.png
The code you posted to this question has typos, and won't even run as is. Once I fixed those, I was able to reproduce your foreign key error. The problem is actually being caused by these last two inserts:
INSERT INTO EventStaff (Role) VALUES ('Editor');
INSERT INTO EventStaff (Role) VALUES ('Budget/Accounting Analyst IV');
You are not specifying values for the StaffID and EventID, and these columns have been marked as not nullable. They are foreign keys, and it would actually have been OK to insert NULL, had you not marked the columns as non nullable. Here is one way to make this error go away:
INSERT INTO EventStaff (Role, StaffID, EventID)
VALUES ('Editor', 1, 1);
INSERT INTO EventStaff (Role, StaffID, EventID)
VALUES ('Budget/Accounting Analyst IV', 2, 2);
I don't know what values you intend to insert, but the above is one way to go here.
Note that in your insets into the Event table, you don't specify a value for the TypeID column, which is also a foreign key. However, in this case, you have marked the column as nullable, so MySQL accepts it.

SQL data constraints doubts

In SQL, can you set a column that allows null values to be unique?
What happens if the table contains multiple rows with null value for that column?
What happens in case you specify a foreign key to that column in another table and a tuple in the referencing table contains a null value for the foreign key?
You can have as many rows with null as you want in a column set with a Unique Index in mysql. Check the table below:
CREATE TABLE `test1` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`nullable` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `nullable` (`nullable`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INTO `test1` (`id`, `nullable`)
VALUES
(1, NULL),
(2, NULL),
(3, NULL);
You can add as many records as you want to this table with nulls in the nullable field.
I think I invented the name nullable :).

Clarification - Default Foreign Key Constraints - InnoDB

MySQL Server version: 5.6.17
I have two tables:
vatbands:
`vatbands_id` INT(11) UNSIGNED NOT NULL,
`client_id` INT(11) UNSIGNED NOT NULL COMMENT 'Customer ID',
`code` ENUM('A', 'B', 'C', 'D', 'E', 'F') NOT NULL,
PRIMARY KEY (`vatbands_id`, `code`, `client_id`),
INDEX `vatcode_vatbands` (`code` ASC, `client_id` ASC)
ENGINE = InnoDB;
1 row in vatbands:
INSERT INTO `vatbands` (`client_id`, `code`) VALUES ('1', 'A');
items:
`client_id` INT(11) UNSIGNED NOT NULL,
`vatcode` ENUM('A', 'B', 'C', 'D', 'E', 'F') NOT NULL DEFAULT 'A',
PRIMARY KEY (`item_id`, `client_id`),
INDEX `vatcode_item` (`vatcode` ASC, `client_id` ASC),
CONSTRAINT `vatcode_item`
FOREIGN KEY (`vatcode` , `client_id`)
ENGINE = InnoDB;
Inserting into child (item) table:
INSERT INTO `item` (`client_id`, `code`) VALUES ('1', '');
When I try to insert into my items table without specifying a vatcode i get foreign key constraint failure:
Cannot add or update a child row: a foreign key constraint fails (`mydb`.`item`, CONSTRAINT `vatcode_item` FOREIGN KEY (`vatcode`, `client_id`) REFERENCES `vatbands` (`code`, `client_id`) ON DELETE NO ACTION ON UPDATE NO ACTION)
Why is this, I thought specifying a default value for the vatcode would allow this to continue (as long as the row exists)?
I have checked the InnoDB manual:
14.6.6 InnoDB and FOREIGN KEY Constraints
Referential actions for foreign keys of InnoDB tables are subject to
the following conditions:
While SET DEFAULT is allowed by the MySQL Server, it is rejected as
invalid by InnoDB.
Is this the reason it is failing?
UPDATE:
If i input a value directly using PHP:
INSERT INTO `item` (`client_id`, `code`) VALUES ('1', 'A');
The constraint succeeds as expected.
The SET DEFAULT clause for a foreign key has nothing to do with inserting to the child table. It declares what to do with dependent rows in the child table if the referenced row in the parent table is deleted or updated.
Example: If an employee belongs to a department, and the department is deleted, should the employee be fired as well? Or should they be reassigned to some other "default" department?
I tested your example, and I find that it works fine, but you must specify at least a client_id that exists in the parent table.
mysql> insert into items (client_id) values (1);
Query OK, 1 row affected (0.00 sec)
I also notice that your key in vatbands on (code,client_id) is a non-unique key. It should really be a primary key or unique key to be referenced by a foreign key of a child table. In fact, when I test with MySQL 5.7 milestone release, I can't even create the items table because apparently in this regard the new version of MySQL is more strict than older versions. So I had to make your key a primary key. Then the test worked.
The only way I could get this to work is inserting a default using PHP.
INSERT INTO `item` (`client_id`, `code`) VALUES ('1', 'A');
If anyone has any better ways of resolving this please don't hesitate to comment, I would much prefer a pure MySQL method.
If you use:
INSERT INTO `item` (`client_id`, `code`) VALUES ('1', '');
You are not inserting NULL but an empty string.
INSERT INTO `item` (`client_id`, `code`) VALUES ('1', NULL);
will work

FOREIGN KEY references same table's column. Can't insert values

I created table with FOREIGN KEY and can't insert anything.
CREATE TABLE menus (
id int(10),
parent_id int(10),
label varchar(255),
PRIMARY KEY (id),
FOREIGN KEY (parent_id) REFERENCES menus (id)
);
I need FOREIGN KEY to automatically delete children when parent was deleted. This table was successfully created but I can't insert anything.
INSERT INTO `menus` (`parent_id`, `label`)
VALUES ('1', 'label1');
or
INSERT INTO `menus` (`label`)
VALUES ( 'label1');
#1452 - Cannot add or update a child row: a foreign key constraint fails
I really don't want look for any children in php code so I need somehow create simple table with 3 columns and automatically drop all children and they children too.
For all your needs you should take this structure
CREATE TABLE `menus` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`parent_id` int(11) unsigned DEFAULT NULL,
`label` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `fk_parent_menu` (`parent_id`),
CONSTRAINT `fk_parent_menu` FOREIGN KEY (`parent_id`)
REFERENCES `menus` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
SQL Fiddle DEMO
Demo shows inserting and deleting of a parent node
The magic drop part for all children is done by ON DELETE CASCADE
Typically, you will need to allow the 'root' record to have a null parent - i.e. menus.parent_id should be nullable, and the 'root' menu item will have a null parent_id.
i.e.
Change your DDL to:
parent_id int(10) NULL
And then you add your root element with NULL as the parent_id
insert into `menus` (id, `label`, parent_id)
VALUES (1, 'label1', null);
Then you are good to go with child elements:
insert into `menus` (id, `label`, parent_id)
VALUES (2, 'subitem1', 1);
etc.
SQL Fiddle here