How to make a column have a unique value accross other columns - mysql

Let's say I have a User table with a username column that has a unique constraint. Now I need to add an alias column that must also be unique, but with the added requirement that no two users can have the same username and alias (user1.username <> user2.alias). How would I go about doing this with MySQL?
I know about composite unique indices, but they check against a combination of username and alias being duplicated, not a combination of one user's username being equal to the new user's alias.

The relational way would be to make another table user_names where each user can have one or many rows in that table.
CREATE TABLE user_name (
user_id INT NOT NULL,
user_name VARCHAR(16) NOT NULL,
PRIMARY KEY (user_id, user_name),
UNIQUE KEY (user_name)
);
The UNIQUE KEY enforces uniqueness across all users.
The composite PRIMARY KEY makes it efficient to join from the users table to the clustered index of user_names.

I would go for a trigger on that table. The specifics you find here --> https://www.siteground.com/kb/mysql-triggers-use/.
Basically you are going to perform your check before every single insert. Not quite a friend of performance, but that would give you what you need.
CREATE TRIGGER aliascheck BEFORE INSERT ON User FOR EACH ROW IF NEW.alias in (select username from User) THEN SIGNAL SQLSTATE '45000' set message_text='Alias already exists!'; END IF;
... Or something like that. I have a TSQL background :)

Related

How to make data in a table unable to be used (i.e. disallow to perform query on it)?

