Creating tables for user authentication - room for improvements? - mysql

At the moment I'm developing a web application. For that, I need to create a database for user authentication.
I have something like the following in mind:
create table users
(
id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
username VARCHAR(100) NOT NULL,
password VARCHAR(60) NOT NULL,
PRIMARY_KEY(id),
UNIQUE(username)
);
create table roles
(
id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
role VARCHAR(100) NOT NULL,
PRIMARY_KEY(id),
UNIQUE(role)
);
create table user_roles
(
user_id SMALLINT UNSIGNED NOT NULL,
role_id SMALLINT UNSIGNED NOT NULL,
PRIMARY_KEY(user_id)
UNIQUE (user_id, role_id),
);
Passwords are fixed size but I use varchar because I've read somewhere that in a table where you have both char and varchar columns, the char columns get converted to varchar.
Also, would it be beneficial to use FOREIGN KEY CONSTRAINTS?

Don't use SMALLINTs for IDs. Seriously. Why would anybody do that!? Use a larger integer type.
The primary key on user_roles isn't going to work. Make (user_id, role_id) the primary key and put separate (non-unique) indexes on the fields you will be querying on.
You could add foreign keys that link user_roles to the other tables. If you use ON DELETE CASCADE, it'll even delete the stale links if you ever delete a user or a role.

Related

Is it good to use UNIQUE index?

I have a register of students, the main table is this one and I just wanna make sure there won't be students with duplicated names, emails, etc. ID is the main key so I don't need to use a PK.
CREATE TABLE student(
id INT UNSIGNED NOT NULL,
campus_id SMALLINT UNSIGNED NOT NULL,
first_career_id SMALLINT UNSIGNED NOT NULL,
second_career_id SMALLINT UNSIGNED NULL,
name VARCHAR(70) NOT NULL,
password CHAR(60) NOT NULL,
email VARCHAR(100) NOT NULL,
phone TINYINT UNSIGNED NULL,
UNIQUE(id),
UNIQUE(name),
UNIQUE(email),
UNIQUE(phone),
FOREIGN KEY(first_career_id)
REFERENCES career(id)
ON DELETE CASCADE,
FOREIGN KEY(second_career_id)
REFERENCES career(id)
ON DELETE CASCADE
) ENGINE=INNODB;
I could validate those fields when inserting them through the applicacion, which way is better?
If uniqueness is a domain constraint (which it is in your example), then create a UNIQUE constraint on the columns(s) (which creates a unique index for you).
ALTER TABLE student
ADD CONSTRAINT ux_student_email
UNIQUE (email)
If you can be 100% sure (and future proof) that no other application will insert data, then ensuring uniqueness in the application is fine. If you value correctness, add a constraint at the database level in addition to checking in the application.

Log user updates

In the making of a small community networking site, but I was seeking some advice as to how best to design a table that will store data to be pulled as a stream that shows for example:
User X has added a friend!
User Y has commented on a post!
User X changed their profile picture!
User X has changed their motto!
Currently this is my setup but before I continue wanted to know if I was on the right track
update_id int PK
member_id int FK
friend_id int FK
update_action text
update_time datetime
update_hidden tinyint
At the moment I was planning to run an extra insert query to update this table with each activity when a user does it, not sure if that is the best or in my case a good way to get this done. Any tips or advice will be appreciated thanks for your time.
You can have a table for all the different activities that your system should offer.
CREATE TABLE activity
(
id MEDIUMINT UNSIGNED PRIMARY KEY UNIQUE NOT NULL AUTO_INCREMENT,
description NVARCHAR(126) UNIQUE NOT NULL
);
For example: has commented on a post, has a new friend, etc.
And then log all the activities that happen, depending on the id of the activity (so that you can manually define where in the code which activity should be used) and store it somehow like this:
CREATE TABLE activityLog
(
id MEDIUMINT UNSIGNED PRIMARY KEY UNIQUE NOT NULL AUTO_INCREMENT,
userId UNSIGNED NOT NULL,
friendId UNSIGNED DEFAULT NULL,
acitivityId UNSIGNED NOT NULL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
hidden TINYINT(1) DEFAULT 0,
FOREIGN KEY(userId) REFERENCES users(id),
FOREIGN KEY(friendId) REFERENCES users(id),
FOREIGN KEY(acitivityId) REFERENCES activity(id)
);
Assuming that you have your users stored in a table called 'users'.
Just make sure that It's easy to create new activities, easy to link the events, etc.

