Primary key couple unique in both ways - mysql

Here is my problem :
I want to create relations between two items of the same type.
Basically, I have a 'tag' table and I want to establish relations between tags.
I created a table 'tag_relations'. In this table, each line would represent one relation. It will be composed with 2 atributes : tag_id (which is the id of the tag concerned by the relation), and relation (which is the id of the tag related to the first tag represented by tag_id).
I set the primary key of my 'tag_relations' table as the couple of these 2 attributes.
Here is how I created my table :
CREATE TABLE `tag_relation` (
`tag_id` int(11) NOT NULL,
`relation` int(11) NOT NULL,
PRIMARY KEY (`tag_id`,`relation`),
KEY `relation` (`relation`),
CONSTRAINT `tag_relation_ibfk_1` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`),
CONSTRAINT `tag_relation_ibfk_2` FOREIGN KEY (`relation`) REFERENCES `tag` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
My question is : how can I make sure, if I already have a couple (1,2) in my table (so tag 1 is related to tag 2), that it's impossible for me to insert the couple (2,1) (because this relation would already exist implicitly).
Am I forced to create a trigger ?
Thanks in advance

Some databases support indexes on expressions. Since 5.7, MySQL has generated columns with indexes. This allows you to do:
alter table tag_relation add tag1 generated always as (least(tag1, relation));
alter table tag_relation add tag2 generated always as (greatest(tag1, relation));
create unique index unq_tag_relation_tag1_tag2 on tag_relation(tag1, tag2);
In earlier versions, you would need an insert (and possibly update) trigger to ensure data integrity.

Related

When to use an auto-increment key?

I've recently started to work on MySQL and while I've read some documentation on database structure, I cannot get my head around auto-increment keys and why using them.
I have been told:
it's best to use a number instead of text as a primary key,
it's best to use a key that doesn't have any business signification
Let's look at the situation below:
tStores tSales tCustomers
---------- ----------- --------------
store_id sale_id customer_id
storeCode store_id
customer_id
First, I load some data in tStores for all the stores products can be sold. In our business, all stores have a 4 letters code to identify them. I could use this as a primary key, but based on the recommendations above I should use a store_id field that auto-increments?
The problem is, each time I insert something in tSales, I have to go back to tStores and do something like:
SELECT store_id from tStores WHERE storeCode = #myStoreCode;
Assuming I am loading hundreds of thousands rows in tSales for each store, would it not be more efficient to use the storeCode as primary key?
What would be the most efficient way to deal with this?
Yes you can use storeCode as the primary key, it will work if you can ensure it is unique. Then you will add a foreign key on your other tables to establish the relationship.
The benefit of auto increment index are:
It is usually faster than any index on other column type
It is usually recommended by some framework (such as Laravel in PHP)
Related to you structure I would comment on some points:
You have mixed casing columns/tables. When working on MySQL, especially when used on different OS (Windows/Linux), I would always recommend to use lowercase names for both schemas, tables and columns.
You added a prefix in front of store_id and store_code. This prefix is not necessary. Why not simply naming the columns id and code.
The relationship on tSales should be named tStores_id instead to clearly indicate from which table and which column you are referring to.
Here the SQL code for this example:
CREATE SCHEMA `myshop` ;
CREATE TABLE `store`.`stores` (
`code` VARCHAR(10) NOT NULL,
PRIMARY KEY (`code`));
CREATE TABLE `store`.`sales` (
`id` INT NOT NULL AUTO_INCREMENT,
`store_code` VARCHAR(10) NOT NULL,
`customer_id` INT NOT NULL,
PRIMARY KEY (`id`));
CREATE TABLE `store`.`customers` (
`id` INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`));
ALTER TABLE `store`.`sales`
ADD INDEX `fk_sales_customers_id_idx` (`customer_id` ASC) VISIBLE;
ALTER TABLE `store`.`sales`
ADD CONSTRAINT `fk_sales_customers_id`
FOREIGN KEY (`customer_id`)
REFERENCES `store`.`customers` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE `store`.`sales`
ADD INDEX `fk_sales_stores_code_idx` (`store_code` ASC) VISIBLE;
ALTER TABLE `store`.`sales`
ADD CONSTRAINT `fk_sales_stores_code_id`
FOREIGN KEY (`store_code`)
REFERENCES `store`.`stores` (`code`)
ON DELETE CASCADE
ON UPDATE CASCADE;

