Can I conditionally enforce a uniqueness constraint? - mysql

My database contains a table of users. Every active user has a unique username. I'd like to be able to deactivate a user and free up the username they're using, but keep them in the same table.
Is there a way to only conditionally enforce the uniqueness constraint?

Add another column called something like isactive. The create a unique constraint on (username, isactive).
Then you can have both an active and inactive user name at the same time. You will not be able to have two active user names.
If you want multiple inactive names, use NULL for the value of isactive. NULL values can be repeated in a unique index.

No, a UNIQUE constraint can't be "conditional".
One option is to set the username column to NULL. The UNIQUE constraint will allow multiple rows with NULL value.
You could translate that to any string you wanted for display. either in the application, or in the SQL
SELECT IFNULL(t.username,'USER DELETED') AS username
FROM mytable t
If you are retaining these rows for historical/archive purposes, you probably do NOT want to update the username column. (If you change the value of the username column, then a subsequent statement will be allowed to insert a row with the same value as the previous username.)
You could instead add an additional column to your table, to represent the "user deleted" condition. For example:
user_deleted TINYINT(1) UNSIGNED DEFAULT 0 COMMENT 'boolean'
You could check this column and return the 'USER DELETED' constant in place of the username column whenever the user_deleted boolean is set:
SELECT IF(u.user_deleted,'USER DELETED',u.username) AS username
(Use a value of 1 to indicated a logical "user deleted" condition.)
The big advantage to this approach is that the username column does NOT have to be modified, the username value, and the UNIQUE constraint will prevent a new row with a duplicate username from being inserted.

Different way to achieve the same result. May not be really required for the question asked. But just for information.
Create a trigger on insert / update
Check if there is duplicate records found with current (NEW) records values.
a. This can be checked by counting dupicates or checking of OTHER records exists with the same values, but different primary key
If found raise a Signal to throw an error
This is best suited if your condition is complex to decide uniqueness. Also consider the performance cost.
Sample
DELIMITER $$
CREATE TRIGGER `my_trigger` BEFORE INSERT/UPDATE
ON `usertable`
FOR EACH ROW BEGIN
IF EXISTS (SELECT 1 FROM usertable WHERE userid <> NEW.userid AND username = NEW.username AND isactive = 1) THEN
SELECT CONCAT(NEW.username, ' exists !') INTO #error_text;
SIGNAL SQLSTATE '45000' SET message_text = #error_text;
END IF;
END$$
DELIMITER ;

I would just create another (non-unique) field called FORMER_NAME and move the original name to that field when a user goes inactive. No need for a special uniqueness constraint that's not possible.

Nope, if there is a unique index (hence the name) you can not have duplicates. Either add a extra column to make each record unique. Or change the value so its unique.
Not recommended but for example you could add a timestamp "USER DELETED 2013/08/17:233805"

This is my solution when I met a similar problem:
add a column inactive, so the unique key as: (username,inactive)
for inactive, inactive = 0 means a user is active, inactive > 0 means a user is active
when deactivate a user, just set inactive = user_id, not 1 as we usually did!
now it allows duplicated usernames for inactive users, but only unique usernames for active users.

I expanded on #gordon-linoff answer by adding a generated column which provides the nullable functionality. I would rather have a true not null active column that has a definitive true and false value I can use to read and write that is not confusing and won't get messed up by accidentally forgetting about this null behavior later on when writing code. So I compute a column with a specialized name and then use that value in the constraint, so I get the nullable unique active behavior but can use the active column as I wish.
isactive BOOL NOT NULL,
_isactive_constraint_key_ BOOL AS (CASE WHEN isactive IS true THEN true END),
CONSTRAINT active_user UNIQUE(username, _isactive_constraint_key)

Related

Unique constraint on tables for particular values of a field

