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).
Related
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.
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, ''))));
I have the following tables:
specie (MyIsam)
image (InnoDB)
specie_map (InnoDB)
The specie_map table should map an image to a specie, and therefore has the following columns:
specie_id
image_id
Both are int 11, just like the id columns of the specie and image tables. I know I can't create a foreign key between specie_id and specie=>id, since the specie table is a MyIsam table. However, I would expect it to be possible to create a foreign key between image_id and image=>id.
I can create that foreign key and it will save it, however, the CASCADE action I have associated with it does not work. When I delete an image, it does not delete the specie_map entry that is associated with it. I would expect this to work, as this foreign key is between InnoDB tables. Both columns are indexed and of the same data type.
Is this a limitation of MySQL, or am I doing something else wrong?
Update: as requested hereby the table definitions. I have snipped unimportant columns:
-- ----------------------------
-- Table structure for `image`
-- ----------------------------
DROP TABLE IF EXISTS `image`;
CREATE TABLE `image` (
`id` int(11) NOT NULL auto_increment,
`guid` char(36) default NULL,
`title` varchar(255) NOT NULL,
`description` text,
`user_id` int(11) NOT NULL,
`item_id` int(11) default NULL,
`date_uploaded` timestamp NOT NULL default '0000-00-00 00:00:00',
`date_created` timestamp NOT NULL default '0000-00-00 00:00:00',
`date_modified` timestamp NOT NULL default '0000-00-00 00:00:00',
`status` enum('softdeleted','tobedeleted','active') default 'active',
PRIMARY KEY (`id`),
KEY `image_user` (`user_id`),
KEY `image_item` (`item_id`),
KEY `image_mod_by` (`moderated_by`),
CONSTRAINT `image_mod_by` FOREIGN KEY (`moderated_by`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `image_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='stores image data (not file data)';
-- ----------------------------
-- Table structure for `specie`
-- ----------------------------
DROP TABLE IF EXISTS `specie`;
CREATE TABLE `specie` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(256) NOT NULL,
`commonname` varchar(256) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=22 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Table structure for `specie_map`
-- ----------------------------
DROP TABLE IF EXISTS `specie_map`;
CREATE TABLE `specie_map` (
`id` int(11) NOT NULL auto_increment,
`image_id` int(11) NOT NULL,
`specie_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`karma` int(11) NOT NULL,
`date_created` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `image_id` (`image_id`),
KEY `specie_id` (`specie_id`),
CONSTRAINT `specie_map_ibfk_1` FOREIGN KEY (`image_id`) REFERENCES `image` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Foreign keys works only with InnoDb in mysql. MyISAM doesn't support them (the statements are ignored).
And is there any reason why you mix multiple DB engines?
I think you should post the exact DDL statements you used when you attempted to create these tables and the foreign key. Foreign keys between innodb tables work fine, but there are still a few things to look out for:
0) Both tables must be InnoDB. This was already highlighted by the other posters and this is probably the immediate cause of your problem.
1) the data type of the referencing columns (those that make up the foreign key) and their respective referenced columns should be the same. For example, you can't create a foreign key constrain on an INT UNSIGNED column to a plain INT column.
2) if the foreign key is created as part of the table DDL, be sure to put the foreign key definition in the constraints section, that is, below all column definitions. For example:
CREATE TABLE parent (
id int unsigned PRIMARY KEY
);
CREATE TABLE child (
parent_id int unsigned
, foreign key (parent_id)
references parent (id)
);
will work but this:
CREATE TABLE child (
parent_id int unsigned
foreign key references parent (id)
);
won't. It will fail silently because MySQL's parser ignores these types of constraint definitions even before InnoDB gets to create the table (silly, but that's how it is)
3) There must be an index over all the referenced columns. Usually the referenced columns will together make up a primary key or a unique constraint anyway, but it is your job to define this before defining the foreign key.
Final word of advice: if you think your DDL is ok but you still get an error when you execute it, for example like this:
ERROR 1005 (HY000): Can't create table 'test.child' (errno: 150)
Warning (Code 150): Create table 'test/child' with foreign key constraint failed. There is no index in the referenced table where the referenced columns appear as the first columns.
Error (Code 1005): Can't create table 'test.child' (errno: 150)
Then these errors may still not reveal the true nature of the error (silly again, but that's how it is). To shed more light on it, run this command immediately after your attempt to create the foreign key:
SHOW ENGINE INNODB STATUS;
This will give you a bunch of status info, and one section there looks like this:
------------------------
LATEST FOREIGN KEY ERROR
------------------------
120122 11:38:28 Error in foreign key constraint of table test/child:
foreign key (parent_id) references parent (id) ):
Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types
in the table and the referenced table do not match for constraint.
Note that the internal storage type of ENUM and SET changed in
tables created with >= InnoDB-4.1.12, and such columns in old tables
cannot be referenced by such columns in new tables.
See http://dev.mysql.com/doc/refman/5.5/en/innodb-foreign-key-constraints.html
for correct foreign key definition.
As you can see, this gives a bit more information and reveals the true problem, namely "column types in the table and the referenced table do not match for constraint"
So please, post your actual DDL, I'm sure there is a problem in there somewhere.
I have 2 tables with a composite foreign key between the 2. When I try to insert a row into the child table, I get a restraint failure, even though the values exist in the parent table.
Here's a overview of the parent table:
CREATE TABLE `residual_reports` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`processor` enum('1','2','3') NOT NULL,
`posting_date` date NOT NULL DEFAULT '0000-00-00',
`approved_on` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `posting_date_2` (`processor`,`posting_date`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=latin1
The child table has the foreign key to the processor and posting date columns:
CREATE TABLE `residual_data` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`mid` varchar(29) DEFAULT NULL,
`processor` enum('1','2','3') NOT NULL,
`posting_date` date NOT NULL,
......
PRIMARY KEY (`id`),
KEY `residual_data_ibfk_1` (`processor`,`posting_date`),
CONSTRAINT `residual_data_ibfk_1` FOREIGN KEY (`processor`, `posting_date`) REFERENCES `residual_reports` (`processor`, `posting_date`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1
I inserted a row into the residual_reports table with processor = 1, and posting_date = 2010-03-10.
When I try to insert into the residual_data table with processor = 1, and posting_date = 2010-03-10.
INSERT INTO `residual_data`(processor,posting_date) VALUES ('1','2010-03-10');
I get an:
[Err] 1452 - Cannot add or update a child row: a foreign key constraint fails (residual_data, CONSTRAINT residual_data_ibfk_1 FOREIGN KEY (processor, posting_date) REFERENCES residual_reports (processor, posting_date) ON UPDATE CASCADE)
Verified that the values definitely exist in the parent table, but still get foreign key restraint errors. Is there something I'm missing with a composite foreign key?
I would suspect the ENUM's, did you do something with them afterwards? Did you change values or so?
Your code works for me as-is. Is it possible that changes to residual_reports have not been committed yet?
What Mysql version do you use? It seems that they have similar problems before with enum values
http://bugs.mysql.com/bug.php?id=24985
I am trying to design a database but I need some help with the relationships. Am i getting the table design right?
Here is the database idea..
User will submit a howto, each howto will have one or more steps associated with(a one to many). each step can have random pictures associated with(another one to many). so I am thinking of this:
CREATE TABLE `HowtoStepImage`
`id` int(10) unsigned NOT NULL auto_increment,
`user_id` int(10) unsigned NOT NULL,
`howto_id` varchar(25) NOT NULL,
`step_id` varchar(25) NOT NULL,
`img_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `hsi_k_1` (`howto_id`),
CONSTRAINT `hsi_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
CONSTRAINT `hsi_ibfk_2` FOREIGN KEY (`step_id`) REFERENCES `HowtoStep` (`step_id`),
CONSTRAINT `hsi_ibfk_3` FOREIGN KEY (`img_id`) REFERENCES `StepImage` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
table HowtoStep
step_id, title, content, created
primary key (step_id)
table StepImage
img_id, filename, created
CREATE TABLE `UserHowtoComment` (
`id` int(10) unsigned NOT NULL auto_increment,
`howto_id` varchar(25) NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`comment` varchar(500) NOT NULL,
`created` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `UserHowtoComment_ibfk_1` (`howto_id`),
KEY `UserHowtoComment_ibfk_2` (`user_id`),
CONSTRAINT `UserHowtoComment_ibfk_1` FOREIGN KEY (`howto_id`) REFERENCES `HowtoStepImage` (`howto_id`),
CONSTRAINT `UserHowtoComment_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
however, I am getting error when creating the table, I am sure it is due to my database design. here is what mysql>SHOW ENGINE INNODB STATUS; shows:
091217 9:59:59 Error in foreign key constraint of table UserhowtoComment:
FOREIGN KEY (`howto_id`) REFERENCES `howtoStepImage` (`howto_id`),
CONSTRAINT `UserHowtoComment_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8:
Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types
in the table and the referenced table do not match for constraint.
Note that the internal storage type of ENUM and SET changed in
tables created with >= InnoDB-4.1.12, and such columns in old tables
cannot be referenced by such columns in new tables.
See http://dev.mysql.com/doc/refman/5.0/en/innodb-foreign-key-constraints.html
for correct foreign key definition.
the howto_id is a key(index) in UserHowtoComment though. I am not sure if that is the exact problem here..
Make 3 tables: one for HowTo, one for HowToStep, one for HowToStepImage.
Give each table a clearly defined key, e.g. a number or a string.
Then let the 'child' table refer to the key of the parent table.
Make sure that the columns have clear names as well.
TABLE HowTo
COLUMNS HowToId(key)
TABLE HowToStep
COLUMNS HowToStepId(key), HowToId
TABLE HowToStepImage
COLUMNS HowToStepImageId(key), HowToStepId
your query is really messy e.g. step_id varchar(25) needs to be an int.
why dont you just use a gui programm or maybe the good old phpMyAdmin, so you can learn the from the Querys they are creating, phpMyAdmin also has a advanced feature call "Designer" to create constraints.
If I read this correctly, your HowToComment id is a foreign key to HowtoStepImage. Does every comment have to have an image? Seems like a chicken and the egg issue. It seems, from your problem description, that an image links to a comment, not the other way around.
you're falling prey to the misleading terminology in MySQL. in the relational model, key is (necessarily) distinct. in the MySQL-speak, it's just an index. you need either PRIMARY KEY or UNIQUE KEY.
edit to add explicitly what is implied above: foreign keys must point to a key in the relational sense.