mysql insert if not exists, concurrent sessions - mysql

I want to insert new user into users table and make sure that user's nick and email are not already in the table (InnoDB).
Here is my logic:
if (SELECT COUNT(*) FROM users WHERE nick = :nick) > 0:
return "name exists";
if (SELECT COUNT(*) FROM users WHERE email = :email) > 0:
return "email exists";
# OK to insert? Or something bad can happen here?
INSERT INTO users (nick, email) VALUES (:nick, :email)
But now I'm not sure if this is the right way. Suppose that between SELECT and INSERT query some other, concurrent connection creates new record with same nick or email (is this even possible?). Then INSERT will throw an exception and I'm unable to provide any feedback to the front end (beside simple "error occurred, try again).
Another idea is to use INSERT IGNORE and then check LAST_INSERT_ID(), but can I always be sure LAST_INSERT_ID()==0 when insertion is skipped?
Is there any better way to handle this?

Why don't you use a UNIQUE INDEX? Just insert the new value and let the query fail when the unique constraint is violated. A bit of errorhandling will do the trick.
The UNIQUE contraint will also solve your problem with concurrent users.

INSERT INTO users (nick, email)
SELECT :nick, :email
FROM Dual
WHERE NOT EXISTS (
SELECT 1
FROM users
WHERE nick = :nick OR email = :email
)
most MySql connectors out there have a way to get the rows affected, or you can SELECT ROW_COUNT().

Good question.
unfortinately mysql doesnt support something like "insert into if not exists".
there are several ugly solutions.
mostly the best is to handle it in your application. select before, see if you get anything, only insert if you dont get anything.
then you can put a unique key on the fields to ensure that the database keeps consistent.
you can also directly insert and rely on the unique keys. you will get an error which you have to deal with in your application. you CAN distinguish between the errors so you can display the proper message. duplicate key will be a 1062 if i remember that correclty.
however there ARE means to accomplish this with other teqniques.
one that i know of is called a mutex table.
it works so that you create a second table "mutex" which has the syme key fields as your working table which i now call "table".
then you can do something like:
isnert into table (field1,field2) select ("input1","input2") from mutex left outer join table on(mutex.field1 = table.field1 AND mutex.field2 = table.field2) where mutex.field1 != "input1" AND mutex.field2 != "field2"
i did not test this and i hope i remember the tequnique correctly, better look it up!
it is also possible to advance this to mre flexibility so you can for example only allow a desired number of duplicates in one table.
but this does not ensure data consistency as the data table is accessible as well, so i would really recommend the first method: rely on key constraints where possible, and deal with the error number in your app, if that is not possible, deal with it in your application.

Based on the following link click here
mysql> LOCK TABLE t WRITE, t AS t1 READ;
mysql> INSERT INTO t SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> INSERT INTO t SELECT * FROM t AS t1;
LOCK TABLE users WRITE, users AS t1 READ;
INSERT INTO users (nick, email)
SELECT :nick, :email
FROM Dual
WHERE NOT EXISTS (
SELECT *
FROM users AS t1
WHERE nick = :nick OR email = :email
)
UNLOCK TABLES;

Related

MySQL - select or insert

I have users from many external sources which I try to map to internal userId, so the table I have is:
userId, externalSourceId, externalUserId
In my code, I'm getting externalSourceId and externalUserId and want to get the userId from the database, if exists, otherwise, create one and return the newly created value. I need this action to be atomic because several processes may try to do the same thing at the same time, so I wished only the first time will create a userId.
In pseudo code it will look like that:
u = find user with (externalSourceId, externalUserId)
if no u:
2.1. u = create new user with (externalSourceId, externalUserId) and random userId
return u
INSERT INTO `users`
(`externalSourceId`, externalUserId)
VALUES( 10, 100)
ON DUPLICATE KEY
UPDATE userId=userId
You can also use insert ignore. You can read more about DUPLICATE KEY versus INSERT IGNORE
INSERT IGNORE INTO test (externalSourceId,externalUserId) VALUES (23,32);
SELECT userId FROM test WHERE externalSourceId=23 AND externalUserId=32;
You can use this if externalSourceId and externalUserId are defined unique.

insert when a particular record not found

I got a situation that need to insert only if record does not exist. Normally, I'm going to use 2 queries with conditions like this:
SELECT FROM TABLE ->
IF RECORD NOT FOUND THEN -> INSERT INTO TABLE
ELSE -> DO NOTHING
I feel my solution is not a good one. How can I achieve the same thing with just a single query? For example:
SELECT * from user where status='A' AND name='Lewis'
IF RECORD NOT FOUND THEN
INSERT INTO user(status,name) VALUES('F','Lewis');
You mean something like:
BEGIN
IF NOT EXISTS (SELECT * from user where status='A' AND name='Lewis')
BEGIN
INSERT INTO user (status, name)
VALUES('F','Lewis')
END
END
This works in SQL Server, and should be possible in MySQL as well.
Edit:
Apparently it's not working in MySQL (just testd). However, you could use INSERT IGNORE:
INSERT IGNORE INTO user2 (status, name)
VALUES('F','Lewis');
Note that that would only work if you have a unique or primary key.
Another way could be to have the same unique or primary key, and then use:
INSERT IGNORE INTO user2 (status, name)
VALUES('F','Lewis')
ON DUPLICATE KEY UPDATE status=status;
This avoids ending up with other errors being ignored, and only ignores the duplicate key warning.

MySQL Procedure - Insert row if not exists

OK, this is what I want to do :
If an entry already exists (e.g. based on field name), then just return its id
If it doesn't, add it
This is what I've managed so far (for the "if doesn't exist, create it" part) :
INSERT INTO `objects` (`id`,`name`)
SELECT NULL,'someObj2' FROM `objects`
WHERE NOT EXISTS
(SELECT name FROM `objects` WHERE `name`='someObj2');
SELECT LAST_INSERT_ID();
How can I get the id (instead of LAST_INSERT_ID()) if the entry does exist?
P.S. Yep, I know that the main reason I can't get my head around SQL is the degree at which I'm used to the more classical if-then-else approach of regular programming languages... lol
UPDATE :
I keep trying and trying and this what I've managed so far (as a stored procedure) :
IF EXISTS (SELECT * FROM `objects` WHERE `name` = NAME)
THEN
SELECT `id` FROM `objects` WHERE `name` = NAME;
ELSE
INSERT INTO `objects` (`id`,`name`) VALUES(NULL,NAME);
SELECT LAST_INSERT_ID() AS 'id';
END IF
and calling it like: CALL insertObject("someObj2");
However, it's not working as expected - neither does it add the entry, nor does it return the id (instead it returns all ids in the table...). Any idea what could be going wrong?
It looks like you are trying to enforce a unique constraint on name. If so, you can also do this by just declaring the column to be unique or equivalently creating a unique index:
create unique index objects_name on objects(name);
If this is true, then change the question from getting the last inserted id to just getting the id for name:
select id
from objects o
where o.name = 'someObj2';
I hasten to add that in a high-transaction environment where things are being added and deleted quickly, any approach might have a problem. Consider your code, the row could be inserted and then deleted, even before the last_insert_id() is executed. If you are dealing with a high transaction environment with potential race conditions, then you need to use transactions and locking to do what you want.

MySql Trigger that inserts a record in another table if id isn't a duplicate

I have seen similar questions to mine, however nothing seems to fit the scenario exactly.
I have 2 tables, bills and accounts.
If a bill is pushed into the bills table and there is not already an account, I want to create a record in the accounts table.
I have:
...
BEFORE INSERT ON bills
FOR EACH ROW
BEGIN
INSERT INTO accounts (id, field1, field2, field3, field4)
SELECT accountID, 'value1', value2, value3, value4
FROM bills
WHERE ((SELECT LEFT(accountID, 5) = 'XXXXX' |
(SELECT LEFT(accountID, 5) = 'XXXXX' |
(SELECT LEFT(accountID, 5) = 'XXXXX' |
(SELECT LEFT(accountID, 5) = 'XXXXX' |
(SELECT LEFT(accountID, 5) = 'XXXXX')) &&
accountID NOT IN (SELECT id from accounts); ## I know this line is not right
END$$
From What I have read elsewhere on SO my options seem to be ON DUPLICATE KEY UPDATE or INSERT IGNORE, neither of which I can seem to make work, nor can I find an example with INSERT, SELECT, FROM and WHERE all involved.
What am I missing?
EDIT: I can make it insert the record, but depending on how I have tried it I always get either "duplicate id" error, or it doesnt insert the record.
Havent coded in MySQL for quite a while hence please excuse if there is some small syntax error. However, you will get the idea what needs to be done in the following code:
before insert on bills
for each row
begin
declare #li_cnt int;
select count('') into #li_cnt from accounts where accounts.id_account = inserted.id_account;
if #li_cnt = 0 then
insert into accounts (id_account) values (inserted.id_account)
end
end
However, this type of issue should be handled at the middle tier application server level and not at the database trigger level where one should be actually implementing strict policies and not complying to business requirements. So in that respect, assuming your app to be a PHP web app, you should have the bill's class method save() which should be checking the same and inserting the value in accounts table using the accounts table's class and then inserting the row in the bills table. Using this method one can really make modern sophisticated logic e.g. you can restrict insertion of bills for a specific account if the accounts class method that you call from the bill insertion sends back a decline response. Although it seems you require a simple insertion of data into accounts table when a bill is inserted with an accounts code / ID that doesnt exist in the master accounts table, I still feel coding the right way even in simple requirements makes for a good platform for an app to later scale and become pretty complicated with minimum issues maintenance cost.

Looking for mysql sub query

I have a mysql query doubt.
I have a student table email is the column name, before inserting new student i need to make sure same email can't be inserted.How can i right insert and check in one query(using sub query).
Please help me to find a solution
Thanks
The right way is to enforce it with unique constraint, but if you want to do in insert query, you can try something like
insert into student(field1, field2, email)
select 'value1', 'value2','test#test.com' from dual
where not exists (select null from student where email='test#test.com')
Note :if you use an engine that supports transactions, you may still end up having duplicate)