#1452 - Cannot add or update a child row: a foreign key constraint fails the query is failing [duplicate]

I'm having a bit of a strange problem. I'm trying to add a foreign key to one table that references another, but it is failing for some reason. With my limited knowledge of MySQL, the only thing that could possibly be suspect is that there is a foreign key on a different table referencing the one I am trying to reference.
I've done a SHOW CREATE TABLE query on both tables, sourcecodes_tags is the table with the foreign key, sourcecodes is the referenced table.
CREATE TABLE `sourcecodes` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) unsigned NOT NULL,
`language_id` int(11) unsigned NOT NULL,
`category_id` int(11) unsigned NOT NULL,
`title` varchar(40) CHARACTER SET utf8 NOT NULL,
`description` text CHARACTER SET utf8 NOT NULL,
`views` int(11) unsigned NOT NULL,
`downloads` int(11) unsigned NOT NULL,
`time_posted` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `language_id` (`language_id`),
KEY `category_id` (`category_id`),
CONSTRAINT `sourcecodes_ibfk_3` FOREIGN KEY (`language_id`) REFERENCES `languages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `sourcecodes_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `sourcecodes_ibfk_2` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
CREATE TABLE `sourcecodes_tags` (
`sourcecode_id` int(11) unsigned NOT NULL,
`tag_id` int(11) unsigned NOT NULL,
KEY `sourcecode_id` (`sourcecode_id`),
KEY `tag_id` (`tag_id`),
CONSTRAINT `sourcecodes_tags_ibfk_1` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1
This is the code that generates the error:
ALTER TABLE sourcecodes_tags ADD FOREIGN KEY (sourcecode_id) REFERENCES sourcecodes (id) ON DELETE CASCADE ON UPDATE CASCADE
Quite likely your sourcecodes_tags table contains sourcecode_id values that no longer exists in your sourcecodes table. You have to get rid of those first.
Here's a query that can find those IDs:
SELECT DISTINCT sourcecode_id FROM
sourcecodes_tags tags LEFT JOIN sourcecodes sc ON tags.sourcecode_id=sc.id
WHERE sc.id IS NULL;
I had the same issue with my MySQL database but finally, I got a solution which worked for me.
Since in my table everything was fine from the mysql point of view(both tables should use InnoDB engine and the datatype of each column should be of the same type which takes part in foreign key constraint).
The only thing that I did was to disable the foreign key check and later on enabled it after performing the foreign key operation.
Steps that I took:
SET foreign_key_checks = 0;
alter table tblUsedDestination add constraint f_operatorId foreign key(iOperatorId) references tblOperators (iOperatorId); Query
OK, 8 rows affected (0.23 sec) Records: 8 Duplicates: 0 Warnings: 0
SET foreign_key_checks = 1;
Use NOT IN to find where constraints are constraining:
SELECT column FROM table WHERE column NOT IN
(SELECT intended_foreign_key FROM another_table)
so, more specifically:
SELECT sourcecode_id FROM sourcecodes_tags WHERE sourcecode_id NOT IN
(SELECT id FROM sourcecodes)
EDIT: IN and NOT IN operators are known to be much faster than the JOIN operators, as well as much easier to construct, and repeat.
Truncate the tables and then try adding the FK Constraint.
I know this solution is a bit awkward but it does work 100%. But I agree that this is not an ideal solution to deal with problem, but I hope it helps.
For me, this problem was a little different and super easy to check and solve.
You must ensure BOTH of your tables are InnoDB. If one of the tables, namely the reference table is a MyISAM, the constraint will fail.
SHOW TABLE STATUS WHERE Name = 't1';
ALTER TABLE t1 ENGINE=InnoDB;
This also happens when setting a foreign key to parent.id to child.column if the child.column has a value of 0 already and no parent.id value is 0
You would need to ensure that each child.column is NULL or has value that exists in parent.id
And now that I read the statement nos wrote, that's what he is validating.
I had the same problem today. I tested for four things, some of them already mentioned here:
Are there any values in your child column that don't exist in the parent column (besides NULL, if the child column is nullable)
Do child and parent columns have the same datatype?
Is there an index on the parent column you are referencing? MySQL seems to require this for performance reasons (http://dev.mysql.com/doc/refman/5.5/en/create-table-foreign-keys.html)
And this one solved it for me: Do both tables have identical collation?
I had one table in UTF-8 and the other in iso-something. That didn't work. After changing the iso-table to UTF-8 collation the constraints could be added without problems. In my case, phpMyAdmin didn't even show the child table in iso-encoding in the dropdown for creating the foreign key constraint.
It seems there is some invalid value for the column line 0 that is not a valid foreign key so MySQL cannot set a foreign key constraint for it.
You can follow these steps:
Drop the column which you have tried to set FK constraint for.
Add it again and set its default value as NULL.
Try to set a foreign key constraint for it again.
I'd the same problem, I checked rows of my tables and found there was some incompatibility with the value of fields that I wanted to define a foreign key. I corrected those value, tried again and the problem was solved.
I end up delete all the data in my table, and run alter again. It works. Not the brilliant one, but it save a lot time, especially your application is still in development stage without any customer data.
try this
SET foreign_key_checks = 0;
ALTER TABLE sourcecodes_tags ADD FOREIGN KEY (sourcecode_id) REFERENCES sourcecodes (id) ON DELETE CASCADE ON UPDATE CASCADE
SET foreign_key_checks = 1;
I had this exact same problem about three different times. In each instance it was because one (or more) of my records did not conform to the new foreign key. You may want to update your existing records to follow the syntax constraints of the foreign key before trying to add the key itself. The following example should generally isolate the problem records:
SELECT * FROM (tablename)
WHERE (candidate key) <> (proposed foreign key value)
AND (candidate key) <> (next proposed foreign key value)
repeat AND (candidate key) <> (next proposed foreign key value) within your query for each value in the foreign key.
If you have a ton of records this can be difficult, but if your table is reasonably small it shouldn't take too long. I'm not super amazing in SQL syntax, but this has always isolated the issue for me.
Empty both your tables' data and run the command. It will work.
I was getting this error when using Laravel and eloquent, trying to make a foreign key link would cause a 1452. The problem was lack of data in the linked table.
Please see here for an example: http://mstd.eu/index.php/2016/12/02/laravel-eloquent-integrity-constraint-violation-1452-foreign-key-constraint/
You just need to answer one question:
Is your table already storing data? (Especially the table included foreign key.)
If the answer is yes, then the only thing you need to do is to delete all the records, then you are free to add any foreign key to your table.
Delete instruction: From child(which include foreign key table) to parent table.
The reason you cannot add in foreign key after data entries is due to the table inconsistency, how are you going to deal with a new foreign key on the former data-filled the table?
If the answer is no, then follow other instructions.
I was readying this solutions and this example may help.
My database have two tables (email and credit_card) with primary keys for their IDs. Another table (client) refers to this tables IDs as foreign keys. I have a reason to have the email apart from the client data.
First I insert the row data for the referenced tables (email, credit_card) then you get the ID for each, those IDs are needed in the third table (client).
If you don't insert first the rows in the referenced tables, MySQL wont be able to make the correspondences when you insert a new row in the third table that reference the foreign keys.
If you first insert the referenced rows for the referenced tables, then the row that refers to foreign keys, no error occurs.
Hope this helps.
Make sure the value is in the other table otherwise you will get this error, in the assigned corresponding column.
So if it is assigned column is assigned to a row id of another table , make sure there is a row that is in the table otherwise this error will appear.
you can try this exapmple
START TRANSACTION;
SET foreign_key_checks = 0;
ALTER TABLE `job_definers` ADD CONSTRAINT `job_cities_foreign` FOREIGN KEY
(`job_cities`) REFERENCES `drop_down_lists`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
SET foreign_key_checks = 1;
COMMIT;
Note : if you are using phpmyadmin just uncheck Enable foreign key checks
as example
hope this soloution fix your problem :)
UPDATE sourcecodes_tags
SET sourcecode_id = NULL
WHERE sourcecode_id NOT IN (
SELECT id FROM sourcecodes);
should help to get rid of those IDs. Or if null is not allowed in sourcecode_id, then remove those rows or add those missing values to the sourcecodes table.
I had the same problem and found solution, placing NULL instead of NOT NULL on foreign key column. Here is a query:
ALTER TABLE `db`.`table1`
ADD COLUMN `col_table2_fk` INT UNSIGNED NULL,
ADD INDEX `col_table2_fk_idx` (`col_table2_fk` ASC),
ADD CONSTRAINT `col_table2_fk1`
FOREIGN KEY (`col_table2_fk`)
REFERENCES `db`.`table2` (`table2_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION;
MySQL has executed this query!
In my case, I created a new table with the same structure, created the relationships with the other tables, then extracted the data in CSV from the old table that has the problem, then imported the CSV to the new table and disabled foreign key checking and disabled import interruption, all my data are inserted to the new table that has no problem successfully, then deleted the old table.
It worked for me.

On delete cascade, records can be deleted on the parent table, but parent table can not be dropped

I have two tables
CREATE TABLE `category` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `item` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
categoryid` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`), KEY `fk_categoryid_item` (`categoryid`),
CONSTRAINT `fk_categoryid_item` FOREIGN KEY (`categoryid`)
REFERENCES `category` (`id`) ON DELETE CASCADE)
ENGINE=InnoDB DEFAULT CHARSET=utf8
In the table category I have a record with id 2.
In the item I have a record with id = 1, categoryid = 2, with 2 as the foreign key referring to the category table. If I delete the row in the category table with the id 2, the record in the item table that has the categoryid as 2 also gets deleted. This is as expected because of on delete cascade. But If I try to drop the table category, I get the error Error Code:
1217. Cannot delete or update a parent row: a foreign key constraint fails
Why does this happen ? Of course, setting foreign_key_checks = 0 dropping the table becomes possible. But I would like to know why does this happen that we can delete the records, but can not drop the table with on cascade delete option. Does this option only apply for deleting records, but not for dropping tables.
I checked the documentation, I could not find any explanation for this.
Please let me know if there is something fundamental that I am missing or if you point out to the related documentation it would be helpful. I am using MySQL 5.7.
Thanks in advance.
If you delete the table category but do not remove/alter the foreign key, then that will be left pointing to nothing. Internally the database has a management system that reinforces the referential constraints and that prevents you from creating lose ends. See also this, this and this questions.
It has something to do also with the math behind it, it is called relational algebra. I am not at that level either, but I think it breaks the definition of a FK if you delete one of the associated tables.
In database relational modeling and implementation, a unique key is a set of zero or more attributes, the value(s) of which are guaranteed to be unique for each tuple (row) in a relation.

In SQL, is it OK for two tables to refer to each other?

In this system, we store products, images of products (there can be many image for a product), and a default image for a product. The database:
CREATE TABLE `products` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`NAME` varchar(255) NOT NULL,
`DESCRIPTION` text NOT NULL,
`ENABLED` tinyint(1) NOT NULL DEFAULT '1',
`DATEADDED` datetime NOT NULL,
`DEFAULT_PICTURE_ID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `Index_2` (`DATEADDED`),
KEY `FK_products_1` (`DEFAULT_PICTURE_ID`),
CONSTRAINT `FK_products_1` FOREIGN KEY (`DEFAULT_PICTURE_ID`) REFERENCES `products_pictures` (`ID`) ON DELETE SET NULL ON UPDATE SET NULL
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
CREATE TABLE `products_pictures` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`IMG_PATH` varchar(255) NOT NULL,
`PRODUCT_ID` int(10) unsigned NOT NULL,
PRIMARY KEY (`ID`),
KEY `FK_products_pictures_1` (`PRODUCT_ID`),
CONSTRAINT `FK_products_pictures_1` FOREIGN KEY (`PRODUCT_ID`) REFERENCES `products` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
as you can see, products_pictures.PRODUCT_ID -> products.ID and products.DEFAULT_PICTURE_ID -> products_pictures.ID, so a cycle reference. Is it OK?
No, it's not OK. Circular references between tables are messy. See this (decade old) article: SQL By Design: The Circular Reference
Some DBMS can handle these, and with special care, but MySQL will have issues.
Option 1
As your design, to make one of the two FKs nullable. This allows you to solve the chicken-and-egg problem (which table should I first Insert into?).
There is a problem though with your code. It will allow a product to have a default picture where that picture will be referencing another product!
To disallow such an error, your FK constraint should be:
CONSTRAINT FK_products_1
FOREIGN KEY (id, default_picture_id)
REFERENCES products_pictures (product_id, id)
ON DELETE RESTRICT --- the SET NULL options would
ON UPDATE RESTRICT --- lead to other issues
This will require a UNIQUE constraint/index in table products_pictures on (product_id, id) for the above FK to be defined and work properly.
Option 2
Another approach is to remove the Default_Picture_ID column form the product table and add an IsDefault BIT column in the picture table. The problem with this solution is how to allow only one picture per product to have that bit on and all others to have it off. In SQL-Server (and I think in Postgres) this can be done with a partial index:
CREATE UNIQUE INDEX is_DefaultPicture
ON products_pictures (Product_ID)
WHERE IsDefault = 1 ;
But MySQL has no such feature.
Option 3
This approach, allows you to even have both FK columns defined as NOT NULL is to use deferrable constraints. This works in PostgreSQL and I think in Oracle. Check this question and the answer by #Erwin: Complex foreign key constraint in SQLAlchemy (the All key columns NOT NULL Part).
Constraints in MySQL cannot be deferrable.
Option 4
The approach (which I find cleanest) is to remove the Default_Picture_ID column and add another table. No circular path in the FK constraints and all FK columns will be NOT NULL with this solution:
product_default_picture
----------------------
product_id NOT NULL
default_picture_id NOT NULL
PRIMARY KEY (product_id)
FOREIGN KEY (product_id, default_picture_id)
REFERENCES products_pictures (product_id, id)
This will also require a UNIQUE constraint/index in table products_pictures on (product_id, id) as in solution 1.
To summarize, with MySQL you have two options:
option 1 (a nullable FK column) with the correction above to enforce integrity correctly
option 4 (no nullable FK columns)
The only issue you're going to encounter is when you do inserts.
Which one do you insert first?
With this, you will have to do something like:
Insert product with null default picture
Insert picture(s) with the newly created product ID
Update the product to set the default picture to one that you just inserted.
Again, deleting will not be fun.
this is just suggestion but if possible create one join table between this table might be helpfull to tracking
product_productcat_join
------------------------
ID(PK)
ProductID(FK)- product table primary key
PictureID(FK) - category table primary key
In the other table you can just hold that field without the foreign key constraint.
it is useful in some cases where you want to process with the smaller table but connect to the bigger table with the result of the process.
For example if you add a product_location table which holds the country, district, city, address and longitude and latitude information. There might be a case that you want to show the product within a circle on the map.
John what your doing isnt anything bad but using PK-FK actually helps with normalizing your data by removing redundant repeating data. Which has some fantastic advantages from
Improved data integrity owing to the elimination of duplicate storage locations for the same data
Reduced locking contention and improved multiple-user concurrency
Smaller files
that is not a cyclic ref, that is pk-fk

MySQL deleting multiple columns from two table

I have Two tables like this:
Table categories:
columns: id, name, parent
1, Foods, 0
2, Drinks, 0
3, FastFood, 1
4, Hamburger, 3
Table documents:
columns: id, name, categoryID
1, CheseBurger, 4
2, shop, 3
the parent column has the parent category's id. So When i want to delete Foods entry from categories, i want to delete all child categories and documents.
How can I do this?
As mentioned before, you could use FOREIGN KEY CONSTRAINTS to achieve such a task. Below would be your new table structure for MySQL to support automatically deleting both documents and child categories:
CREATE TABLE categories (
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
parent INT(11) UNSIGNED,
INDEX(parent),
FOREIGN KEY (parent) REFERENCES categories(id) ON DELETE CASCADE
) engine=InnoDB;
CREATE TABLE documents (
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
categoryID INT(11) UNSIGNED NOT NULL,
INDEX(categoryID),
FOREIGN KEY (categoryID) REFERENCES categories(id) ON DELETE CASCADE
) engine=InnoDB;
I would either use a trigger, or create a more detailed "delete" sproc that would handle it.
Some databases support enforcing referential integrity through foreign keys. I've done it with Oracle, but I'm no mysql expert. It's done as an attribute of the foreign key through the keyword 'CASCADE DELETE'. The database automatically handles it for you.
Here's a quick Oracle example:
ALTER TABLE Things ADD CONSTRAINT FK_Things_Stuff
FOREIGN KEY (ThingID) REFERENCES Stuff (ThingID)
ON DELETE CASCADE
;
You have 2 choices - in either case I recommend that you create foreign key constraints for your relationships.
Choice 1 is to use ON DELETE CASCADE. I think that this is not a good practice, though, because an unintended delete can have quite surprising consequences.
Choice 2 is to walk the tree and find the records that need to be deleted. You can use a self-join for your categories table to identify all the children in n levels of the hierarchy. This is hte prefered approach, imo.
Other ideas like triggers are just a variation of 2.