There are two tables - posts and comments:
create table posts
(
id integer not null primary key auto_increment,
body text not null
);
create table comments
(
id integer not null primary key auto_increment,
body text not null,
post_id integer not null references posts(id)
);
Now I want to create one more table - reports("bad post" flags) and I want it to store reports for both posts and comments.
create table reports
(
id integer not null primary key auto_increment,
obj_type tinyint not null, /* '1' for posts and '2' for comments */
obj_id integer not null,
body text not null
);
alter table reports add foreign key(obj_id) references posts(id) on delete cascade;
alter table reports add foreign key(obj_id) references comments(id) on delete cascade;
As you see there are two references in a single field (I differentiate them by obj_id), and the question is - Is it all right to do like this ?
If not what would be better solution?
Thanks in advance.
Intuitively that feels not the right way to do it. I think MySQL would be confused too; how would it validate that a constraint is being met; would it try posts first, or comments first ... maybe both?
Personally I would choose to create two link tables:
comments <-> reports
posts <-> reports
That way you disambiguate the obj_id properly.
you just need to reference your comments table since it already references the posts table, this way whenever you get a report you have the key to the comment and the key to the post.
Related
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;
The following query fails with error "Error creating foreign key on city (check data types)":
ALTER TABLE `hotels` ADD FOREIGN KEY ( `city` )
REFERENCES `mydatabase`.`cities` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE ;
Basically I want to have a ony-to-many relation between city.id and hotels.city.
Here are both tables:
CREATE TABLE IF NOT EXISTS `cities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `hotels` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`city` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `city` (`city`)
) ENGINE=InnoDB;
The data types need to match:
cities.id int(11)
hotels.city bigint(20)
Needs to become either:
cities.id bigint(20)
hotels.city bigint(20)
Or:
cities.id int(11)
hotels.city int(11)
Depending on what your application needs.
Also worth mentioning is the fact that both need to be either signed or unsigned.
You may need to OPTIMIZE your tables after changing the data types to match.
I was using phpMyAdmin and i tried creating multiple indices on different tables using the relations view. However, I got the same error saying datatypes did not match. However, the cause indeed was that i was giving the same foreign key name to multiple relations, and because of duplicate names, mysql was throwing this error. So rename your relation, and it should work fine.
I know this is quite an old thread, but I spent some time with this error as well.
The situation I had was the following one:
Table 1: administrations (Primary key: AdministrationId)
Table 2: invoices (Foreign key to AdministrationId)
Table 3: users (error pops up while creating foreign key)
The colomns AdministrationId in my invoices and users table were both of the same type as the AdministrationId column in the administrations table.
The error on my side was that I tried to create a foreign key called administration_id in my users table. But a minute before that I already created a foreign key in my invoices table also called administration_id. When I tried to give the foreign key another name, it worked out fine.
Therefore, keep in mind to correctly name your foreign keys (e.g. prefix them with the table name, eg: invoices_administration_id and users_administration_id). Multiple foreign keys with the same name may not exist (within the same database).
I know this has been answered and I know this question is old. However, I just came across this same error with a different cause and, since this is the top result for this error, I thought I would put this information here both for my own use in the future as well as anyone else who happens along after me.
My columns were both bigint and unsigned. However, after first creating the referenced tables, I then went on to change the name of the primary key column. Nothing else about it had changed, but I was unable to create a foreign key relationship. I ended up dropping the referenced tables and recreating them using the column names I desired and I was able to create the foreign key relationships.
Worth mentioning, but the collation should be the same between both table
I faced the same issue with varchar(64) fields in both tables, and It took me some time to identify the problem was coming from the collation field which was not the same between the 2 table fields.
Updating hotels.city to unsigned worked for me. Because cities.id is unsigned
I'd like to point out, you will get a similar error in case you have set the foreign key to NOT NULL and you have set either the ON DELETE or ON UPDATE to SET NULL.
Udating data type cities.id bigint(20) and hotels.city bigint(20)
OR
Udating data type cities.id int(11) and hotels.city int(11)
AND
Updating hotels.city to unsigned because cities.id is unsigned.
I have two dependent tables as
CREATE TABLE posts
(
post_id int(11) unsigned NOT NULL AUTO_INCREMENT,
title varchar(255),
PRIMARY KEY(post_id)
) ENGINE=InnoDB
CREATE TABLE post_meta
(
post_id int(11) unsigned REFERENCES posts(post_id) ON DELETE CASCADE,
info varchar(255),
PRIMARY KEY(post_id)
) ENGINE=InnoDB
Question 1: After INSERTing into posts, post_meta does not accept value with error Duplicate entry XX for key 'PRIMARY. How should I modify the table structure?
Question 2: How can I set to create a corresponding row in post_meta upon INSERT INTO posts? I mean creating an empty row (only having id of the FK) in post_meta when creating a row in posts. In other words having the same number of rows in two columns without any INSERT into the second column.
Your current implementation looks as out of normalized form. Are you sure you need to keep the data separated to two different tables? Maybe
CREATE TABLE posts
(
post_id int(11) unsigned NOT NULL AUTO_INCREMENT,
title varchar(255),
info varchar(255),
PRIMARY KEY(post_id)
) ENGINE=InnoDB
will do?
Speculating: if you're doing it that way because of security issues then MySQL supports Column-level privileges.
If normalizing your data is unacceptable for some reasons then you can just make post_id primary key in both tables (don't make it foreign key!) and add INSERT and DELETE triggers on table posts which will insert or delete corresponding rows from post_meta.
UPDATE
You said in comment that you can have multiple records in post_meta table corresponding to single record in posts. That changes a lot:
You MUST NOT use primary key on post_id in table post_meta. Primary key MUST be unique in table scope.
You can set up your foreign key (the one you already tried to) - this will ensure that metadata is deleted automatically with posts.
If you do need PRIMARY KEY defined on post_meta then you shoud add a new (possibly auto_increment field) to post_meta table and use it as primary key. (Also, table can exist even without primary key but it's against most DB guidelines)
If you need to create a meta record(s) automatically for each post then you can add an INSERT trigger on posts as I've already suggested. Another approach is using a stored procedure (and only stored procedure) for adding records to posts - and in this SP you can write some SQL to insert necessary records to post_meta.
I'm beginning to build a stamp collecting web app. Python/flask backend (i think :)) mySQL as db. I don't know much about db design so please keep that in mind if I do some really stupid mistake in the way I thought it out. I was thinking of splitting the data into 3 tables.
users table (all the users should be added upon registration to this table)
stamps table (all stamps should reside here and only modified by me)
owned table (junction table with user_id and stamp_id as foreign keys)
Question : if I put user_id and stamp_id as primary key , there will only be one unique entry of this type for example user_1 has card_1. But user_1 might have a duplicate of card_1 so i should have 2 rows
user_1 card_1
user_1 card_1
Another problem that arises is that I want to include state of owned stamp. For example user_1 might have a card_1 in mint condition and a card_1 in bad condition. As far as I understand I can only enter one unique pair of user_1 card_1 . What can I do to get the desired result? Also if there's a better way of doing this please let me know.
Aditional question. I was using mysql workbench to try to plot the db so I have a question about the sql it generates. the CONSTRAINT "fk_gibberish", is that normal or ... why is that ?
CREATE TABLE IF NOT EXISTS `stampcollect`.`users` (
`user_id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`user_username` VARCHAR(45) NULL ,
`user_password` VARCHAR(45) NULL ,
`user_email` VARCHAR(45) NULL ,
PRIMARY KEY (`user_id`) )
CREATE TABLE IF NOT EXISTS `stampcollect`.`stamps` (
`stamp_id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`stamp_name` VARCHAR(45) NULL ,
PRIMARY KEY (`stamp_id`) )
CREATE TABLE IF NOT EXISTS `stampcollect`.`owned` (
`user_id` INT NOT NULL ,
`stamp_id` INT NOT NULL ,
`stamp_status` BIT NULL ,
PRIMARY KEY (`user_id`, `stamp_id`) ,
INDEX `fk_{F5DBEF0D-24E0-4AFF-A5CB-2A6A0D448C96}` (`stamp_id` ASC) ,
CONSTRAINT `fk_{22B4468E-A5FB-4702-A8A9-576AA48A0543}`
FOREIGN KEY (`user_id` )
REFERENCES `stampcollect`.`users` (`user_id` ),
CONSTRAINT `fk_{F5DBEF0D-24E0-4AFF-A5CB-2A6A0D448C96}`
FOREIGN KEY (`stamp_id` )
REFERENCES `stampcollect`.`stamps` (`stamp_id` ));
If users can own the same stamp in multiple states then the state should go in the "owned" table and be part of the key. If he can own multiple copies of the same stamp then it would make sense to have a "quantity" column in that table (not part of the key).
Add an id field with auto-increment on your owned table, and make that the primary key.
Regarding the other question: it's just Workbench generating a unique id for your foreign key. You can rename them, just keep them unique.
What would be the code for creating a table with two foreign keys?
I have a USER table and a PICTURE table. Since a USER can be in many PICTURE and many PICTURE can be from a USER, I need a third table with both primary keys.
Thank you SO, as usual you are invaluable for a learning novice. :)
I can't speak specifically for mySQL but in most databases I have worked with you can put as many foreign keys as you need on a table. But you can only have one primary key. A third table with both keys is the right choice. Make a foreign key to each of the other two tables and a primary key consisting of both ids in the table.
If I understood correctly, you may need to do something like the following:
CREATE TABLE users (
user_id INT NOT NULL PRIMARY KEY,
name VARCHAR(50) NOT NULL
) ENGINE=INNODB;
CREATE TABLE pictures (
picture_id INT NOT NULL PRIMARY KEY,
filename VARCHAR(255) NOT NULL,
posted_by INT NOT NULL,
FOREIGN KEY (posted_by) REFERENCES users(user_id)
) ENGINE=INNODB;
CREATE TABLE users_in_pictures (
user_id INT NOT NULL,
picture_id INT NOT NULL,
PRIMARY KEY (user_id, picture_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (picture_id) REFERENCES pictures(picture_id)
) ENGINE=INNODB;
Note that each picture can be posted by a user. In fact the posted_by field is constrained by a foreign key that references the users table.
In addition, I assume that you want to tag pictures ala-facebook. In this case, you can use the third table, which is using a composite primary key on (user_id, picture_id) and both fields are also constrained to the appropriate table.