Foreign Key Reference Composite Primary Key - mysql

The database is going to store information about hardware devices and their gathered data. I created a devices table to store the available hardware devices:
CREATE TABLE IF NOT EXISTS `devices` (
`deviceID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`deviceType` int(10) unsigned NOT NULL,
`updateFrequency` int(10) unsigned NOT NULL,
PRIMARY KEY (`deviceID`,`deviceType`)
)
The deviceID will correspond to a real hardware id (from 1 to 12). Since there are two types of hardware devices I thought it would be appropriate to create a deviceType which will be either 0 or 1 depending on which hardware device and make a composite primary key.
To store that data I created another table.
CREATE TABLE IF NOT EXISTS `data` (
`dataID` int(11) unsigned NOT NULL AUTO_INCREMENT,
`deviceID` int(11) unsigned NOT NULL,
`payload` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`dataID`),
KEY `fk_data_devices` (`deviceID`),
CONSTRAINT `fk_data_devices`
FOREIGN KEY (`deviceID`)
REFERENCES `devices` (`deviceID`)
ON DELETE CASCADE
)
The problem is obviously that I cannot reference the composite key in one column inside data. Would it make sense to create an additional column inside data for deviceType and foreign key reference that as well or would it make more sense to assign deviceID and deviceType inside devices to another id and reference that inside data?
Thanks in advance!

You have a parent table with a composite primary key on columns (deviceID, deviceType). If you want to create a child table, you would need to:
create one column in the child table for each column that is part of the primary key in the parent table (deviceID, deviceType)
create a composite foreign key that references that tuple of columns to the corresponding column tuple in the parent table
Consider:
CREATE TABLE IF NOT EXISTS `data` (
`dataID` int(11) unsigned NOT NULL AUTO_INCREMENT,
`deviceID` int(11) unsigned NOT NULL,
`deviceType` int(10) unsigned NOT NULL,
`payload` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`dataID`),
CONSTRAINT `fk_data_pk`
FOREIGN KEY (`deviceID`, `deviceType`)
REFERENCES `devices` (`deviceID`, `deviceType`)
ON DELETE CASCADE
);
NB: creating a composite foreign key is functionally different than creating two foreign keys, each pointing at one of the columns in the parent table.
Given this data in the parent table:
deviceID deviceType
1 0
2 1
If you create a separated foreign key on each column, they will allow you to insert a record in the child table with values like (1, 1), or (2, 0). The composite foreign key will not allow it, since these specific tuples do not exist in the source table.

Related

Composite index for a relationship table

I have the following tables:
CREATE TABLE `students` (
`student_id` int NOT NULL AUTO_INCREMENT,
`student_name` varchar(40) NOT NULL DEFAULT '',
PRIMARY KEY (`student_id`)
);
CREATE TABLE `courses` (
`course_id` int NOT NULL AUTO_INCREMENT,
`course_name` varchar(40) NOT NULL DEFAULT '',
PRIMARY KEY (`course_id`)
);
CREATE TABLE `students_courses` (
`id` int NOT NULL AUTO_INCREMENT,
`student_id` int NOT NULL DEFAULT '0',
`course_id` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
Here, am using the students_courses table to store the relationships between the Students and Courses. Because one Student can enroll to more than one Course.
The doubt am having is, what should be indexed and how for that table.
1) Shall I index student_id and course_id separately like this:
CREATE TABLE `students_courses` (
`id` int NOT NULL AUTO_INCREMENT,
`student_id` int NOT NULL DEFAULT '0',
`course_id` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY (`student_id`),
KEY (`course_id`)
);
2) Or, create a composite index for both the student_id and course_id
CREATE TABLE `students_courses` (
`id` int NOT NULL AUTO_INCREMENT,
`student_id` int NOT NULL DEFAULT '0',
`course_id` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY (`student_id`, `course_id`)
);
3) If going with composite key, should I remove that that id primary key and make the PRIMARY KEY composite?
I will be using this relationship table during JOIN mainly. So am a bit confused here.
Let's say we stick to using the Auto Increment id column as Primary Key. Now, we will also need to ensure that the data is consistent, i.e., there are no duplicate rows for a combination of (student_id, course_id) values. So, we will need to either handle this in application code (do a select every time before insert/update), or we can fix this thing structurally by defining a Composite UNIQUE constraint on (student_id, course_id).
Now, a Primary Key is basically a UNIQUE NOT NULL Key. If you look at your table definition, this newly defined UNIQUE constraint is basically a Primary Key only (because the fields are NOT NULL as well). So, in this particular case, you don't really need to use a Surrogate Primary key id.
The difference in overheads during random DML (Insert/Update/Delete) will be minimal, as you would also have similar overheads when using a UNIQUE index only. So, you can rather define a Natural Primary Composite Key (student_id, course_id):
-- Drop the id column
ALTER TABLE students_courses DROP COLUMN id;
-- Add the composite Primary Key
ALTER TABLE students_courses ADD PRIMARY(student_id, course_id);
Above will also enforce the UNIQUE constraint on the combination of (student_id, course_id). Moreover, you will save 4 bytes per row (size of int is 4 bytes). This will come handly when you would have large tables.
Now, while Joining from students to students_courses table, above Primary Key will be a sufficient index. However, if you need to Join from courses to students_courses table, you will need another key for this purpose. So, you can define one more key on course_id as follows:
ALTER TABLE students_courses ADD INDEX (course_id);
Moreover, you should define Foreign Key constraints to ensure data integrity:
ALTER TABLE students_courses ADD FOREIGN KEY (student_id)
REFERENCES students(student_id);
ALTER TABLE students_courses ADD FOREIGN KEY (course_id)
REFERENCES courses(course_id);

Adding continual data to a users ID? [duplicate]

I have the following tables:
CREATE TABLE `students` (
`student_id` int NOT NULL AUTO_INCREMENT,
`student_name` varchar(40) NOT NULL DEFAULT '',
PRIMARY KEY (`student_id`)
);
CREATE TABLE `courses` (
`course_id` int NOT NULL AUTO_INCREMENT,
`course_name` varchar(40) NOT NULL DEFAULT '',
PRIMARY KEY (`course_id`)
);
CREATE TABLE `students_courses` (
`id` int NOT NULL AUTO_INCREMENT,
`student_id` int NOT NULL DEFAULT '0',
`course_id` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
Here, am using the students_courses table to store the relationships between the Students and Courses. Because one Student can enroll to more than one Course.
The doubt am having is, what should be indexed and how for that table.
1) Shall I index student_id and course_id separately like this:
CREATE TABLE `students_courses` (
`id` int NOT NULL AUTO_INCREMENT,
`student_id` int NOT NULL DEFAULT '0',
`course_id` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY (`student_id`),
KEY (`course_id`)
);
2) Or, create a composite index for both the student_id and course_id
CREATE TABLE `students_courses` (
`id` int NOT NULL AUTO_INCREMENT,
`student_id` int NOT NULL DEFAULT '0',
`course_id` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY (`student_id`, `course_id`)
);
3) If going with composite key, should I remove that that id primary key and make the PRIMARY KEY composite?
I will be using this relationship table during JOIN mainly. So am a bit confused here.
Let's say we stick to using the Auto Increment id column as Primary Key. Now, we will also need to ensure that the data is consistent, i.e., there are no duplicate rows for a combination of (student_id, course_id) values. So, we will need to either handle this in application code (do a select every time before insert/update), or we can fix this thing structurally by defining a Composite UNIQUE constraint on (student_id, course_id).
Now, a Primary Key is basically a UNIQUE NOT NULL Key. If you look at your table definition, this newly defined UNIQUE constraint is basically a Primary Key only (because the fields are NOT NULL as well). So, in this particular case, you don't really need to use a Surrogate Primary key id.
The difference in overheads during random DML (Insert/Update/Delete) will be minimal, as you would also have similar overheads when using a UNIQUE index only. So, you can rather define a Natural Primary Composite Key (student_id, course_id):
-- Drop the id column
ALTER TABLE students_courses DROP COLUMN id;
-- Add the composite Primary Key
ALTER TABLE students_courses ADD PRIMARY(student_id, course_id);
Above will also enforce the UNIQUE constraint on the combination of (student_id, course_id). Moreover, you will save 4 bytes per row (size of int is 4 bytes). This will come handly when you would have large tables.
Now, while Joining from students to students_courses table, above Primary Key will be a sufficient index. However, if you need to Join from courses to students_courses table, you will need another key for this purpose. So, you can define one more key on course_id as follows:
ALTER TABLE students_courses ADD INDEX (course_id);
Moreover, you should define Foreign Key constraints to ensure data integrity:
ALTER TABLE students_courses ADD FOREIGN KEY (student_id)
REFERENCES students(student_id);
ALTER TABLE students_courses ADD FOREIGN KEY (course_id)
REFERENCES courses(course_id);

How to Use One General Table for Relationships

What I have now is table that is "hardcoded" to have a relationship between two of the same table with two foreign keys to that table.
Before 'Relationship' table:
CREATE TABLE IF NOT EXISTS `Item_To_Item` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`item1_id` INT(11) NOT NULL,
`item2_id` INT(11) NOT NULL,
`relationship` ENUM('requires', 'mutually_requires', 'required_by', 'relates', 'excludes') NULL DEFAULT NULL,
`description` VARCHAR(1000) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_Item1_Id`
FOREIGN KEY (`item1_id`)
REFERENCES `Item` (`id`)
CONSTRAINT `fk_Item2_Id`
FOREIGN KEY (`item2_id`)
REFERENCES `Item` (`id`)
So before this had a double many to one reference on the item table to fill the two foreign keys.
There is a need now to expand this relationship to be more general between tables in the db (Enum, Tag, feature etc). So that now Items can relate to items, items can relate to tags, tags can relate to tags etc with the enum relationship value.
What I am thinking for the general table is adding a type table, so each item, tag, etc can be identified, and then restructuring the relationship table to be something like this:
CREATE TABLE IF NOT EXISTS `Relationship` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`relater_id` INT(11) NOT NULL,
`relatee_id` INT(11) NOT NULL,
`relationship` ENUM('requires', 'mutually_requires', 'required_by', 'relates', 'excludes') NULL DEFAULT NULL,
`description` VARCHAR(1000) NULL DEFAULT NULL,
`relater_type_id` INT(11) NULL,
`relatee_type_id` INT(11) NULL,
PRIMARY KEY (`id`),
INDEX `fk_Relatee_Id` (`relatee_id` ASC),
INDEX `fk_Relater_Id` (`relater_id` ASC),
CONSTRAINT `fk_Relater_Id`
FOREIGN KEY (`relater_id`)
CONSTRAINT `fk_Relatee_Id`
FOREIGN KEY (`relatee_id`)
So that now you can identify what type of items are being related by the type_id and table and this can be opened up so any two table ids can go into the Relater and Relatee foreign key columns.
The problem is that I do not know how to have such generality with foreign keys. I believe they can only reference one table so I am not sure how to do what I want with a general key reference. Also, I can see a problem with bidirectional relationships where A mutually Requires B and B mutually Requires A being redundant data. I could block this redundancy in my application, but I would constantly have to check for two sided A to B || B to A. I was wondering the best way to accomplish what I am trying to do. Thank you.
Edit: Maybe using some kind of base type for my (item, feature, tag) could help me?
Edit: I don't think the answer is as simple as inheritance. At least from what I can tell. My problem is that I want to relate two general items no matter the type. I don't want 20 columns that have to be null because it is not that specific type. I just want to be able to pass two ids and therefore two type_ids into the relationship so I can relate any two objects.
One potential solution is to implement object_type and object_index tables:
CREATE TABLE object_type (
`object_type_id` int(11) NOT NULL AUTO_INCREMENT,
`object_type` varchar(30) NOT NULL,
PRIMARY KEY (`object_type_id`),
UNIQUE (`object_type`));
CREATE TABLE object_index (
`object_id` int(11) NOT NULL AUTO_INCREMENT,
`object_type_id` int(11) NOT NULL,
PRIMARY KEY (`object_id`),
UNIQUE (`object_type_id`, `object_id`));
and define your relations against that table only.
CREATE TABLE IF NOT EXISTS `Relationship` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`relater_id` INT(11) NOT NULL,
`relatee_id` INT(11) NOT NULL,
`relationship` ENUM('requires', 'mutually_requires', 'required_by', 'relates', 'excludes') NULL DEFAULT NULL,
`description` VARCHAR(1000) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `fk_Relatee_Id` (`relatee_id` ASC),
INDEX `fk_Relater_Id` (`relater_id` ASC),
CONSTRAINT `fk_Relater_Id`
FOREIGN KEY (`relater_id`)
references object_index (`object_id`),
CONSTRAINT `fk_Relatee_Id`
FOREIGN KEY (`relatee_id`)
references object_index (`object_id`));
Next each of your object tables are defined such they relate back to the object_index on the unique (object_type_id, object_id) tuple. In this example each tables default and check constrained object_type_id should be unique:
CREATE TABLE table1 (
`object_id` int(11) NOT NULL,
`object_type_id` int(11) NOT NULL DEFAULT 1 CHECK (object_type = 1),
`col1` varchar(4),
PRIMARY KEY (`object_id`),
CONSTRAINT fk_t1_ob_idx
FOREIGN KEY (`object_type_id`, `object_id`)
REFERENCES object_index (`object_type_id`, `object_id`));
In MySQL 5.6 and above you could define a virtual/computed column on each table to match object_type_id from the object_index instead of a stored physical column.
In MySQL 8.0 and above you might be able to define a function based index on each table that includes the discriminator object_type_id as an expression instead of as a physical or virtual column in the table.