I have an invoice table. It has many fields, but the problem is around 2 main fields
InvoiceNo - alphanumeric values
Deleted - boolean 1 or 0 ,to represent a record is deleted or not.
Our business requires InvoiceNo to be unique. However if a row is deleted we can re-use the InvoiceNo
InvoiceNo Deleted
123Er 1
123Er 0
Above is a valid use case. But i don't want to have another record with 123Er & 0.
Is it possible to create unique key on combination of 2 fields for certain values Unique (InvoiceNo, Deleted=0) or should we go for stored procedure or triggers ?
It is quite easy to achieve in other RDBMS systems with help of a function based index
As for now MySql doesn't have such a feature, but starting from version 5.7 it can be simulated with use of a virtual (or generated) column.
Simple working example: http://rextester.com/HGY68688
CREATE TABLE IF NOT EXISTS mytable1234(
InvoiceNo varchar(10),
Deleted int,
xxx varchar(10) generated always as (case when deleted = 0 then InvoiceNo end) virtual
);
create unique index myindex12 on mytable1234( xxx );
INSERT INTO mytable1234( InvoiceNo, Deleted) VALUES ('aaa1', 0 );
INSERT INTO mytable1234( InvoiceNo, Deleted) VALUES ('aaa1', 1 );
INSERT INTO mytable1234( InvoiceNo, Deleted) VALUES ('aaa1', 1 );
-- INSERT INTO mytable1234( InvoiceNo, Deleted) VALUES ('aaa1', 0 );
If you uncomment the last INSERT in this snippet, and then try to run this snippet, then you will get: Duplicate entry 'aaa1' for key 'myindex12' error.
In this way, there may be multiple records in the table with the same InvoiceNo value for deleted = 1, but only one value for deleted = 0, because MySql will not allow this.
You could rename the field something like DeletedIfNull (or IsActive).
Then, the field would take the value of "1" or "true" or whatever if the column is active. It would be NULL for any other values. Then you can create:
create unique index unq_t_invoiceno_isactive on t(invoiceno, isactive);
MySQL (although not all databases) allows repeats when a unique index is defined. Hence, this will solve your immediate problem.
You can just create a unique key (InvoiceNo, Deleted), and its done. I dont understand why you need to make (InvoiceNo, Deleted=0), which is not supported. Having a unique key of those 2 columns provide you what you need.
UPDATE:
I got your problem that you might have 2 deleted rows of same values. In this case, i would suggest you prepend something to InvoiceNo column of deleted values so that they won't be unique.
For example:
You have 123er and then delete it, so it becomes 123er-1. When you delete another one, it becomes 123er-2. You can even do it basically in an update(before) trigger.

Mysql UNIQUE Constraint that only applies if one field has specfic values

I hope this works somehow:
I have a Mysql Table with 3 column:
id = int
state = enum('visible', 'moderated', 'deleted')
user_id = int
No user should have more than 1 entry, that is 'visible' or 'moderated', but he can have endless 'deleted' entries.
So, I need a UNIQUE Key on user_id, that only applies if the state is 'visible' or 'moderated'
There are basically two options, first one is fairly simple but would require change in your table structure and application logic.
If you use NULL value instead of 'deleted', you can have as many "deleted" rows as you want for given user_id (having unique constaint on (user_id, state)). Your table structure would be something like this:
id = int
state = enum('visible', 'moderated') NULL
user_id = int
The other option would involve checking in post update/post insert triggers whether you are not breaking your contraint and throwing "sql exception" vide https://dev.mysql.com/doc/refman/5.5/en/signal.html

mysql inserting unique value for either column1 or column2?

