Junction tables with non unique entry - mysql

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.

Related

Key column doesn't exist in table

I'm having trouble adding a foreign key field that references another table.
First I created the users table as so:
CREATE TABLE users (
user_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
userName VARCHAR(256) NOT NULL,
userEmail VARCHAR (256) NOT NULL,
userPwd VARCHAR(256) NOT NULL,
);
then I'd like the quizzes table to have a foreign key that references the user_id from the first table
CREATE TABLE quizzes (
quizId INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
quizName VARCHAR(128) NOT NULL,
quizMax SMALLINT(6) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (user_id)
);
This is throwing the error: 'Key column 'user_id' doesn't exist in table.
Other answers advised to check that DB is InnoDB, which I did, and it is.
Can't understand why it's telling me that user_id doesn't exist, when it clearly does exist in the users table.
Firstly check if table user is created successfully, due to the additional ',' on last column!
Secondly, the column you reffered in FOREIGN KEY(user_id) is not defined in table quizzes, you need to add this column in quizzes table.
First: You do not need the last comma - , in the first CREATE statement. -
Second: you have to create the columns before you can use them in a foreign key constraint and user_id does not exist in the second table at the moment of constraint creation.
Take a look at the example below. The last create succeeds when user_id column is added before the constraint is created:

Create table fails with Foreign Key Constraint is incorrectly Formed

Topic
MariaDB InnoDB Foreign Key Issue
Want to start off by saying I'm new to InnoDB and spent all day reading posts yesterday I've tried multiple things along the way to get me where I am now so am I hosed or is there a way out of this dark forest.
I have a table that is central to a number of tables in my data model. So something along these lines:
create table users (id int not null auto_increment
, username varchar(255) NOT NULL
, password varchar(255) NOT NULL
, active int NOT NULL
, PRIMARY KEY (id))
ENGINE=InnoDB COLLATE=utf8_unicode_ci;
Decided to clean up some DELETE / UPDATE clauses on my FKs quickly this weekend...Famous Last Words...
A related table example is here
create table athing (id int not null auto_increment
, name varchar(255) not null
, status varchar(255) not null
, created_by_user_id int
, PRIMARY KEY (id)
, CONSTRAINT athing_fk1 FOREIGN KEY (created_by_user_id) REFERENCES users (id)
) ENGINE=InnoDB COLLATE=utf8_unicode_ci;
Problem
Modified the FK in the "ATHING" table to include ON DELETE SET NULL. Saved that modification everything seemed ok. I was using HeidiSQL to perform this.
Long story short I was trolling through my list of tables and low and behold my USERS table was GONE! Through a lot of reading and effort I was able to get things cleaned up but felt to really ensure things were good I dropped all FKs pointing at USERS table and dropped the table.
Now when I attempt to re-create the USERS table I receive this error:
ERROR 1005 (HY000): Can't create table `sprintdb`.`system_users` (errno: 150 "Foreign key constraint is incorrectly formed")
What I noticed post my first attempt at doing this is while I'd thought I'd dropped all FKs there were remnants of keys still out there specifically indexes that supported those keys on some of the tables. In querying the INNODB_SYS_TABLES and INNODB_SYS_INDEXES tables that those indexes that I thought were removed still exist in these system tables.
Is there a way to move beyond this I feel like there exists some piece of information somewhere whether it be in the file system or in the database itself that needs to be refreshed or removed so that I can move forward...thoughts?
I have received this message many times while using 3rd party tools to create tables and then constrain against existing tables. It's either one of two things:
The int columns have different sizes
The int columns have different flags (sans AUTO_INCREMENT)
As an example, I created a table with a tool that somehow created a column as INT(10) instead of the expected INT(11). Even though I just chose INT when creating both, it was messed up - never tracked down why.
Long story short, it's generally best to explicitly state the INT size when creating a table.
In your case, the following should work:
create table users (id int(11) not null auto_increment
, username varchar(255) NOT NULL
, password varchar(255) NOT NULL
, active int NOT NULL
, PRIMARY KEY (id))
ENGINE=InnoDB COLLATE=utf8_unicode_ci;
create table athing (id int(11) not null auto_increment
, name varchar(255) not null
, status varchar(255) not null
, created_by_user_id int(11) not null
, PRIMARY KEY (id)
, CONSTRAINT athing_fk1 FOREIGN KEY (created_by_user_id) REFERENCES users (id)
) ENGINE=InnoDB COLLATE=utf8_unicode_ci;
In my case, I received this error when my SQL script for creating the datastructure contained a foreign key that referenced a table that was not yet created. Moving the creating of the referencing table after creating the target table was the solution.