Why setting the key constraints on column removes "NOT NULL" from it (MySQL)?

I have the code to create a table:
DROP TABLE IF EXISTS `database`.`creatures_kinds` ;
CREATE TABLE IF NOT EXISTS `database`.`creatures_kinds` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`parent_id` INT UNSIGNED NULL,
`name_en` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`, `parent_id`),
INDEX `parent_id` (`parent_id` ASC),
CONSTRAINT `parent`
FOREIGN KEY (`parent_id`)
REFERENCES `database`.`creatures_kinds` (`id`)
ON DELETE RESTRICT
ON UPDATE CASCADE)
ENGINE = InnoDB;
What surprise me is that the newly created table looks like this:
Why the parent_id is set to NOT NULL when I wrote NULL in the CREATE TABLE?
Does it has something to do with the PRIMARY KEY or FOREIGN KEY?
I guess that parent_id is an identifiable relation (it points to more general creature kind, e.g. from pigeon to bird, and it's NULL for top-level creature kinds).
A PRIMARY KEY is a unique index where all key columns must be defined as NOT NULL. If they are not explicitly declared as NOT NULL, MySQL declares them so implicitly (and silently). MySQL Manual

SQL Join Table - Does it require a primary key at all or just unique keys?

I have created an application for the CakePHP framework which uses a join table.
I am unsure as to whether it is neccessary that I need a primary key to uniquley identify each row for the join table, as shown in the first block of SQL.
Do the two fields need to be set as unique keys or can they both be set as primary keys and I remove id as the primary key?
I was also asked why declaring atomic primary keys using a table constraint rather
than a column constraint, does this mean I shouldn't set unique keys for a join table?
CREATE TABLE IF NOT EXISTS `categories_invoices` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL,
`invoice_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `category_id` (`category_id`,`invoice_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=163 ;
I was thinking the solution is possibly to set both keys as unique and remove the primary key as shown here:
CREATE TABLE IF NOT EXISTS `categories_invoices` (
`category_id` int(11) NOT NULL,
`invoice_id` int(11) NOT NULL,
UNIQUE KEY `category_id` (`category_id`,`invoice_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
I did in fact test deleting the primary key 'id' for the join table leaving only 'category_id' and 'invoice_id' and the application still worked. This has left both fields as unique fields within the join table. Is this in fact the correct practice?
You don't need both. The compound unique key can replace the Primary key (unless the Cake framework cannot deal with compound Priamry Keys):
CREATE TABLE IF NOT EXISTS categories_invoices (
category_id int(11) NOT NULL,
invoice_id int(11) NOT NULL,
PRIMARY KEY (category_id, invoice_id)
)
ENGINE = MyISAM
DEFAULT CHARSET = latin1 ;
It's also good to have another index, with the reverse order, besides the index created for the Primary Key:
INDEX (invoice_id, category_id)
If you want to define Foreign Key constraints, you should use the InnoDB engine. With MyISAM you can't have Foreign Keys. So, it would be:
CREATE TABLE IF NOT EXISTS categories_invoices (
category_id int(11) NOT NULL,
invoice_id int(11) NOT NULL,
PRIMARY KEY (category_id, invoice_id),
INDEX invoice_category_index (invoice_id, category_id)
)
ENGINE = InnoDB
DEFAULT CHARSET=latin1 ;
If Cake cannot cope with composite Primary Keys:
CREATE TABLE IF NOT EXISTS categories_invoices (
id int(11) NOT NULL AUTO_INCREMENT,
category_id int(11) NOT NULL,
invoice_id int(11) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY category_invoice_unique (category_id, invoice_id),
INDEX invoice_category_index (invoice_id, category_id)
)
ENGINE = InnoDB
DEFAULT CHARSET=latin1 ;
There is nothing wrong with the second method. It is referred to as a composite key and is very common in database design, especially in your circumstance.
http://en.wikipedia.org/wiki/Relational_database#Primary_keys