Members table with one instead of three unique fields

In almost each code example by creating mysql joinUs table, there is the code like this:
CREATE TABLE `members` (
`id` int(10) unsigned NOT NULL auto_increment,
`username` text, //unique
`email` text, //unique
`pass` text,
PRIMARY KEY (`id`)
So, if username and/or email is set as unique, what is then the purpose of id field?
Can I simply set username as primary key and exclude the id from the table?
Id is a nice convention because it will never change. That lets other tables reference users via their id, and enables you to let users change their usernames and emails. Having said that, yes, you could use username as a primary 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.

How should I structure my MySQL database?

First of all, sorry if this might be a stupid question. I'm very new to the world of MySQL, so...
Anyway, my question is this: I'm planning on having a database that deals with (for now) two types of users, let's say Admins and Users. My aim is to have ONE table containing all users, aptly named "users". Below is a rough outline of my MySQL command (which I haven't tested yet so errors are likely):
CREATE TABLE users {
user_id int UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_type int NOT NULL REFERENCES user_types(user_type_id),
ssn char(10) NOT NULL,
password varchar(40) NOT NULL,
first_name varchar(30) NOT NULL,
last_name varchar(30) NOT NULL,
address varchar(80) NOT NULL
} engine = InnoDB;
The "user_type" column above will refer to another table called "user_types", which lists the different user types for the website (I'm doing this for the sake of having the option to add more user types later):
CREATE TABLE user_types {
user_type_id int UNSIGNED NOT NULL PRIMARY KEY,
user_type_desc varchar(10) NOT NULL
} engine = InnoDB;
INSERT INTO user_types (user_type_id, user_type_desc) VALUES(1,'Admin'),(2,'User');
My aim is to link "Users" with "Admins"; one "User" (child) can have one "Admin" (parent), but one "Admin" (parent) can have several "Users" associated (children). The goal for me is to create a simple appointment calendar, and for that I need to connect users with their admins (one-to-one relationships in the sense that the appointment is between one user and one admin). Now the question is:
1) Is it possible to achieve this by having ONE table for all users? If so, how do I do it in a good way? Right now I was thinking of creating a table called "assignments":
CREATE TABLE assignments {
assign_id int UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
patient_id int NOT NULL REFERENCES users(user_id),
doctor_id int NOT NULL REFERENCES users(user_id)
} engine = InnoDB;
But the above code looks strange to me; can I do that kind of foreign key linking to the same table without any dangers? Below is also the SQL 'code' for the "appointments" table:
CREATE TABLE appointments {
appointment_id int UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
assign_id int FOREIGN KEY REFERENCES assignments(assign_id),
date_time datetime NOT NULL,
description varchar(200) NOT NULL
};
That is, every entry in the "appointments" table points to a certain assignment between an "Admin" and a "User".
2) How can I achieve the one-to-many relationship between "Admins" and "Users" in an easy way, or rather, a proper way?
Any help or suggestions would be greatly appreciated, and sorry if these questions are stupid!
Your proposed assignments table would work if you had a many-to-many relationship between Users and Admins. Since you've described the relationship as 1-to-many (one Admin may have many Users), I would simply add an admin_id column to your users table and make it a self-referencing foreign key back to the users table.
CREATE TABLE users {
user_id int UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_type int NOT NULL REFERENCES user_types(user_type_id),
ssn char(10) NOT NULL,
password varchar(40) NOT NULL,
first_name varchar(30) NOT NULL,
last_name varchar(30) NOT NULL,
address varchar(80) NOT NULL,
admin_id int REFERENCES users(user_id)
} engine = InnoDB;
In the users table add admin_userid that References users(user_id)
That way, each user points back to users table to the admin user they belong to.
Using this column a doctor can list all his patients and the assignements table can be used with appointments.
But will a certain user ALWAYS get a meeting with the same doctor/admin?
What about vacations?