How to Use One General Table for Relationships - mysql

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.

Related

Foreign Key Reference Composite Primary Key

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.

Foreign key for multiple tables and columns?

I have been learning about Foreign keys and I wanted to know if the way I used it is correct in the example below:
CREATE TABLE user(
id INT(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(20) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE items(
i_id INT(11) NOT NULL AUTO_INCREMENT,
name TINYTEXT NOT NULL,
price DECIMAL(8,2) NOT NULL,
PRIMARY KEY (i_id)
);
CREATE TABLE user_purchase(
i_id INT(11) NOT NULL,
name TINYTEXT NOT NULL,
id INT(11) NOT NULL,
FOREIGN KEY (i_id) REFERENCES items(i_id),
FOREIGN KEY (name) REFERENCES items(name),
FOREIGN KEY (id) REFERENCES user(id)
);
Thanks
Now, how can I get maximum information from just the Foreign Key if I use, let's say, PHP?
You don't have to include item name in both tables. This is called a denormalized solution.
You should have it only in the items table and only refer to the id, then if you need the name also you can join it based on the primary key(id).
Otherwise it is totally OK in my opinion.
CREATE TABLE user(
id INT(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(20) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE items(
i_id INT(11) NOT NULL AUTO_INCREMENT,
name TINYTEXT NOT NULL,
price DECIMAL(8,2) NOT NULL,
PRIMARY KEY (i_id)
);
CREATE TABLE user_purchase(
i_id INT(11) NOT NULL,
name TINYTEXT NOT NULL,
id INT(11) NOT NULL,
FOREIGN KEY (i_id) REFERENCES items(i_id),
FOREIGN KEY (id) REFERENCES user(id)
);
Sometimes when performance is critical you have to use denormalized tables. It can be much faster.
Normalization is important to avoid different anomalies.
If you have tables in a high level normal form then your tables won't be redundant and wont have these anomalies. For example if you have something stored in multiple locations you have to look after to keep all the redundant data up to date. This gives you a chance to do this incorrectly and end up having different anomalies.
In your situation having a foreign key helps you to keep data integrity but without a foreign key for the name you would be able to have items with names in the purchases that are not present in the items table.
This is a kind of anomaly.
There are many kinds of this, best to avoid them as long as you can.
Read more here about anomalies
In some cases you must denoramalize. So store some data redundantly because of performance issues. This way you can save some join operations that could consume much time.
Details of normalization are covered by topics of different normal forms:
NF0, NF1, NF2, NF3 and BCNF
Normal forms in detail
For further details about the mathematical foundations of loseless decomposition to higher normal forms see "Functional dependencies". This is going to help you understand why you can keep the ids "redundant". Virtually they are necessary redundancy, since you need them in order to be able to later rebuild the original dataset. This is going to be the definition for the different normal forms. What level of this redundancy is allowed?
Functional Dependencies
you've got an i_id as a primary key, you don't need to set up a name as a foreign key. and btw foreign key has to reference to unique attribute.
CREATE TABLE `user`(
id INT(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(20) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE items(
i_id INT(11) NOT NULL AUTO_INCREMENT,
`name` TINYTEXT UNIQUE NOT NULL,
price DECIMAL(8,2) NOT NULL,
PRIMARY KEY (i_id)
);
CREATE TABLE user_purchase(
i_id INT(11) NOT NULL,
`name` TINYTEXT NOT NULL,
id INT(11) NOT NULL,
FOREIGN KEY (i_id) REFERENCES items(i_id),
FOREIGN KEY (id) REFERENCES `user`(id)
);

MySQL creating a table (errno 150)

I would like to ask something that troubles me many many days...
Here is what I mean:
I create these two tables:
CREATE TABLE IF NOT EXISTS journal (
issn varchar(20) NOT NULL,
j_title varchar(100) NOT NULL,
j_publisher varchar(30) NOT NULL,
PRIMARY KEY (issn)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS volume (
volume_no int(11) NOT NULL,
issn varchar(20) NOT NULL,
year int(11) NOT NULL,
PRIMARY KEY (issn,volume_no),
FOREIGN KEY (issn) REFERENCES journal(issn)
) ENGINE=InnoDB;
When I try to create this:
CREATE TABLE IF NOT EXISTS issue (
issue_no int(11) NOT NULL,
issue_pages varchar(10) NOT NULL,
issue_date varchar(10) NOT NULL,
issn varchar(20) NOT NULL,
volume_no int(11) NOT NULL,
PRIMARY KEY (issue_no,issn,volume_no),
FOREIGN KEY (issn) REFERENCES journal(issn),
FOREIGN KEY (volume_no) REFERENCES volume(volume_no)
) ENGINE=InnoDB;
it throws an error (errno 150)
The error is in the foreign key volume_no.
Without FOREIGN KEY (volume_no) REFERENCES volume(volume_no)
the table is created without a problem.... I can't explain what's going on... I have seen it many times again and again but nothing!! Does anybody know what's going on?
Thanks in advance!!
I could see that the foreign key doesnt include issn but which is actually included in primary key for volumn table.
CREATE TABLE IF NOT EXISTS issue ( issue_no int(11) NOT NULL,
issue_pages varchar(10) NOT NULL,
issue_date varchar(10) NOT NULL,
issn varchar(20) NOT NULL,
volume_no int(11) NOT NULL,
PRIMARY KEY (issue_no,issn,volume_no),
FOREIGN KEY (issn,volume_no) REFERENCES volume(issn,volume_no) ) ENGINE=InnoDB;
Look at the below sql fiddle.
http://sqlfiddle.com/#!2/55a63
maybe volume_no needs to be UNSIGNED
FOREIGN keys must reference a PRIMARY or a UNIQUE key in the parent table.
You need only one Foreign Key at table issue, not two. And it should reference the Primary Key of volume:
CREATE TABLE IF NOT EXISTS issue (
issue_no int(11) NOT NULL,
issue_pages varchar(10) NOT NULL,
issue_date varchar(10) NOT NULL,
issn varchar(20) NOT NULL,
volume_no int(11) NOT NULL,
PRIMARY KEY (issn, volume_no, issue_no),
FOREIGN KEY (issn, volume_no)
REFERENCES volume(issn, volume_no)
) ENGINE=InnoDB;
If the PK of the parent table is more than one field, the order of the fields in the FK must be the same as the order in the PK.
issue: FOREIGN KEY (issn, volume_no) REFERENCES volume(issn, volume_no)
These conditions must be satisfied to not get error 150:
The two tables must be ENGINE=InnoDB.
The two tables must have the same charset.
The PK column(s) in the parent table and the FK column(s) must be the same data type.
The PK column(s) in the parent table and the FK column(s), if they have a define collation type, must have the same collation type;
If there is data already in the foreign key table, the FK column value(s) must match values in the parent table PK columns.
source: MySQL Creating tables with Foreign Keys giving errno: 150
I had about the same issue with my database. It wasn't about the definition of the foreign key, actually it was the definition of the primary key field.
CREATE TABLE tblProcesses (
fldProcessesID SMALLINT(5) UNIQUE NOT NULL AUTO_INCREMENT ,
CREATE TABLE tblProcessesMessage (
fldProcesses TEXT NOT NULL,
fldProcessesID VARCHAR(15) NOT NULL DEFAULT ,
The primary key in tblProcesses (fldProcessesID) did not have the UNSIGNED keyword while the foreign key in tblProcessesMessage (fldProcessesID) had the UNSIGNED keyword. This keyword was causing the problem - inconsistent type of field. So i added the UNSIGNED keyword to fldProcessesID in tblPreocesses:
CREATE TABLE tblProcesses (
fldProcessesID SMALLINT(5) UNSIGNED UNIQUE NOT NULL AUTO_INCREMENT,
I hope that this will help you solve your problems.
Best regards,
Nicholas

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

MySQL dilemma: composite unique key across tables

I am attempting to implement an "extension" table structure for some stats that I am gathering from multiple sources.
My "parent" table looks something like this:
`test_parent` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`date` date NOT NULL,
`actions` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
)
My first "child" table looks somethinglike this (eventually I will have a child table for each source):
`test_child` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`test_parent_id` int(11) unsigned NOT NULL,
`external_id` int(11) NOT NULL,
`external_actions` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `test_parent_id` (`test_parent_id`)
)
CONSTRAINT `test_child_ibfk_1` FOREIGN KEY (`test_parent_id`) REFERENCES `test_parent` (`id`)
All this will work fine in my implementation (I will be using Java/Hibernate); however, for the first child table I will need a composite unique key for external_id and date. I know that I cannot have a composite unique key across tables. I would rather not have one table to store all of the stats because the actual analytics I am collecting can vary greatly by source. I would be more open to getting rid of the "parent" table.
Is there some other way I can look at this problem? I am hoping to avoid using triggers to enforce uniqueness, if possible.
You need the date in the child table if you want to establish a unique constraint on it with external_id. You can also have date live in the parent table, and reference it via the foreign key. That will allow you to support date differently by other child tables in the future.
CREATE TABLE `test_parent` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`date` date NOT NULL,
`actions` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`, `date`)
);
CREATE TABLE `test_child` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`test_parent_id` int(11) unsigned NOT NULL,
`date` date NOT NULL,
`external_id` int(11) NOT NULL,
`external_actions` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `test_parent_id` (`external_id`,`date`),
CONSTRAINT `test_child_ibfk_1` FOREIGN KEY (`test_parent_id`, `date`)
REFERENCES `test_parent` (`id`,`date`)
);
Move the date field to the child table and declare a unique key:
ALTER TABLE child ADD UNIQUE INDEX parent_date (parent_id, `date`);