mysql inserting unique value for either column1 or column2? - mysql

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

Related

Mysql Storing multi values in a column

I have a tables called userAccounts userProfiles and usersearches.
Each userAccount may have multiply Profiles. Each user may have many searches.
I have the db set up working with this. However in each search there may be several user profiles.
Ie, each user account may have a profile for each member of their family.
They then want to search and include all or some of their family members in their search. The way i would kinda like it to work is have a column in user searches called profiles and basically have a list of profileID that are included in that search. (But as far as i know, you can't do this in sql)
The only way i can think i can do this is have 10 columns called profile1, profile2 ... profile10 and place each profileid into the column and 0 or null in the unused space. (but this is clearly messy )
Creating columns of the form name1...nameN is a clear violation of the Zero, One or Infinity Rule of database normalization. Arbitrarily having ten of them is not the right approach, that's an assumption that will prove to be either wildly generous or too constrained most of the time. Since you're using a relational database, try and store your data relationally.
Consider the schema:
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT NOT NULL,
name VARCHAR(255),
UNIQUE KEY index_on_name (name)
);
CREATE TABLE profiles (
id INT PRIMARY KEY AUTO_INCREMENT NOT NULL,
user_id INT NOT NULL,
name VARCHAR(255),
email VARCHAR(255),
KEY index_on_user_id (user_id)
);
With that you can create zero or more profile records as required. You can also add or remove fields from the profile records without impacting the main user records.
If you ever want to search for all profiles associated with a user:
SELECT ... FROM profiles
LEFT JOIN users ON
users.id=profiles.user_id
WHERE users.name=?
Using a simple JOIN or subquery you can easily exercise this relationship.

Alternate way to using cursor/loop in SQL

I want to see if there is any way to do what I'm trying to do without using cursors, or any looping logic. I have these 2 tables. There is a ONE-MANY relationship between FileRunData and Invoice, and they're linked by RunId/FileRunId.
CREATE TABLE [dbo].[FileRunData](
[RunId] [uniqueidentifier] primary key,
[Status] [varchar](25) NOT NULL,
[IsEmailSent] [bit] NOT NULL
)
CREATE TABLE [dbo].[Invoice](
[FileRunId] [uniqueidentifier] NULL,
[InvoiceId] [uniqueidentifier] primary key,
[InvoiceType] [varchar](20) NOT NULL,
[Status] [varchar](25) NULL
)
I want to send an email notification for the following condition.
In the FileRunData if Status='Processed' and IsEmailSent=0, then I have to check all the rows in the Invoice table for that FileRunId, and if their Status is 'Invoiced', then I have to send an email.
My approach (will be using cursor here)
select RunIds from FileRunData
for each RunId, get the number of rows for that RunId from Invoice
get the number of rows for that RunId which have Status='Invoiced'
If both 3&4 are equal, then send the email
I think by adding a couple of negations in, the problem is simpler to understand, unless I've severely misunderstood you. We don't want to send an email if there's any row in Invoice which has a status other than Invoiced:
SELECT frd.RunId
FROM FileRunData frd
WHERE frd.Status = 'Processed' AND
frd.IsEmailSent = 0 AND
NOT EXISTS (select * from Invoice i
where i.FileRunID = frd.RunID and
i.Status != 'Invoiced')
should select the IDs for runs which meet your conditions. Unfortunately, if you need to use these RunId values and send one email for each such result, then unfortunately, at this point you'll still have to use a cursor1 to process this result set and make the actual sp_send_dbmail calls.
1Or any morally equivalent way of processing each row in turn. I'd normally just use a cursor but some people have an aversion to the word even appearing in their SQL and insist on creating temp tables and while loops instead, for example.

Can I conditionally enforce a uniqueness constraint?

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)

How to have Unique IDs across two or more tables in MySQL?

