MySql - Product Variants Table (Wide Table) - Unique with NULLs - mysql

I have a products table, and a product_variants table (one-to-many).
The product_variants table has the following structure:
CREATE TABLE product_variants (
id int(11) NOT NULL AUTO_INCREMENT,
id_product int(11) NOT NULL,
id_colourSet int(11) DEFAULT NULL,
id_size int(11) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY UNIQUE (id_product,id_colourSet,id_size),
KEY idx_prod (id_product),
KEY idx_colourSet (id_colourSet),
KEY idx_size (id_size),
CONSTRAINT fk_df_product_variants_id_colurSet FOREIGN KEY (id_colourSet) REFERENCES df_colour_sets (id_colourSet) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_df_product_variants_id_product FOREIGN KEY (id_product) REFERENCES df_products (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_df_product_variants_id_size FOREIGN KEY (id_size) REFERENCES df_sizes (id) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB
The options are known at compile-time. Each option is foreign-keyed to a dedicated table, and the unique key is the combination of all options.
I then insert products with an "ON DUPLICATE KEY UPDATE ..." statement, and if a variant already exists the query will use an existing variant.
The problem is that certain products do not have a color, nor a size. In this case the unique constraint fails and I insert lots of almost-empty rows in the product_variants table.
In order to solve this problem I am creating a "NULL" value for each option (e.g. "NO_COLOR", "NO_SIZE") in the respective option tables, and using that as the default value for the option columns in the product_variants table.
Would this be the recommended solution? Is there a better way of structuring this data? I would really like to avoid an EAV design.
Thank you

Designating a magic value that means "missing value" is not the right solution in almost every case. That's what NULL is for.
It's also not clear how "NO_COLOR" is used for an integer. I guess it would map to the value 0, which is typically not used in an auto-increment column.
You can create another column to be a hash of the three unique key columns, defaulted to '' to avoid null problems. Then put a unique constraint on that hash.
CREATE TABLE product_variants (
id int(11) NOT NULL AUTO_INCREMENT,
id_product int(11) NOT NULL,
id_colourSet int(11) DEFAULT NULL,
id_size int(11) DEFAULT NULL,
option_hash binary(16) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY (option_hash),
KEY idx_prod (id_product),
KEY idx_colourSet (id_colourSet),
KEY idx_size (id_size),
CONSTRAINT fk_df_product_variants_id_colurSet FOREIGN KEY (id_colourSet) REFERENCES df_colour_sets (id_colourSet) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_df_product_variants_id_product FOREIGN KEY (id_product) REFERENCES df_products (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_df_product_variants_id_size FOREIGN KEY (id_size) REFERENCES df_sizes (id) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB;
CREATE TRIGGER product_variants_ins BEFORE INSERT ON product_variants
FOR EACH ROW SET option_hash = UNHEX(MD5(CONCAT_WS('|',
COALESCE(id_product, ''),
COALESCE(id_colourSet, ''),
COALESCE(id_size, ''))));
CREATE TRIGGER product_variants_upd BEFORE UPDATE ON product_variants
FOR EACH ROW SET option_hash = UNHEX(MD5(CONCAT_WS('|',
COALESCE(id_product, ''),
COALESCE(id_colourSet, ''),
COALESCE(id_size, ''))));

Related

How can I delete the rows of a table which stores foreign keys?

THE SOLUTION IS BELOW
I have three tables like the following:
CREATE TABLE `t_arch_layer` (
`arch_layer_id` int(11) NOT NULL AUTO_INCREMENT,
`arch_layer_name` varchar(45) NOT NULL,
PRIMARY KEY (`arch_layer_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
CREATE TABLE `t_tech` (
`tech_id` int(11) NOT NULL AUTO_INCREMENT,
`tech_name` varchar(45) DEFAULT NULL,
`tech_type_id` int(11) NOT NULL,
`tech_icon` text,
PRIMARY KEY (`tech_id`),
KEY `fk_t_tech_t_tech_type1_idx` (`tech_type_id`),
CONSTRAINT `fk_t_tech_t_tech_type1` FOREIGN KEY (`tech_type_id`) REFERENCES `t_tech_type` (`tech_type_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
CREATE TABLE `t_arch_layer_tech` (
`arch_layer_id` int(11) NOT NULL,
`tech_id` int(11) NOT NULL,
PRIMARY KEY (`tech_id`,`arch_layer_id`),
KEY `fk_t_layer_has_t_tech_t_tech1_idx` (`tech_id`),
KEY `fk_t_layer_has_t_tech_t_layer1_idx` (`arch_layer_id`),
CONSTRAINT `fk_t_layer_has_t_tech_t_layer1` FOREIGN KEY (`arch_layer_id`) REFERENCES `t_arch_layer` (`arch_layer_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `fk_t_layer_has_t_tech_t_tech1` FOREIGN KEY (`tech_id`) REFERENCES `t_tech` (`tech_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Basically it's a tipical situation where one table use two foreign keys from another two different tables. This table stores the possible combinations between the layers and technologies so it can't store any combination of layer_id and tech_id which is not in both.
But there is a problem, I need to delete whenever I want some row from t_arch_layer_tech. This it's impossible due to the foreign keys, I know it.
My question is, is there something to use the foreign key as a reference to forbide insert values that there aren't into t_tech or t_arch_layer and also to be consider as "own fields" (I can't explain better) of the table in order to delete any row of the t_arch_layer_tech table? Delete t_tech and t_arch_layer tables to avoid the foreign keys and then set the limits into the t_arch_layer_tech is not a solution.
SOLUTION
When that error appears it's neccesary to check the DB relationships and read carefully the provided message. It seems useless but it helped me to understand what's happening with the t_arch_layer_tech FK. I was using them into another table BUT separately, not as a compound FK. This is the reason because I could insert some rows into t_arch_layer_tech and delete only specific pairs.
So, summarizing, if you are going to use FKs that exist together (as my pair "arch_layer_id, tech_id") create ONLY ONE FK which is a compound FK that uses the mentioned.

Mysql composite key out of a foreign composite key

I'm trying to make a many-to-many relationship between two tables In Mysql WorkBench, and one of those 2 tables has a composite primary key ( parts are coming from 2 foreign keys). When I'm trying to generate the SQL I'm getting this error :
ERROR: Error 1215: Cannot add foreign key constraint
SQL Code:
-- -----------------------------------------------------
-- Table `A_D_schema`.`Resources_has_OwnerGroups`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `A_D_schema`.`Resources_has_OwnerGroups` (
`Resources_id` INT NOT NULL,
`OwnerGroups_id` INT NOT NULL,
`OwnerGroups_Instances_has_Customers_Instances_idInstances` INT NOT NULL,
`OwnerGroups_Instances_has_Customers_Customers_idCustomers` INT NOT NULL,
PRIMARY KEY (`Resources_id`, `OwnerGroups_id`, `OwnerGroups_Instances_has_Customers_Instances_idInstances`, `OwnerGroups_Instances_has_Customers_Customers_idCustomers`),
INDEX `fk_Resources_has_OwnerGroups_OwnerGroups1_idx` (`OwnerGroups_id` ASC, `OwnerGroups_Instances_has_Customers_Instances_idInstances` ASC, `OwnerGroups_Instances_has_Customers_Customers_idCustomers` ASC),
INDEX `fk_Resources_has_OwnerGroups_Resources1_idx` (`Resources_id` ASC),
CONSTRAINT `fk_Resources_has_OwnerGroups_Resources1`
FOREIGN KEY (`Resources_id`)
REFERENCES `A_D_schema`.`Resources` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_Resources_has_OwnerGroups_OwnerGroups1`
FOREIGN KEY (`OwnerGroups_id` , `OwnerGroups_Instances_has_Customers_Instances_idInstances` , `OwnerGroups_Instances_has_Customers_Customers_idCustomers`)
REFERENCES `A_D_schema`.`OwnerGroups` (`id` , `Instances_has_Customers_Instances_idInstances` , `Instances_has_Customers_Customers_idCustomers`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
From the SHOW ENGINE INNODB STATUS I can see this message :
Cannot resolve column name close to:
)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_Resources_has_OwnerGroups_OwnerGroups1`
FOREIGN KEY (`OwnerGroups_id` , `OwnerGroups_Instances_has_Customers_Instances_idInstances` , `OwnerGroups_Instances_has_Customers_Customers_idCustomers`)
REFERENCES `A_D_schema`.`OwnerGroups` (`id` , `Instances_has_Customers_Instances_idInstances` , `Instances_has_Customers_Customers_idCustomers`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
The SHOW CREATE TABLE Resources and SHOW CREATE TABLE OwnerGroups :
CREATE TABLE `Resources` (
`idResources` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(45) DEFAULT NULL,
`role` int(11) DEFAULT NULL COMMENT 'role : 1 disptcher \n0 admin',
PRIMARY KEY (`idResources`),
UNIQUE KEY `idresources_UNIQUE` (`idResources`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `OwnerGroups` (
`idOwnerGroups` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
`group` int(11) DEFAULT NULL,
PRIMARY KEY (`idOwnerGroups`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CONSTRAINT `fk_Resources_has_OwnerGroups_Resources1`
FOREIGN KEY (`Resources_id`)
REFERENCES `A_D_schema`.`Resources` (`id`)
Your Resources table doesn't have a column id. Its primary key is idResources.
CONSTRAINT `fk_Resources_has_OwnerGroups_OwnerGroups1`
FOREIGN KEY (`OwnerGroups_id` , `OwnerGroups_Instances_has_Customers_Instances_idInstances` , `OwnerGroups_Instances_has_Customers_Customers_idCustomers`)
REFERENCES `A_D_schema`.`OwnerGroups` (`id` , `Instances_has_Customers_Instances_idInstances` , `Instances_has_Customers_Customers_idCustomers`)
Your OwnerGroups table doesn't have a column id. Its primary key is idOwnerGroups. It doesn't have the other two columns you reference at all.
In general, when you declare a foreign key, first you name the columns in the child table:
CREATE TABLE Child (
childCol1 INT,
childCol2 INT,
...
FOREIGN KEY (childCol1, childCol2) ...
Then you reference columns in the parent table:
... REFERENCES Parent (parentCol1, parentCol2)
);
You must use the names of columns as they exist in the parent table.
The columns you reference in the parent table must together be the PRIMARY KEY or UNIQUE KEY of that table. In other words, given the example above, it would not work against this Parent table:
CREATE TABLE Parent (
parentCol1 INT,
parentCol2 INT,
PRIMARY KEY (parentCol1)
);
Because the PRIMARY KEY does not include parentCol2.
In your case, the following should work:
CREATE TABLE IF NOT EXISTS `A_D_schema`.`Resources_has_OwnerGroups` (
`Resources_id` INT NOT NULL,
`OwnerGroups_id` INT NOT NULL,
`OwnerGroups_Instances_has_Customers_Instances_idInstances` INT NOT NULL,
`OwnerGroups_Instances_has_Customers_Customers_idCustomers` INT NOT NULL,
PRIMARY KEY (`Resources_id`, `OwnerGroups_id`, `OwnerGroups_Instances_has_Customers_Instances_idInstances`, `OwnerGroups_Instances_has_Customers_Customers_idCustomers`),
CONSTRAINT `fk_Resources_has_OwnerGroups_Resources1`
FOREIGN KEY (`Resources_id`)
REFERENCES `A_D_schema`.`Resources` (`idResources`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_Resources_has_OwnerGroups_OwnerGroups1`
FOREIGN KEY (`OwnerGroups_id`)
REFERENCES `A_D_schema`.`OwnerGroups` (`idOwnerGroups`)
ON DELETE NO ACTION
ON UPDATE NO ACTION
) ENGINE = InnoDB
I took out a couple of INDEX definitions that are redundant. You don't need to index your PRIMARY KEY, it's already the clustered index of the table. You don't need to index the column you use in a foreign key declaration, MySQL will index that column automatically if it need to (though if an index already exists for that column, the FK constraint will use that index).
I'm not sure I understand what your other two columns OwnerGroups_Instances_has_Customers_Instances_idInstances and OwnerGroups_Instances_has_Customers_Customers_idCustomers are meant to do. Typically in a many-to-many table, you only need enough columns to reference the primary keys of the respective parent tables.
Re your comment:
You should try refreshing the view of the schema from time to time. There's a button with a pair of curvy arrows, to the right of "SCHEMAS".

How to prevent duplicate pair to be inserted into lookup table?

I'm trying to make relationship between CAR and ITEM tables.
Since a car can have the same component as other cars, and an item can be used in many cars. I decide to use many-to-many relationship for this issue.
This issue force me to remove PK from both car_idcar and item_list_iditem_list on the middle table.
But...How I can prevent entry of duplicate pair(red box below)from being inserted
Additional info as #Barmar requested:
CREATE TABLE `car_item` (
`id_car_item` int(11) NOT NULL AUTO_INCREMENT,
`car_idcar` int(11) NOT NULL,
`item_list_iditem_list` int(11) NOT NULL,
PRIMARY KEY (`id_car_item`),
UNIQUE KEY `car_idcar_UNIQUE` (`car_idcar`),
UNIQUE KEY `item_list_iditem_list_UNIQUE` (`item_list_iditem_list`),
KEY `fk_car_has_item_list_item_list1_idx` (`item_list_iditem_list`),
KEY `fk_car_has_item_list_car1_idx` (`car_idcar`),
CONSTRAINT `fk_car_has_item_list_car1` FOREIGN KEY (`car_idcar`) REFERENCES `car` (`idcar`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `fk_car_has_item_list_item_list1` FOREIGN KEY (`item_list_iditem_list`) REFERENCES `item_list` (`iditem_list`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
Full schema creating script here: http://pastebin.com/1vPYxswd
You should not have unique keys on each column, you should have a unique key on the combination of columns. This allows each column to be duplicated by itself, but not the pair of them. So you can have multiple references to the same car, and multiple references to the same item_list, but not multiple links between the same car and item_list.
CREATE TABLE `car_item` (
`id_car_item` int(11) NOT NULL AUTO_INCREMENT,
`car_idcar` int(11) NOT NULL,
`item_list_iditem_list` int(11) NOT NULL,
PRIMARY KEY (`id_car_item`),
UNIQUE KEY `car_idcar_iditem_list_UNIQUE` (`car_idcar`, `item_list_iditem_list`),
CONSTRAINT `fk_car_has_item_list_car1` FOREIGN KEY (`car_idcar`) REFERENCES `car` (`idcar`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `fk_car_has_item_list_item_list1` FOREIGN KEY (`item_list_iditem_list`) REFERENCES `item_list` (`iditem_list`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
See Creating UNIQUE constraint on multiple columns in MySQL Workbench EER diagram

MySQL: unique index not respecting `null` values

I am having trouble creating a unique index respecting null-values in MySQL v5.6.20. I checked similar answers here. but couldn't solve my problem with them.
Desired behavior
I want a table which has references to three other table (date_list_assignment). The purpose of the table is the mapping of date_list-entries to course categories and/or date_list categories. The first column is therefore mandatory, while the latter two are not. If the latter two are null the date list entries is declared global. If a date list entry has no entry within this table, it is not shown anywhere.
Here are some examples of entries and their meaning:
# entry which is global within course category 2
date_list_id: 1, course_category_id: 2, date_list_category_id: null
# entry which is global
date_list_id: 1, course_category_id: null, date_list_category_id: null
# entry which is only visible within course category 2 and date list category 17
date_list_id: 1, course_category_id: 2, date_list_category_id: 17
Short version: I want to make sure, that any combination of the three columns stays unique within the table...no matter if the values are null or not.
Table schema
I have the following table:
CREATE TABLE `date_list_assignment` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date_list_id` int(11) NOT NULL,
`course_category_id` int(11) DEFAULT NULL,
`date_list_category_id` int(11) DEFAULT NULL,
`created` int(11) DEFAULT NULL,
`created_by` int(11) DEFAULT NULL,
`updated` int(11) DEFAULT NULL,
`updated_by` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `IN_relation_unique` (`date_list_id`,`course_category_id`,`date_list_category_id`),
KEY `FK_date_list_assignment_user_created` (`created_by`),
KEY `FK_date_list_assignment_user_updated` (`updated_by`),
KEY `FK_date_list_assignment_course_category` (`course_category_id`),
KEY `FK_date_list_assignment_date_list_category` (`date_list_category_id`),
CONSTRAINT `FK_date_list_assignment_course_category` FOREIGN KEY (`course_category_id`) REFERENCES `course_category` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `FK_date_list_assignment_date_list` FOREIGN KEY (`date_list_id`) REFERENCES `date_list` (`id`) ON UPDATE CASCADE,
CONSTRAINT `FK_date_list_assignment_date_list_category` FOREIGN KEY (`date_list_category_id`) REFERENCES `date_list_category` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `FK_date_list_assignment_user_created` FOREIGN KEY (`created_by`) REFERENCES `user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `FK_date_list_assignment_user_updated` FOREIGN KEY (`updated_by`) REFERENCES `user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Problem
As you can see, I declared a unique index (IN_relation_unique) on the three columns. However, I still can create to identical rows having for example these values:
date_list_id: 1, course_category_id: 2, date_list_category_id: null
I am aware that some of this behavior changed in current MySQL-versions which is also the reason I use an index and not a composite PK allowing null-values.
For exmaple this answer states, that this is expected behavior in MySQL. If so, how can you achieve this since its also no longer possible with composite PKs allowing null-values either!?
Thanks for your help!
Yes, this is the expected behaviour in MySQL (in fact in ANSI-92 too). NULL values are not treated as equal values in unique keys and primary keys can not contain NULL values by definition.
A unique constraint is satisfied if and only if no two rows in a table have the same non-null values in the unique columns. In addition, if the unique constraint was defined with PRIMARY KEY, then it requires that none of the values in the specified column or columns be the null value.
(http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt)
Since you have the foreign key constraint on the nullable column, I would suggest to add a dummy value to the parent tables which states the fact that the parent is irrelevant or not determined (the records where ID = 0 maybe) and add the NOT NULL constraint to the column. (Additionally you can add the dummy value as DEFAULT).

Getting a "foreign key constraint fails" even though I have "on delete cascade"

I thought the point of ON DELETE CASCADE was that this wouldn't happen. :\ I have the following tables:
CREATE TABLE Tweets (
tweetID INTEGER NOT NULL AUTO_INCREMENT,
userID INTEGER NOT NULL,
content VARCHAR(140) NOT NULL,
dateTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
hasPoll INTEGER NOT NULL,
visible INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (tweetID),
FOREIGN KEY (userID) REFERENCES Users(userID)
ON DELETE CASCADE
);
CREATE TABLE Polls (
pollID INTEGER NOT NULL AUTO_INCREMENT,
tweetID INTEGER NOT NULL,
pollOptionText VARCHAR(300),
PRIMARY KEY (pollID),
FOREIGN KEY (tweetID) REFERENCES Tweets(tweetID)
);
The problem is that when I try to delete a Tweet which has a Poll attached to it, I get the following error (via Flask):
_mysql_exceptions.IntegrityError
IntegrityError: (1451, 'Cannot delete or update a parent row: a foreign key constraint fails (`twitter`.`polls`, CONSTRAINT `polls_ibfk_1` FOREIGN KEY (`tweetID`) REFERENCES `Tweets` (`tweetID`))')
Help please!
That is indeed the point of on delete cascade. You get the error, because your code doesn't declare on delete cascade from "poll" to "tweet".
CREATE TABLE Polls (
pollID INTEGER NOT NULL AUTO_INCREMENT,
tweetID INTEGER NOT NULL,
pollOptionText VARCHAR(300),
PRIMARY KEY (pollID),
FOREIGN KEY (tweetID) REFERENCES Tweets(tweetID)
ON DELETE CASCADE
);
This will delete rows in "Polls" when corresponding rows are deleted in "Tweets".
You have to put ON DELETE CASCADE after FOREIGN KEY (tweetID) REFERENCES Tweets(tweetID)
According to the MySQL Foreign Key Constraints reference:
CASCADE: Delete or update the row from the parent table, and
automatically delete or update the matching rows in the child table.
Both ON DELETE CASCADE and ON UPDATE CASCADE are supported.
Also, according to the MySQL Foreign Keys reference:
For storage engines other than InnoDB, it is possible when defining a
column to use a REFERENCES tbl_name(col_name) clause, which has no
actual effect, and serves only as a memo or comment to you that the
column which you are currently defining is intended to refer to a
column in another table.
So since the foreign key is from the child table to the parent table, it makes foo a parent table and Polls a child table, so deleting a row from Tweets will cascade deletions to Pools, providing you use InnoDB or some other storage engine that supports it.
UPDATE:
This error is because you have a relation between poll and twitter... without cascading you have to delete or update the polls removing the relation with the Tweet that will be deleted. Or use ON DELETE CASCADE:
CREATE TABLE Tweets (
tweetID INTEGER NOT NULL AUTO_INCREMENT,
content VARCHAR(140) NOT NULL,
PRIMARY KEY (tweetID)
);
CREATE TABLE Polls (
pollID INTEGER NOT NULL AUTO_INCREMENT,
tweetID INTEGER NOT NULL,
pollOptionText VARCHAR(300),
PRIMARY KEY (pollID),
FOREIGN KEY (tweetID) REFERENCES Tweets(tweetID)
ON DELETE CASCADE
);
INSERT INTO Tweets VALUES(1,'tweet');
INSERT INTO Polls VALUES(1,1,"pool");
DELETE FROM Tweets WHERE tweetID = 1;
You will surely get this error. you have to keep at least one record into tweets.