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.
Related
I want to create a join table between two or more tables.
The tables are Student, and course.
Join table will be enrolled.
the business rule is that a student can only enroll in one course at a time.
I want to prevent a user from creating additional enrollments after making 1 enrollment in a course.
I am not sure what type of contraint this will be, or if its even possible.
Can anyone help?
thank you
note: I dont think it is possible to create a Primary key as the primary key of another table, ie the studentID of the student table. If i could I would. breaks the rules i think. This would be a foreign key which is not unique.
If the business rule should be ignored, and assume that a student naturally will only enroll in one course at a time.. maybe ill stop worrying...
You could create a unique index for the id_student but this provably will bring problems if a student try to register in other course later. You shoud include the id_course into the unique constraint.
ALTER TABLE table_name ADD CONSTRAINT constraint_name UNIQUE(studentId, course_id)
Other solution could be creating a Trigger.
The trigger should be a "before insert" trigger. This one should serch for information related to the student in the table, if the table doesn´t has information then insert information, else do nothing.
CREATE TRIGGER 'ONE_STUDENT_PER_COURSE'
BEFORE INSERT ON 'Enrollments'
FOR EACH ROW
BEGIN
DECLARE student_id INT;
SELECT n.id_student INTO student_id
FROM table_enrollments n
`IF student_id IS NULL THEN
/* I DON´T REALLY KNOW EXACTLY THE SINTAXIS FOR INSERTING DATA OF THE BEFORE INSERT FOR YOU VERSION OF MYSQL
BUT TRY THIS ONE
*/
INSERT INTO table_enrollments (student_id, course_id) SELECT student_id, course_id FROM inserted
END IF;
END; $$`
You can create unique index in join table.
CREATE UNIQUE INDEX index_name
ON your_join_table (studentId);
Each table can have a primary key. Two tables can have the same primary key defined. (But the implementation depends on the Entity Relationship Model, what we've discovered about the entities and relationships between the entities.
Based on the information provided in the question, a possible implementation of an enrollment table:
CREATE TABLE current_enrollment
( student_id INT UNSIGNED NOT NULL COMMENT 'pk, fk ref student.id'
, course_id INT UNSIGNED NOT NULL COMMENT 'pk, fk ref course.id'
, PRIMARY KEY (student_id, course_id)
, CONSTRAINT FK_currrent_enrollment_student FOREIGN KEY ( student_id )
REFERENCES student (id) ON UPDATE CASCADE ON DELETE RESTRICT
, CONSTRAINT FK_currrent_enrollment_course FOREIGN KEY ( course_id )
REFERENCES course (id) ON UPDATE CASCADE ON DELETE RESTRICT
)
The datatypes of the foreign key columns must match the datatypes of the referenced columns; in this example, i've assumed primary key columns id in both student and course, defined as datatype INT UNSIGNED
In this example, the PRIMARY KEY constraint enforces a unique constraint on the combination of (student_id,course_id). An attempt to insert a second enrollment (same student in the same course) would be a duplicate row, and that would throw a constraint violation, preventing the row from being added.
If enrollment turns out to be an entity in the model, with its own attributes, I'd opt to add a separate id column as a surrogate primary key, with a unique constraint on (student_id,course_id)
CREATE TABLE current_enrollment
( id INT UNSIGNED NOT NULL COMMENT 'pk'
, student_id INT UNSIGNED NOT NULL COMMENT 'fk ref student.id'
, course_id INT UNSIGNED NOT NULL COMMENT 'fk ref course.id'
, enrollment_dt DATETIME
, status VARCHAR(8)
, approval_by VARCHAR(8)
, PRIMARY KEY (id)
, CONSTRAINT current_enrollment_UX1 UNIQUE KEY (student_id, course_id)
, CONSTRAINT FK_currrent_enrollment_student FOREIGN KEY ( student_id )
REFERENCES student (id) ON UPDATE CASCADE ON DELETE RESTRICT
, CONSTRAINT FK_currrent_enrollment_course FOREIGN KEY ( course_id )
REFERENCES course (id) ON UPDATE CASCADE ON DELETE RESTRICT
)
Scenario:
Parent table | id primary key, message_p
Child table | id primary key, parent_id foreign key, message_c
I had 1 row of data in the parent table and 2 rows of data in the child table. I wanted to test constraints that an FK relationship enforces. I then attempted to remove the foreign key from the child table so that evene though the child table had 2 rows, I could then go ahead and remove the parent row:
alter table child
drop foreign key parent_id
I then got the following error:
[1091 - Can't DROP 'parent_id'; check that column/key exists]
Notes:
show create table child
CREATE TABLE `track` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`member_id` int(11) NOT NULL,
`title` varchar(50) DEFAULT NULL,
`artist` varchar(50) DEFAULT 'TBA',
`album` varchar(50) DEFAULT 'TBA',
`genre` varchar(50) DEFAULT 'TBA',
`dance_style` varchar(50) DEFAULT 'TBA',
PRIMARY KEY (`id`),
KEY `member_id` (`member_id`),
CONSTRAINT `track_ibfk_1` FOREIGN KEY (`member_id`) REFERENCES `member` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
Am I missing something in my query or the general understanding about FK's?
You are trying to delete the Foreign Key Constraint by column name, that's why your code doesn't work.
First query your foreign key constraint name (using show create table child as you did show the key name, something like track_ibfk_1
If you tried out everything as commented (assuming correct table names, constraint names, ...), I see no reason why it should not work.
If you have, however, other tables that hold foreign keys to parent (or 'member'), maybe that these constraints block removal of parent entries?
Anyway, here is an example showing that dropping a foreign key actually works:
drop table if exists testchild;
drop table if exists test;
create table test(
id int primary key,
name varchar(50)
);
create table testchild(
childid int primary key,
reftotest int,
constraint reftotest_FK foreign key (reftotest) references test(id)
);
insert into test values (1, 'Jack'), (2, 'Sam');
insert into testchild values (1, 1), (2, 2), (3, 1);
insert into testchild values (4,5); # will fail
delete from test where id = 1; # will fail
alter table testchild drop foreign key reftotest_FK;
insert into testchild values (4,5); # will not fail any more
delete from test where id = 1; # will not fail any more
I have some sport facilities that have fields that one can play 5x5 football in them. I am trying to make a simple reservation system for them.
My problem is that some fields combine and make bigger fields that the manages of the facilities want to treat them as their own entities (makes sense, if they book them like that why not).
Let's give an actual example.
We have facility FA. They have 3 5x5 fields one next to another, let's call then sa, sb, sc and any two of them can combine to make a 7x7 field, let's call it dd and all three to make a 10x10 field, let's call it te.
This happens with the other facilities as well but this is the more extreme case.
I have been trying to think how to model the tables for the fields when I make the reservation and deal with it but I am not sure.
One solution I have is to have a table for the fields
CREATE TABLE IF NOT EXISTS field (
id SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
arena_id SMALLINT(4) UNSIGNED NOT NULL,
internal_id TINYINT(3) UNSIGNED NOT NULL,
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (id),
UNIQUE (arena_id, internal_id),
CONSTRAINT fk_field_arena_id FOREIGN KEY (arena_id) REFERENCES arena(id) ON UPDATE CASCADE ON DELETE CASCADE
)
;
And theh have a one to one or zero relationship with another table
CREATE TABLE IF NOT EXISTS field_component (
field_id SMALLINT(5) UNSIGNED NOT NULL,
component SMALLINT(5) UNSIGNED NOT NULL,
PRIMARY KEY (field_id, component),
CONSTRAINT fk_field_component_field_id FOREIGN KEY (field_id) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_field_component_field_id2 FOREIGN KEY (component) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE
)
;
that will have entries with the fields that comprise a component field. An entry will exist here only when the flag is_composite in the field table is true.
A simpler solution I was thinking that is a bit more manual, was instead of having the second table and the flag, to just have a string column where I put the ids of the fields that make the composite field as a comma separated list.
On a separate note, I was thinking of moving the flag is composite to a third table called field_info that I might have one to one relationship with the field and will contain information about each field. i.e. size, material of the ground, if it's composite or not, notes about it etc.
Any thought suggestions, criticism, alternatives are welcome.
I would consider the following, that ensures in the composite table field_component that the child and composite arenas are at least the same. Note that InnoDB check constraints are not enforced.
Point #1: The is_composite quality in the following for the field_component implicitly pointing back to something that is truly a composite is not enforced. It could be with more compositing (meaning more tables).
Point #2: The datatypes should not be overly engineered into small and tiny INTs at this stage or perhaps ever. Especially if new to mysql.
Point #3: The FK relationships have the tendency to create KEYS for you automatically when not present in the child table. The unique key that we explicitly have in field_component effectly serves two purposes. It enforces non-dupes, and it serves as the index used where an FK auto-gen one would have been generated. Another one is generated automatically as can be seen in show create table. So, our UNIQUE KEY serves a few purposes there.
CREATE TABLE IF NOT EXISTS field (
id SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
arena_id SMALLINT(4) UNSIGNED NOT NULL,
internal_id TINYINT(3) UNSIGNED NOT NULL,
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (id),
UNIQUE (arena_id, internal_id),
CONSTRAINT fk_field_arena_id FOREIGN KEY (arena_id) REFERENCES arena(id) ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS field_component (
field_id SMALLINT(5) UNSIGNED NOT NULL,
component SMALLINT(5) UNSIGNED NOT NULL,
PRIMARY KEY (field_id, component),
CONSTRAINT fk_field_component_field_id FOREIGN KEY (field_id) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_field_component_field_id2 FOREIGN KEY (component) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE
);
TweakA:
CREATE SCHEMA TweakA;
USE TweakA;
-- drop table arena
CREATE TABLE IF NOT EXISTS arena
( id INT PRIMARY KEY,
aName varchar(200) NOT NULL
);
-- drop table field
CREATE TABLE IF NOT EXISTS field
( id INT AUTO_INCREMENT PRIMARY KEY,
arena_id INT NOT NULL, -- like the Arena #
internal_id INT NOT NULL, -- 1, 2, 3 for the field #
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
friendly_name VARCHAR(100) NOT NULL,
UNIQUE KEY (arena_id, internal_id),
CONSTRAINT fk_field_arena_id FOREIGN KEY (arena_id) REFERENCES arena(id) ON UPDATE CASCADE ON DELETE CASCADE
);
-- drop table field_component
CREATE TABLE IF NOT EXISTS field_component
( id INT AUTO_INCREMENT PRIMARY KEY,
arena_id INT NOT NULL,
child_internal_id INT NOT NULL,
composite_internal_id INT NOT NULL,
-- The following UK will pick up part of what I will explain in the Narrative
UNIQUE KEY `unq_arena_comp_child` (arena_id,child_internal_id,composite_internal_id),
CONSTRAINT fk_field_child_field_id FOREIGN KEY (arena_id,child_internal_id)
REFERENCES field(arena_id, internal_id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_field_composite_field_id FOREIGN KEY (arena_id,composite_internal_id)
REFERENCES field(arena_id, internal_id) ON UPDATE CASCADE ON DELETE CASCADE
-- note that InnoDB check constraints are not effective
);
-- Note, look at output from the following
-- show create table field_component; -- this shows the auto-gen of 1 key due to FK
--
-- The following block is a Helper block during testing
-- Truncate in reverse order:
-- TRUNCATE TABLE field_component;
-- TRUNCATE TABLE field;
-- TRUNCATE TABLE arena;
-- test data load:
INSERT arena(id,aName) VALUES (1,'Boston Arena, North Shore');
INSERT field(arena_id,internal_id,is_composite,friendly_name) VALUES
(1,1,FALSE,'sa'),
(1,2,FALSE,'sb'),
(1,3,FALSE,'sc'),
(1,4,TRUE,'dab'),
(1,5,TRUE,'dac'),
(1,6,TRUE,'dbc'),
(1,7,TRUE,'abc');
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(1,1,4),
(1,2,4),
(1,1,5),
(1,3,5),
(1,2,6),
(1,3,6),
(1,1,7),
(1,2,7),
(1,3,7); -- SUCCESS
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(2,2,4); -- will fail, as expected
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(1,72,4); -- will fail, as expected
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(1,1,444); -- will fail, as expected
show create table field_component;
-- the above will exhibit the AUTO_INCREMENT gap anomoly due to the above
-- expected failed inserts, setting AI=13 or so
DROP SCHEMA TweakA;
I wrote an FK Enforces Composite Relationship answer that got a little complicated. Yours can go that route depending on the level of DB Enforcement you are looking for.
Also see the MySQL Using FOREIGN KEY Constraints concerning auto-gen of KEYS due to FK relationships as mentioned in Point #3.
So, this answer could just keep growing as you work thru enforcement. Or do it client side. If it were me, I would do it DB Enforcement.
Regardless, as mentioned in comments, don't store CSV values in a column.
If there is possible insert of the same row in table I want to ignore this one. Here the table with foreign key game_id and 2 fields step_num and cell, they could differ and should differ. Cell should be 0-8 and always different for game_id. Step_num the same, but 1-9. But I can't set unique, because the could be the same, but in different game_id's.
Here is part of the script:
create table games(
id int not null auto_increment primary key,
name varchar(255) not null unique ,
status varchar(255) not null,
createDate DATETIME
);
create table steps(
game_id int not null ,
step_num int not null,
cell int not null,
constraint cell_unique on games(id),
constraint chk_number check(step_number > 0 and step_number < 10),
constraint fk_game foreign key (game_id) references games(id) on delete cascade,
constraint chk_cell check(cell >= 0 and cell <= 8)
);
INSERT IGNORE didn't worked adn INSERT ON DUPLICATE KEY I think not what I need. If I try Insert (2, 2, 2) several times, it should ignore. I prevent this actions on the back-end side, but want, that db was correct and automatically prevent from similar actions.
What are the ways to do it correctly?
You have 2 options:
Define composite primary key using ( game_id, step_num, cell ). This restricts repeated row data like 2, 2, 2.
Define a before insert trigger that checks duplicates and
to restrict, updates the same row with same values, affecting no rows.
And as per my knowledge check constraints have no effect in MySQL.
i want to change the ids of a table to some other unique value.
This is a simplified example:
CREATE TABLE IF NOT EXISTS test (
id int(11) NOT NULL,
reverse_id int(11) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY reverse_id (reverse_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO test (id, reverse_id) VALUES ('1', '2'), ('2', '1');
UPDATE test SET id = reverse_id;
# Duplicate entry '2' for key 'PRIMARY'
I am looking for a command that checks only at the end of the UPDATE for the uniqueness of the id elements.
[I know that I can create a second row and change the status of this row to primary, then i can update the ids and reset the primary status, but i want to have one command, without adding or changing other rows, tables]
This is not possible with MySQL as far as I know.
It neither evaluates constraint on statement level (it does that on row level while processing) nor does it allow you to define them to be deferred (so the constraint would be evaluated at commit time).
The only option I can see if you want to "renumber" your primary key: drop the primary key, renumber the ids then re-create the primary key.
What if you create the table having reverse_id as its primary key? - that is unique in your definition so it is a valid candidate for primary key.
Primary keys are unique by definition - this constraint is stopping you from achieving what you want