I have a table called events where all new information goes. This table works as a reference for all queries for news feed(s) so event items are selected from there and information corresponding to that event is retrieved from the correct tables.
Now, here's my problem. I have E_ID's in the events table which correspond to the ID of an event in a different table, be it T_ID for tracks, S_ID for status and so on... These ID's could be the same so for the time being I just used a different auto_increment value for each table so status started on 500 tracks on 0 etc. Obviously, I don't want to do that as I have no idea yet of which table is going to have the most data in it. I would assume status would quickly exceed tracks.
The information is inserted into the event table with triggers. Here's an example of one;
BEGIN
INSERT INTO events (action, E_ID, ID)
VALUES ('has some news.', NEW.S_ID, NEW.ID);
END
That ones for he status table.
Is there an addition to that trigger I can make to ensure the NEW.S_ID != an E_ID currently in events and if it does change the S_ID accordingly.
Alternatively, is there some kind of key I can use to reference events when auto incrementing the S_ID so that the S_ID is not incremented to a value of E_ID.
Those are my thoughts, I think the latter solution would be better but I doubt it is possible or it is but would require another reference table and would be too complex.
It's really uncommon to require a unique id across tables, but here's a solution that will do it.
/* Create a single table to store unique IDs */
CREATE TABLE object_ids (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
object_type ENUM('event', ...) NOT NULL
) ENGINE=InnoDB;
/* Independent object tables do not auto-increment, and have a FK to the object_ids table */
CREATE TABLE events (
id INT UNSIGNED NOT NULL PRIMARY KEY,
...
CONSTRAINT FOREIGN KEY (id) REFERENCES object_ids (id)
) ENGINE=InnoDB;
/* When creating a new record, first insert your object type into the object_ids table */
INSERT INTO object_ids(object_type) VALUES ('event');
/* Then, get the auto-increment id. */
SET #id = LAST_INSERT_ID();
/* And finally, create your object record. */
INSERT INTO events (id, ...) VALUES (#id, ...);
Obviously, you would duplicate the structure of the events table for your other tables.
You could also just use a Universal Unique Identifier (UUID).
A UUID is designed as a number that is globally unique in space and time. Two calls to UUID() are expected to generate two different values, even if these calls are performed on two separate computers that are not connected to each other.
Please read more about it in the manual.
There's also a shorter version.
UUID_SHORT() should do the trick.
It will generate 64-bit unsigned integers for you.
According to the doc the generator logic is:
(server_id & 255) << 56
+ (server_startup_time_in_seconds << 24)
+ incremented_variable++;
The value of UUID_SHORT() is guaranteed to be unique if the following conditions hold:
The server_id value of the current server is between 0 and 255 and is unique among your set of master and slave servers
You do not set back the system time for your server host between mysqld restarts
You invoke UUID_SHORT() on average fewer than 16 million times per second between mysqld restarts
mysql> SELECT UUID_SHORT();
-> 92395783831158784
If you curious what is your server id you can use either of these:
SELECT ##server_id
SHOW VARIABLES LIKE 'server_id';

Update table on condition from another table Trigger

I have a two tables
MovieStar (
name: char(15),
address:varchar(40),
gender:char(1),
birthdate: date,
rating:float
)
StarsIn (
movieTitle:char(25),
movieYear:int,
starname:char(15),
role:varchar(15)
)
starName is foreign key which is related to name of MovieStar.
I want to increment rating by one when Starsin record is inserted and role of the new record is 'main'
What it would do is;
on insert to starsin, check whether role = 'main'
if so, what is starname
increment rating of moviestar name = starname
I would not store the value rating in your table as it can be derived from the other table's data when it is required. And why are you using float for the count?
Create a VIEW which counts the rows in Starsin, based on your condition role = 'main' and then you have no need for the trigger and the count will always be up to date when new rows are added or removed.
With the trigger solution you also have to take account of row deletion to decrement the count.
EDIT: From the comment you made, here's a sample trigger (although the syntax may be wrong as I mainly work with SQL Server). I'm assuming the name field in the MovieStar table is a primary key and therefore unique.
CREATE TRIGGER UpdateRating AFTER INSERT ON StarsIn
FOR EACH ROW
BEGIN
UPDATE MovieStar SET rating =
(SELECT rating FROM MovieStar WHERE name = NEW.starname) + 1
FROM MovieStar INNER JOIN NEW ON MoveStar.name = NEW.starname
WHERE NEW.role = 'main'
END
I'm not familiar with MySQL, I work with SQL Server, where triggers need to be set based. I'm guessing the FOR EACH ROW part of the trigger means the statements are executed for each row inserted, but I may be wrong about that.
You need to create a trigger on insert and do the intended update.