MySQL dilemma: composite unique key across tables - mysql

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`);

Related

Interpretation of model diagram for allocation of foreign/primary keys

https://imgur.com/a/HAlSO7z
I am totally confused as to what I should identify as the primary key for the flatpack components table. For the flatpack components, there is a multiplicty relationship with both of the other tables, although intuitively I am leaning toward the component table because that seems more relevant, and the multiplicity is 0 to Many to 1, as opposed to the Many to 1 associated with flatpack components.
I have tried a composite key of both flatpackID and componentNo, but have ran into errors whilst trying to populate the table with sample data.
At present, I have the following tables:
CREATE TABLE `flatpack_apn` (
`flatpackid` INT(11) NOT NULL AUTO_INCREMENT,
`name` TINYTEXT NOT NULL,
`colour` TEXT NULL DEFAULT NULL,
`flatpacktype` ENUM('Office','Kitchen','Bedroom','General') NOT NULL,
`unitprice` DECIMAL(5,2) NULL DEFAULT NULL,
PRIMARY KEY (`flatpackid`)
)
COLLATE='hp8_english_ci'
ENGINE=InnoDB
AUTO_INCREMENT=2217
;
CREATE TABLE `component_apn` (
`componentno` INT(11) NOT NULL AUTO_INCREMENT,
`description` VARCHAR(30) NOT NULL,
PRIMARY KEY (`componentno`)
)
COLLATE='hp8_english_ci'
ENGINE=InnoDB
AUTO_INCREMENT=20
;
For the flatpackcomponents_apn table (the table in contention) I have tentatively put the following code:
CREATE TABLE `flatpackcomponents_apn` (
`flatpackid` INT(11) NOT NULL DEFAULT '12',
`componentno` INT(11) NOT NULL DEFAULT '23',
`quantity` INT(10) NOT NULL,
PRIMARY KEY (`componentno`, `flatpackid`),
INDEX `flatpackid` (`flatpackid`),
INDEX `componentno` (`componentno`),
CONSTRAINT `flatpackcomponents_apn_ibfk_1` FOREIGN KEY (`flatpackid`) REFERENCES `flatpack_apn` (`flatpackid`),
CONSTRAINT `flatpackcomponents_apn_ibfk_2` FOREIGN KEY (`componentno`) REFERENCES `component_apn` (`componentno`)
)
COLLATE='hp8_english_ci'
ENGINE=InnoDB
;

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.

How MySQL one to many and one to one relationships are defined?

I was referring these to Hibernate tuts: 1, 2.
I was not able to understand how one to one and one to many relationships are defined in MySQL tables.
This is SQL for one to many relationship:
CREATE TABLE `Cart` (
`cart_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`total` decimal(10,0) NOT NULL,
`name` varchar(10) DEFAULT NULL,
PRIMARY KEY (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
CREATE TABLE `Items` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`cart_id` int(11) unsigned NOT NULL,
`item_id` varchar(10) NOT NULL,
`item_total` decimal(10,0) NOT NULL,
`quantity` int(3) NOT NULL,
PRIMARY KEY (`id`),
KEY `cart_id` (`cart_id`),
CONSTRAINT `items_ibfk_1` FOREIGN KEY (`cart_id`) REFERENCES `Cart` (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
This is SQL for one to one relationship:
-- Create Transaction Table
CREATE TABLE `Transaction` (
`txn_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`txn_date` date NOT NULL,
`txn_total` decimal(10,0) NOT NULL,
PRIMARY KEY (`txn_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
-- Create Customer table
CREATE TABLE `Customer` (
`txn_id` int(11) unsigned NOT NULL,
`cust_name` varchar(20) NOT NULL DEFAULT '',
`cust_email` varchar(20) DEFAULT NULL,
`cust_address` varchar(50) NOT NULL DEFAULT '',
PRIMARY KEY (`txn_id`),
CONSTRAINT `customer_ibfk_1` FOREIGN KEY (`txn_id`) REFERENCES `Transaction` (`txn_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
If eyes are ok, I dont see any difference between two. Is it like this relationship cardinality constraints are implemented only at hibernate level and are not enforced by database? Or my eyes are missing something?
It's actually possible to define 1:1 relationships in SQL. There are two ways:
The child table has the same PK as the parent table, with the same values. This column is also an FK to the parent table.
The child table has a different PK. It also has a FK that points to the parent table, and this FK has a UNIQUE constraint.
If you noticed, in both cases the FK is UNIQUE (it's the PK, or has a UNIQUE constraint), and that's the key aspect. It's not possible the create a second row in the child table that has the same parent.
The case you included in your question opted for strategy #1.

What is the cause for MySQL: Errorno 150

Can someone please explain the cause for the following error, 'Can't create table 'Activities' (errno: 150)'
I'm under the understading that the data types and lengths have to be the same, does is have anything to do with the auto increment?
Create Table `LinkMemberActivity` (
`LinkID` int(11) unsigned NOT NULL AUTO_INCREMENT,
`MID` int(11) unsigned NOT NULL,
`AID` int(11) unsigned NOT NULL,
PRIMARY KEY (`LinkID`),
FOREIGN KEY (`MID`) REFERENCES Members(`MID`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;
)
CREATE TABLE `Activities` (
`AID` int(11) unsigned NOT NULL AUTO_INCREMENT,
`Name` varchar(25) DEFAULT NULL,
`MaxCapacity` int(25) DEFAULT NULL,
`StartTime` time DEFAULT NULL,
`EndTime` time DEFAULT NULL,
PRIMARY KEY (`AID`),
FOREIGN KEY (`AID`) REFERENCES LinkMemberActivity(`AID`))
ENGINE=InnoDB DEFAULT CHARSET=latin1 );
You are trying to make a primary key column a foreign key dependent field. This is not only unusual but makes no sense in a datamodel, unless it is part of a composite key. Common practice has a column foreign key dependent on another tables primary key. Not sure what reasons you have for the way you designed your datamodel this way, but you can fix this problem by creating a not null autoincrement column named ID and make this column the primary key. Next remove autoincrement from aid.

Simple add foreign key returns #1215 cannot add foreign key constraint

I am sure I am missing something simple.
RequestLog table:
CREATE TABLE `requestlog` (
`RequestID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`RequestName` varchar(30) NOT NULL,
`RequestData` varchar(150) NOT NULL,
`RequestDate` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`Version` varchar(15) NOT NULL,
PRIMARY KEY (`RequestID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
ResponseLog table:
CREATE TABLE `responselog` (
`ResponseID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`FK_RequestID` int(10) NOT NULL,
`ResponseText` text NOT NULL,
PRIMARY KEY (`ResponseID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
Trying to add a foreign key on ResponseLog.FK_RequestID with
ALTER TABLE ResponseLog
ADD FOREIGN KEY (FK_RequestID) REFERENCES RequestLog(RequestID)
Don't shoot me, what am I missing?
ALTER TABLE references tables ResponseLog and RequestLog. Your CREATE TABLE statements create tables named requestlog and responselog. Try changing your ALTER TABLE statement so that it uses table identifiers with the same case.
Also, and it is probably the main problem, the referenced fields have different data types. One is an int, the other an unsigned int. Data types have to match, otherwise the fields could become inconsistent. MySQL knows this and prevents you from creating a broken foreign key.