I have 2 tables in a database, users and users_removed with columns "id(primary key), email(unique), password" and "id, user_id(foreign key (user_id) references users(id)" respectively.
When a user registers the users table gets the data accordingly. And when the user wants to delete account I can get user's id in users_removed and consider it deleted such as
INSERT into users_removed (user_id)
VALUES ((SELECT id FROM users WHERE email = 'user#example.com'))
The id from users gets inserted into users_removed with a foreign key constraint.
Now the question is what will be the right way to get rid of data from users with that id but preserve it somehow.
Deleting entirely is not an option because I loose data and so the purpose of the table users_removed. Also if I delete I get error "Cannot delete or update a parent row: a foreign key constraint fails" because of the foreign key constraint.
The user should be able to re-register with previous email but considering it an entirely new entry, as email in users is unique.
Is there a way in sql to make certain data unable to be used, disallow to perform query on it, such as it gets ignored when I perform query in the backend.
Or what could be the possible ways to the solution?
I have a way of restricting users_removed to be able to login, but how should I proceed with the registration thing.
As mysql doesn't allow rerecly to use a select in the INSERT and delete from the same table, you must corcumvent ideally in a programming language out side of mysql.
I used here a seperate SELCT with a user defined variable, to get first the user_id
CREATE TABLE users (id int AUTO_INCREMENT PRIMARY key, email varchar(100) UNIQUE)
CREATE TABLE users_removed (id int AUTO_INCREMENT PRIMARY key,user_id int)
INSERT INTO users (email) VALUES ('user#example.com')
CREATE TRIGGER after_users_removed_insert
AFTER INSERT
ON users_removed FOR EACH ROW
BEGIN
IF NEW.user_id IS NOT NULL THEN
DELETE FROM users WHERE id = new.user_id;
END IF;
END
SELECT id INTO #user_id FROM users WHERE email = 'user#example.com' ;
INSERT into users_removed (user_id)
VALUES (#user_id)
SELECT * FROM users
id
email
INSERT INTO users (email) VALUES ('user#example.com')
SELECT * FROM users
id
email
2
user#example.com
fiddle
IMHO it will better to add two fields (IsDeleted, DeletedAt) to the users table.
CREATE TABLE usersss (
id int unsigned NOT NULL auto_increment,
email varchar(100),
IsDeleted tinyInt default 0,
DeletedAt datetime,
PRIMARY KEY (id),
UNIQUE KEY uk_email (email, IsDeleted),
KEY email (email),
KEY IsDeleted (IsDeleted)
);
You will include (IsDeleted=0) within your conditions in every query that deals with users.
when the user wants to delete account, You will set IsDeleted to 1 and DeletedAt to NOW().
UPDATE users SET IsDeleted=1 AND DeletedAt = NOW() WHERE id=$ID;
To make user able to re-register with previous email you will check for a unique index on fields (email, IsDeleted) not on (email).
SELECT COUNT(id) FROM users WHERE (IsDeleted=0) AND (email = '$email');
If query returns (0) then the user can use that email.
you may remove the data of users with IsDeleted=1 after a specified period of time has elapsed from the date of deletion (DeletedAt).
Example :
If you want to remove user and its data after one year, to get users you will remove :
SELECT id FROM users WHERE TO_DAYS(NOW()) >= (TO_DAYS(DeletedAt) + 365) ;
then you will delete data from related tables for these users.

MySQL Circular References in One-to-Many Relation: Is there a way to avoid it?

Consider following as an example:
I have a User table which contains user's information along with a PrimaryAddress column that references Address table.
The Address table, contains address information along with a UserId column which refers to who the address belongs to.
Each user can have many addresses, but only one address can be PrimaryAddress. Therefore, the User table needs to store a reference to PrimaryAddress to enforce this rule. Having a IsPrimary column in address table would not have a similar effect.
But as you can tell, this will create a circular relation between User and Address tables and circular dependencies can be a sign of bad design as far as I'm aware. The question is, is there a way to avoid this? If so, how?
A circular reference is not necessarily a "bad" design. You gave an example of a real-world case that has legitimate meaning.
I admit it's a little bit complex to manage. The User.PrimaryAddress must be nullable, if you need to create a user row before you create the address you will eventually designate as the primary address.
Also if you need an SQL script to recreate the database, you can add foreign key constraints only after the referenced table is created. So you have a chicken-and-egg problem if you have circular references. The typical solution is to create tables without their foreign keys, then once they are created, add the foreign keys using ALTER TABLE.
The workarounds all sacrifice something. If you add an attribute Address.IsPrimary, then you have to figure out how to ensure that exactly one address per user is primary. If you use a third table, you have to worry that it is missing a row for each user.
The circular reference may be the least problematic solution.
no you would use a bridge4 table in combination with a BEFORE INSERT TRIGGER
This will also help, if a address can have multiple users
That would then look like
CREATE tABLE user (id int PRIMARY KEY)
INSERT INTO user VALUEs(1)
CREATE TABLe address(id int PRIMARY KEY)
INSERt INTO address VALUES(1),(2)
CREATE TABLE user_address ( user_id int, address_id int, Is_primary int
,FOREIGN KEY (user_id)
REFERENCES user(id)
,FOREIGN KEY (address_id)
REFERENCES address(id)
,PRIMARY KEY (user_id,address_id)
)
CREATE TRIGGER before_user_address_insert
BEFORE INSERT
ON user_address FOR EACH ROW
BEGIN
DECLARE rowcount INT;
IF NEW.Is_primary = 1 then
SELECT COUNT(*)
INTO rowcount
FROM user_address WHERE user_id = NEw.user_id AND Is_primary = 1;
IF rowcount = 1 THEN
set #message_text = CONCAT('userid ',New.user_id ,' has already a primary address');
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = #message_text;
END IF;
end if;
END
INSERT INTO user_address VALUEs(1,1,1)
✓
INSERT INTO user_address VALUEs(1,2,1)
userid 1 has already a primary address
db<>fiddle here

What would be proper schema for the following situation?

I need to create a database table in which I have the following three fields mainly:
ID(default auto incrementing).
User_id( starting from 1000 ,auto incrementing)
Email
So, I want to set Email to be unique ,not null( Primary key ),but also I want to make sure that User_id remains unique ( that can be solved by setting unique key ) and I also want it to never be set as null. So, how I can achieve this? I am using MySQL BTW.
PS: I am not much good to this schema building for the current time. So, ignore any silly mistakes.
I would go for something like this
CREATE TABLE USERS
(
ID INT PRIMARY KEY AUTO_INCREMENT,
User_Id INT AUTO_INCREMENT = 1000,
Email VARCHAR(250) NOT NULL
)
And then
ALTER TABLE USERS ADD UNIQUE (Email)
Or if you intend to search on the Email field
CREATE UNIQUE INDEX inx_users_email ON USERS (Email)
You are better of using the Id field as your primary key. The PK is not meant to change and is used to identify the record. What happens if your user changes email address?
Auto_INCREMENT ensures column has unique values and can never be null.

Updating same table in stored procedure after UPDATE and INSERT

RELATIONSHIP : students (1 can have N) addresses
SCENARIO: Students can have many records but only one associated record must have 'current' field set as 'Yes' (other value is NULL) so the query below should always return only one record per student.
SELECT * FROM address WHERE student_id = 5 AND current = 'Yes'
PROBLEM:
People sometimes mark more than one record as 'Yes' after INSERT or UPDATE for same student so I need to avoid it. What is the best way of doing it by using triggers or stored procedures within MySQL?
If UPDATE happens on 'address' table then this should run somewhere to mark other records as NULL: UPDATE addresses SET current = NULL WHERE student_id = IN_student_id
If INSERT happens on 'address' table then this should run somewhere to mark other records as NULL: UPDATE addresses SET current = NULL WHERE student_id = IN_student_id AND id <> IN_inserted_id
Thanks in advance
If you need something updated automatically after data is modified, the right approach is a trigger. Notice a trigger may call a stored procedure.
However you will not be able to implement the described behaviour in a trigger because:
A stored function or trigger cannot modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger.
In fact, the information "Address X is the current address" should be stored in a column in the students table, as a foreign key to the address table. Therefore, unicity is guaranteed.
Something like this (fiddle with it here):
CREATE TABLE student (
id INT NOT NULL PRIMARY KEY,
current_address INT,
name VARCHAR(20)
);
CREATE TABLE address (
id INT NOT NULL PRIMARY KEY,
student_id INT NOT NULL,
contents VARCHAR(50) NOT NULL,
CONSTRAINT student_fk FOREIGN KEY student_fk_idx (student_id)
REFERENCES student(id)
);
ALTER TABLE student
ADD CONSTRAINT curraddr_fk_idx
FOREIGN KEY curraddr_fk_idx (id, current_address)
REFERENCES address(student_id, id);
Notice this structure allows insertion of students with no "current address". This is because at least one for the two tables must allow a NULL value for their foreign key (or else we cannot add a single row in either table). If it makes more sense, let address.student_id be NULL instead, and allow an address to be nobody's address until you create the corresponding student.

Trying to understand what this MySQL table means

I have this table:
create table user_roles
(
user_id INT UNSIGNED NOT NULL,
role_id INT UNSIGNED NOT NULL,
UNIQUE (user_id, role_id),
INDEX(id)
);
user_id and role_id are primary keys of other tables, namely user and roles. What does UNIQUE (user_id, role_id), mean? Does it mean that all user_id/role_id pairs have to be unique?
And what is INDEX(user_id) for?
UNIQUE does indeed ensure that all values that go into the fields differ from the previous.
INDEX(user_id) will create an index on the user_id field so that it is better indexed, i.e. it can be searched through faster.
Without an index, MySQL must begin with the first row and then read through the entire table to find the relevant rows.
http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html
Yes, UNIQUE(user_id, role_id) means that the combination of those two fields for any particular row cannot exist in the table more than once.
INDEX(user_id) applies an index on the user_id column, making searching on that column very fast (at some small performance expense when inserting/updating to the table)
Yes it means that user_id/role_id pairs are unique.