Why should I create a MySql table containing index only? - mysql

I'm following a Java Spring tutorial to learn some basic information about the secure login in a web app.
In this tutorial, the author has created 3 MySql table to manage the authentication:
CREATE TABLE `roles` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`role` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
CREATE TABLE `users` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`login` varchar(20) NOT NULL,
`password` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
CREATE TABLE `user_roles` (
`user_id` int(6) NOT NULL,
`role_id` int(6) NOT NULL,
KEY `user` (`user_id`),
KEY `role` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The table "roles" contains the user role (for example "Admin", "User" etc...).
The table "users" contains the user login and password.
So, I can't understand why the table "user_roles" has been created! The relation between "roles" and "users" is one-to-one, so I could insert an index for these 2 tables and delete the "user_roles"...is it right?
Why should I need to join the tables "users --> user_roles --> roles" instead of "users --> roles" ?
Thanks in advance :)

For a 1:1 relationship, have just one table. (There are exceptions, but I don't see any reason for such here.)
If you have many users in each role but a user is in only one role, then add role_id to the Users table. This is 1:many. You may need INDEX(role_id).
If a user can have many roles and each role can have many users, then you need many:many. And this would be the optimal way to write the 3rd table:
CREATE TABLE `user_roles` (
`user_id` int(6) NOT NULL,
`role_id` int(6) NOT NULL,
PRIMARY KEY (`user_id`, role_id),
KEY (`role_id`, user_id)
) ENGINE=InnoDB DEFAULT;
In some sense, that is an index-only table, since the PRIMARY KEY contains all the fields.
The (6) in INT(6) is meaningless. In particular, it does not give you 6-digit integers, it still gives you 4-byte signed integers up to 2 billion. Perhaps you should use MEDIUMINT UNSIGNED for values of 0..16M. Or SMALLINT UNSIGNED for 2-byte values of 0..64K.

Related

How MySQL one to many and one to one relationships are defined?

I was referring these to Hibernate tuts: 1, 2.
I was not able to understand how one to one and one to many relationships are defined in MySQL tables.
This is SQL for one to many relationship:
CREATE TABLE `Cart` (
`cart_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`total` decimal(10,0) NOT NULL,
`name` varchar(10) DEFAULT NULL,
PRIMARY KEY (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
CREATE TABLE `Items` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`cart_id` int(11) unsigned NOT NULL,
`item_id` varchar(10) NOT NULL,
`item_total` decimal(10,0) NOT NULL,
`quantity` int(3) NOT NULL,
PRIMARY KEY (`id`),
KEY `cart_id` (`cart_id`),
CONSTRAINT `items_ibfk_1` FOREIGN KEY (`cart_id`) REFERENCES `Cart` (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
This is SQL for one to one relationship:
-- Create Transaction Table
CREATE TABLE `Transaction` (
`txn_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`txn_date` date NOT NULL,
`txn_total` decimal(10,0) NOT NULL,
PRIMARY KEY (`txn_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
-- Create Customer table
CREATE TABLE `Customer` (
`txn_id` int(11) unsigned NOT NULL,
`cust_name` varchar(20) NOT NULL DEFAULT '',
`cust_email` varchar(20) DEFAULT NULL,
`cust_address` varchar(50) NOT NULL DEFAULT '',
PRIMARY KEY (`txn_id`),
CONSTRAINT `customer_ibfk_1` FOREIGN KEY (`txn_id`) REFERENCES `Transaction` (`txn_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
If eyes are ok, I dont see any difference between two. Is it like this relationship cardinality constraints are implemented only at hibernate level and are not enforced by database? Or my eyes are missing something?
It's actually possible to define 1:1 relationships in SQL. There are two ways:
The child table has the same PK as the parent table, with the same values. This column is also an FK to the parent table.
The child table has a different PK. It also has a FK that points to the parent table, and this FK has a UNIQUE constraint.
If you noticed, in both cases the FK is UNIQUE (it's the PK, or has a UNIQUE constraint), and that's the key aspect. It's not possible the create a second row in the child table that has the same parent.
The case you included in your question opted for strategy #1.

Table "Products" with predefined products, user can customize the price. How to avoid data redundancy?

I've been thinking on this problem for fews days and I still can't find a way to do what I want.
Below is how my database is currently designed (it's where I'm stuck) :
This is what I want :
a User can create multiple PriceSheets. A User can give a PriceSheet any name he wants. There are two PriceSheets types : "Lab Fulfillment", or "Self Fulfillment".
if the User chooses "Lab Fulfillment", he can import all or part of the Products of one of the predefined Labs. (I rephrase : there are few Labs that come with a predefined list of Products). The User will only be able to customize the price. He can't add custom products to this PriceSheet.
if the User chooses "Self Fulfillment", he can add his own products, and can personalize each field (name, cost, price, dimension_h, dimension_l).
I don't know how to link the tables between them. If I put the predefined Products in the Products table and set a Many-to-Many relationship between PriceSheets and Product, the default price of a predefined Product will be overwritten when a User customizes it, which is not what I want.
Also, I want the default values of my predefined Products to be only once in my database. If 100 users uses the predefined Products, I don't want the default cost to be in my database 100 times.
Don't hesitate to ask for precisions, I had trouble making this question clear and I think it's still not totaly clear.
Thanks in advance for your help
OK, database normalization 101. Lots of ways to do this, would take me a day to really optimize all this, this should help:
User
Lab
Product
id name cost dimension .....
1 a
2 b
3 c
4 d
So those three tables are fine. All your products will go in the Product table. No foreign keys in any of those tables.
PriceSheet
user_id custom_price product_id type
1 1.99 1 lab-fulfillment
0 NULL 2 self-fulfillment
1 5.99 3 lab-fulfillment
So a user can have as many price sheets as they want, and they can only adjust the price of a product. This can actually be normalized further if you so wish:
PriceSheet (composite key on id, user_id, FK user_id)
id user_id
0 0
1 1
2 1
LabPriceSheet (you could add an id, might be better, or you could use a composite key, stricter)
PriceSheet_id custom_price lab_product_id
0 1.99 0
2 5.99 1
CustomPriceSheet
PriceSheet_id custom_product_id
1 0
With foreign keys as appropriate. This now makes MySQL restrict the custom_price, rather than in PHP (although you would still have to deal with ensuring correct INSERT!).
Now, to deal with who adds the products:
CustomProduct
id user_id product_id timestamp
0 3 2 ...
LabProduct
id lab_id product_id timestamp
0 0 1 ...
1 0 3 ...
So let's double check:
This is what I want :
a User can create multiple PriceSheets. check A User can give a PriceSheet
any name he wants. check There are two PriceSheets types : "Lab
Fulfillment", or "Self Fulfillment". check
if the User chooses "Lab Fulfillment", he can import all or part of the Products of one of the predefined Labs. (I rephrase : there are few Labs that come with a predefined list of Products). The User will only be able to customize the price. He can't add custom products to this PriceSheet.
Yup, because he would create a LabPriceSheet that can only add lab_product_id. Custom price is there too, that overrides the default price in product table.
if the User chooses "Self Fulfillment", he can add his own products, and can personalize each field (name, cost, price, dimension_h, dimension_l).
Yup, he would add a product (you would need to check if a similar one exists, else return the id of the existing product in the product table), and then that would also be an entry in CustomProduct.
I don't know how to link the tables between them. If I put the predefined Products in the Products table and set a Many-to-Many relationship between PriceSheets and Product, the default price of a predefined Product will be overwritten when a User customizes it, which is not what I want.
Yeah that won't happen :) Never (very very rarely) implement many-many rels.
Also, I want the default values of my predefined Products to be only
once in my database. If 100 users uses the predefined Products, I
don't want the default cost to be in my database 100 times.
Of course.
Let me know if you want the MySQL code, I assume you're good! Remember to use InnoDB and properly configure your MySQL configuration!
EDIT
I felt like helping you out with a copy and paste thing. I like copy and paste things. Also, there's a redundant user_id column in the blurb above which I fixed in an earlier edit.
SET GLOBAL innodb_file_per_table = 1;
SET GLOBAL general_log = 'OFF';
SET FOREIGN_KEY_CHECKS=1;
SET GLOBAL character_set_server = utf8mb4;
SET NAMES utf8mb4;
CREATE DATABASE SO; USE SO;
ALTER DATABASE SO CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
CREATE TABLE `User` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`email` VARCHAR(555) NOT NULL,
`password` VARBINARY(200) NOT NULL,
`username` VARCHAR(100) NOT NULL,
`role` INT(2) NOT NULL,
`active` TINYINT(1) NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `Lab` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(1000) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `Product` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(1000) NOT NULL,
`password` VARBINARY(200) NOT NULL,
`cost` DECIMAL(10, 2) NOT NULL,
`price` DECIMAL(10, 2) NOT NULL,
`height` DECIMAL(15, 5) NOT NULL,
`length` DECIMAL(15, 5) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `CustomProduct` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`user` BIGINT(20) UNSIGNED NOT NULL,
`product` BIGINT(20) UNSIGNED NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user`) REFERENCES `User`(`id`),
FOREIGN KEY (`product`) REFERENCES `Product`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `LabProduct` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`lab` BIGINT(20) UNSIGNED NOT NULL,
`product` BIGINT(20) UNSIGNED NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`lab`) REFERENCES `Lab`(`id`),
FOREIGN KEY (`product`) REFERENCES `Product`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `PriceSheet` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(1000) NOT NULL,
`user` BIGINT(20) UNSIGNED NOT NULL,
PRIMARY KEY (`id`,`user`),
FOREIGN KEY (`user`) REFERENCES `User`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `LabPriceSheet` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`price_sheet` BIGINT(20) UNSIGNED NOT NULL,
`lab_product` BIGINT(20) UNSIGNED NOT NULL,
`custom_price` DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`price_sheet`) REFERENCES `PriceSheet`(`id`),
FOREIGN KEY (`lab_product`) REFERENCES `LabProduct`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `CustomPriceSheet` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`price_sheet` BIGINT(20) UNSIGNED NOT NULL,
`custom_product` BIGINT(20) UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`price_sheet`) REFERENCES `PriceSheet`(`id`),
FOREIGN KEY (`custom_product`) REFERENCES `CustomProduct`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

How do I add a foreign key to a table in Sequel Pro?

I am trying to add a foreign key to a table in Sequel Pro (using the UI).
I have two tables: "titles" and "categories" as below:
CREATE TABLE `titles` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` tinytext NOT NULL,
`category` varchar(256) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `category` (
`key` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(256) NOT NULL DEFAULT '',
PRIMARY KEY (`key`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
I want to create a foreign key, but nothing I try works.
The category table should be a simple lookup table. I want to assign each title a category from about 6 - 8 different choices.
Originally I had the category fields as tinytext, but I would get the error:
"MySQL Error 1170 (42000): BLOB/TEXT Column Used in Key Specification Without a Key Length".
Searched here and discovered you can't use text field that way, so I switched to Varchar and added a length of 256. Now I get:
MySQL said: Can't create table 'lit.#sql-2bf3_2' (errno: 150).
How can I create a foreign key for my table?
In Access this is pretty easily done. Somehow Access associates the unique key in the table with the lookup, but then hides the key and shows you the text field instead. How can I get a similar result with Sequel Pro and MySQL?
EDIT:
So, to clarify this is where I'm at right now. I've added an index on the category field in the titles table (first picture).
I've changed the "key" field in the category table to CategoryID (second picture).
However, I still can't seem to create the relationship between the two tables. I get the same error
As category will be your lookup table off of titles, you'd need to create an index on category which would refer to the foreign key. They would both need to be the same datatype (usually an INT, though sometimes you could use a CHAR(2) variable in some cases, but usually not necessary). Since you only expect 6-8 categories, I'd make it INT(1) (or may be INT(2) to be safe).
In this case, you would need to create something like categoryId which would first need to be indexed, then connect to the foreign key on categorywhich does not appear to exist; I'm not sure you want to use a term like key. Why not just make categoryId the primary key on category? this way when you create the foreign key on titles with the same name, it should link up fine.
Edit:
To clarify a little, after you've created categoryID on category you can do this under titles
ALTER TABLE Orders
ADD CONSTRAINT fk_categoryID
FOREIGN KEY (`categoryId`) REFERENCES `category`(`categoryId`)
Edit:
Here's a modification using your original layout. this should work for you:
CREATE TABLE IF NOT EXISTS `category` (
`key` int(2) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(256) NOT NULL DEFAULT '',
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
CREATE TABLE IF NOT EXISTS `titles` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` tinytext NOT NULL,
`categoryID` int(2) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `categoryID` (`categoryID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
ALTER TABLE `titles`
ADD CONSTRAINT `titles_ibfk_1` FOREIGN KEY (`categoryID`) REFERENCES `category` (`key`);

Improving Database Design for a Notification System in MySQL

I'm in the process of building a common notification system for a webapp. The main technologies I'm using are Java, Spring MVC and Hibernate. I've been looking at several posts here and there, trying to come up with the solution that suits me best, taking into account recommended practices.
I've already coded my database tables and would like to receive some feedback in order to
improve my design to avoid big changes while I'm implementing my Java classes. My goal is to make it as complete, scalable and optimal as possible mantaining the complexity to the minimum possible.
Here's my code:
NOTIFICATION SAMPLE:
The #user added a new comment.
The user [USER_ID] [USER_ACTION] a new [OBJECT].
>>> Updates >>>
[05/02/14]
Fields id_recipient and seen removed from notification table. (#Kombajn zbożowy)
New table notification_user created. (#Kombajn zbożowy)
Lowercase identifiers. (#wildplasser)
notification
CREATE TABLE notification (
id_notification BIGINT(20) NOT NULL AUTO_INCREMENT,
id_notification_type BIGINT(20) NOT NULL,
id_action_type BIGINT(20) NOT NULL,
id_sender BIGINT(20) NOT NULL,
created_date TIMESTAMP NOT NULL,
url VARCHAR(300) NULL,
PRIMARY KEY (id_notification),
FOREIGN KEY (id_notification_type) REFERENCES notification _type (id_notification_type),
FOREIGN KEY (id_action_type) REFERENCES action_type (id_action_type),
FOREIGN KEY (id_sender) REFERENCES user (id_user)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
notification_user: so that one notification can be sent to many recipients (users).
CREATE TABLE notification_user (
id_notification BIGINT(20) NOT NULL,
id_recipient BIGINT(20) NOT NULL,
seen TINYINT(1) DEFAULT 0,
PRIMARY KEY (id_notification , id_recipient),
FOREIGN KEY (id_notification) REFERENCES notification (id_notification),
FOREIGN KEY (id_recipient) REFERENCES user (id_user)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
notification_type: refers to the type of object that was modified by the actions of a certain user. Example: comment, post, etc.
CREATE TABLE notification_type (
id_notification_type BIGINT(20) NOT NULL AUTO_INCREMENT,
notification_name VARCHAR(100) NOT NULL,
description VARCHAR(300) NULL,
PRIMARY KEY (id_notification_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
action_type: actions executed by the users which trigger the notifications. Typically: update, add, remove, etc.
CREATE TABLE action_type (
id_action_type BIGINT(20) NOT NULL AUTO_INCREMENT,
action_name VARCHAR(100) NOT NULL,
PRIMARY KEY (id_action_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

How to delete two linked rows from MySQL database?

Can somebody show my how to delete two linked rows?
I use Delphi 2007 and MySQL.
I have a database with two tables:
CREATE TABLE `Picture`.`Picture` (
`ID` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`IMG` LONGBLOB,
PRIMARY KEY (`ID`)
)
ENGINE = InnoDB;
CREATE TABLE `contacts` (
`ID` int(10) unsigned NOT NULL auto_increment,
`FirstName` varchar(45) NOT NULL,
`LastName` varchar(45) NOT NULL,
`Phone` varchar(45) default NULL,
`PICID` int(10) unsigned default NULL,
PRIMARY KEY (`ID`),
KEY `FK_contacts_1` (`PICID`),
CONSTRAINT `FK_contacts_1` FOREIGN KEY (`PICID`) REFERENCES `picture` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
In my Delphi Application I have Delete button. When I find some contact on my DBGrid and press Delete button I can delete only contact from table contacts, I also want to delete contact picture.
I want to delete row from table contacts and row from table Picture. Table Picture is linked to table contacts with foreign key.
Use FK with ON DELETE CASCADE - http://dev.mysql.com/doc/refman/5.5/en/innodb-foreign-key-constraints.html
When you delete a row from the master table Picture SQL engine cascades to detail table and deletes there too.