I created a table with five columns:
userID,name,mobile_num,telphone_num,count
Either mobile or telephone number or both field must be filled in, and it should be unique. How can i create such a row in mysql?
CREATE TABLE information (
userID INT NOT NULL,
name varchar(255),
mobile_num INT UNIQUE,
telphone_num INT UNIQUE,
count INT,
PRIMARY KEY (userID)
)
the UNIQUE property forces the values to be unique, however that might be a problem for you because if two users GIVE you his mobile but DONT GIVE not their home phone, you won't be able to have two users with the value NULL, so I think the solution you are asking for might not be your best option.
The simplest way to do this is to enforce the requirement in the application code that inserts a row to the database.
This question suggests that it is not possible to enforce one of two columns not being null by a table constraint.
You could create an after insert trigger to check the requirements, but assuming the database is in the control of your application, just enforcing this requirement in the application makes most sense. The coding and error handling will be simpler.
It is not clear what you mean when you say the phone numbers "must be unique" but I would probably reconsider this requirement.
Two users could be at the same location (e.g. in the same household) and have the same phone number. So there could be duplicate phone numbers between users.
A user who uses their mobile number as their primary phone might enter the same phone number in both fields. I don't see any benefit in preventing this. Actually when this happens, you have more useful information than if you had prevented it. And trying to prevent it would probably result in a data entry interface that is confusing for the user.
CREATE TABLE information (
userID INT NOT NULL,
name varchar(255),
mobile_num INT,
telphone_num INT,
count INT,
PRIMARY KEY (userID,mobile_num,telephone_num)
)
Use the below query when face a situation to insert either any one number
insert into information(UserID,name,mobile_num,count)
values(3,'andrew',23443546,5);
or
insert into information(UserID,name,phone_num,count)
values(3,'andrew',23443546,5);
By default mysql will insert the value for other number as 0 in such situation,
So you can use 0 for the other number when you are going to insert only one number using
insert into information
values(3,'andrew',0,23443546,5);
insert into information
values(3,'andrew',23443546,0,5);
You can use a trigger:
DELIMITER //
CREATE TRIGGER checkPhoneNumbers
BEFORE INSERT ON tableName
FOR EACH ROW
BEGIN
SET #exists = (SELECT *
FROM tableName
WHERE mobile_num = NEW.mobile_num
|| mobile_num = NEW.telphone_num
|| telphone_num = NEW.telphone_num
|| telphone_num = NEW.telphone_num);
IF(#exists IS NOT NULL) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Telephone or mobile number already present!';
END IF;
END
//
DELIMITER ;
In this way you will have a db error if you insert a mobile or telephone number which is already present as mobile or telephone number

Ensure combo of values are unique, but entered any number of times - Mysql

How can I enforce constraint checks to ensure that a given combination of values are unique, but can be entered any number of times?
Example: I have two columns: Group_ID and Group_Name. So all data with Group_ID = 1 will always have Group_Name as 'Test1'. What I want to prevent is someone entering 'Test2' into Group_Name where Group_ID=1. This should fail the insert. All this data is loaded directly into the DB without any UI, hence I cannot enforce these checks in application. So what I need is:
A unique constraint over multiple columns, but only for the given combination without checking how many times they have been entered.
Is there anything built in Mysql to do this?
You should normalize your table a little bit. The group_id,group_name pair should be in a separate table that defines your groups and then the table you're working with should only have group_id. Then you could add a foreign key from your table to the group table to ensure that your group_id values reference real groups.
If you can't normalize your tables then you'll probably have to use a before insert and before update trigger to ensure that Group_ID and Group_Name always come together as required.

Unique constrain with condition MYSQL

I have User table in my DB.
A user has the fields name, company_id and status: boolean, 1- live, 0- deleted.
When a user is deleted, his status is set to 0.
The combination of a live user name in a company should be unique. After a user is deleted, I don't mind that a user should be created with the same name for the company.
My question is how do I define a uniuqe constrain for the fields name, company_id and status=1 (It's not a uniuqe constrain on those three field becuase I don't mind that the combination of name-company_id-0 will appear a few times in the table).
Thanks,
Dvora
Use NULL value for deleted users.
Unique key allows unlimited number of NULL values.
Update: Don't touch user name, NULL in status field is enough.
Which programming language you are using?
your logic shoule be as follows
select * from Table_name where name='' AND company_id = '' AND status = 1
if this return any rows give uniqueness error to the user else create it.
I would create another column to store a deleted user's previous name and set their real name to NULL when they're deleted (as well as setting the status to 0).
Then have a unique constraint on name and company. NULLs will not affect the uniqueness (since NULL != NULL) and you can still recover the user's original name if desired.
So the delete operation is something like:
update users
set prev_name = name,
name = null,
status = 0
where name = 'paxdiablo' and company = 'SmallGreen';
Would it be easier if you split "live" and "deleted" so they have their own tinyint/boolean columns ?
I would replace status field with deleted_at (datetime). When the user is active its value would be NULL, but when deleted, it would be set to current datetime.
Next, i would add unique index on username & deleted_at fields, which will allow me to delete more than one user with the same username (in at least 1 second interval).