Mysql Foreign Key Usage

I'm trying my first hand at creating a mysql database for a simple blog. I'm having trouble understanding foreign keys and their appropriate relations. If someone can explain in "layman's" terms I'll be very happy.
I have a table called users that has the basics of fields (username, email, password etc) which I've created a user_type field and set it to INT. I've created the corresponding table called user_type and added two fields (one being the type_id = primary key and the other been the type = VARCHAR).
My question is:
Am I correct in understanding that I connect the two tables together by setting the foreign key link from the user_type INT in the users table to reference the type_id from the user_type table?
Your understanding is correct.
From SQL FOREIGN KEY Constraint
A FOREIGN KEY in one table points to a PRIMARY KEY in another table.
So in your example, the user_type id in table user_types would be the primary key, and the user_type int in table users would be the foreign key entry.
This enforces that an entry in table user_types has to exist before it can be used in table users.
You referencing from user to usertype:
n users have one user_type
If you create the table with an sql statement it should include something like this in the user part:
DROP TABLE IF EXISTS `user` ;
CREATE TABLE IF NOT EXISTS `user` (
`ID` INT NOT NULL AUTO_INCREMENT ,
`username` VARCHAR(55) NOT NULL ,
`email` VARCHAR(55) NOT NULL ,
`password` VARCHAR(55) NOT NULL ,
`user_type` INT NOT NULL ,
PRIMARY KEY (`ID`) ,
INDEX `user_to_usertype_idx` (`user_type` ASC) ,
CONSTRAINT `user_to_usertype`
FOREIGN KEY (`user_type` )
REFERENCES `user_type` (`type_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
You have to create user_type before you create user, otherwise you will get a failure.

MySQL: optimization of table (indexing, foreign key) with no primary keys

Each member has 0 or more orders. Each order contains at least 1 item.
memberid - varchar, not integer - that's OK (please do not mention that's not very good, I can't change it).
So, thera 3 tables: members, orders and order_items. Orders and order_items are below:
CREATE TABLE `orders` (
`orderid` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`memberid` VARCHAR( 20 ),
`Time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
`info` VARCHAR( 3200 ) NULL ,
PRIMARY KEY (orderid) ,
FOREIGN KEY (memberid) REFERENCES members(memberid)
) ENGINE = InnoDB;
CREATE TABLE `order_items` (
`orderid` INT(11) UNSIGNED NOT NULL,
`item_number_in_cart` tinyint(1) NOT NULL , --- 5 items in cart= 5 rows
`price` DECIMAL (6,2) NOT NULL,
FOREIGN KEY (orderid) REFERENCES orders(orderid)
) ENGINE = InnoDB;
So, order_items table looks like:
orderid - item_number_in_cart - price:
...
1000456 - 1 - 24.99
1000456 - 2 - 39.99
1000456 - 3 - 4.99
1000456 - 4 - 17.97
1000457 - 1 - 20.00
1000458 - 1 - 99.99
1000459 - 1 - 2.99
1000459 - 2 - 69.99
1000460 - 1 - 4.99
...
As you see, order_items table has no primary keys (and I think there is no sense to create an auto_increment id for this table, because once we want to extract data, we always extract it as WHERE orderid='1000456' order by item_number_in_card asc - the whole block, id woudn't be helpful in queries).
Once data is inserted into order_items, it's not UPDATEd, just SELECTed.
The questions are:
I think it's a good idea to put index on item_number_in_cart. Could anybody please confirm that?
Is there anything else I have to do with order_items to increase the performance, or that looks pretty good? I could miss something because I'm a newbie.
Thank you in advance.
Primary keys can span multiple columns. You can't use the PRIMARY attribute of columns to do this, but you can define a separate primary key with multiple columns:
CREATE TABLE `order_items` (
`orderid` INT(11) UNSIGNED NOT NULL,
`item_number_in_cart` tinyint(1) NOT NULL , --- 5 items in cart= 5 rows
`price` DECIMAL (6,2) NOT NULL,
PRIMARY KEY (orderid, item_number_in_cart),
FOREIGN KEY (orderid) REFERENCES orders(orderid)
) ENGINE = InnoDB;
Moreover, a primary key is simply a unique key where every column is not null with a certain name; you can create your own unique keys on non-nullable columns to get the same effects.
You'll not likely get much of a performance improvement by indexing item_number_in_cart; as the number of line items for a given order will tend to be small, sorting by item_number_in_cart won't take much time or memory. However, including the column in a primary key will help with data consistency.
Index on item_number_in_cart won't be used. It's tiny int, not selective enough, and won't even considered by the engine once you have 2 records. You can add it as a second column to the existing index on orderid (since you created FK constraint on orderid, mysql automatically adds an index on this field).
You say that data in order_items never updated, but I think it can be deleted; doing so without primary key will be problematic.
Well I'd be having an autoinc anyway, as I'm a big believer in surrogate keys, but as suggested by alex07 an index, or even primary key of orderid,item_number_in_cart should sort things out. Note the order by item_number will be using a two pass sort, (get the data and then sort it in the number order) so an index / key will chop that out straight off so you'd want that index even with a surrogate key.

How do you create a constraint on parent tables that also constrains the child tables?

I am not sure how to phrase the question so I'll illustrate the tables and the explain what I want to achieve.
-- static table of the entity classes supported by the application
create table entity_type (
id integer not null auto_increment,
name varchar(30) not null,
primary key(id)
);
-- static table of statuses supported by the application
create table entity_status (
id integer not null auto_increment,
name varchar(30) not null,
primary key(id)
);
-- table of valid combinations
create table entity_type_entity_status_link (
entity_type_id integer not null,
entity_status_id integer not null,
unique key(entity_type_id, entity_status_id),
foreign key(entity_type_id) references entity_type(id),
foreign key(entity_status_id) references entity_status(id),
);
-- The tables where user types and statuses are defined
create table user_type (
id integer not null auto_increment,
name varchar(30) not null,
entity_type_id integer not null,
primary key(id),
foreign key(entity_type_id) references entity_type(id)
);
create table user_status (
id integer not null auto_increment,
name varchar(30) not null,
entity_status_id integer not null,
primary key(id),
foreign key(entity_status_id) references entity_status(id)
);
-- table of valid pairs
create table user_type_user_status_link (
user_type_id integer not null,
user_status_id integer not null,
unique key(user_type_id, user_status_id),
foreign key(user_type_id) references user_type(id),
foreign key(user_status_id) references user_status(id),
);
The basic premise behind these tables is that the system supports core types and statuses and the user is able to create their own user types and statues that derive from these.
The question I have is that I cannot see a way of creating any database constraints on the user_type_user_status_link table to ensure that the you cannot insert a file_type - file_status pair where the parent entity_type - entity_status is itself not valid. Or is this something that would have to be done with triggers.
The basic premise behind these tables is that the system supports core
types and statuses and the user is able to create their own user types
and statues that derive from these.
Although that sounds like a laudable goal on the surface, the effect is to delegate database design to your users. Database design, because the effect of your desire to set foreign key references to a subset of the rows in entity_type_entity_status_link means each of those subsets is a defacto, unnamed table.
This approach never ends well.
What you've developed is the "One True Lookup Table". Google that for a host of reasons why OTLT is an anti-pattern.
The best solution is to model real things in your tables. (Entity isn't a real thing. It's an abstraction of a real thing.) Something along the lines of either
create table file_status (
file_status varchar(30) primary key
);
or
create table file_status (
file_status_id integer primary key,
file_status varchar(30) not null unique
);
would work well for file statuses.
In the case of the second one, you can set a foreign key reference to either the id number (saves space, requires an additional join) or to the status text (takes more space, eliminates a join). Note that you need the unique constraint on the status text; your original design allows the user to enter the same text multiple times. (You could end up with 30 rows where entity_type.name is 'File'.
You should use triggers for that.
MySQL does not support constraints of the form that will prevent what you want.