INSERT ON DUPLICATE KEY UPDATE with last_insert_id() - mysql

im trying to create a function
CREATE FUNCTION `func`(param1 INT, param2 INT, param3 TEXT) RETURNS int(11)
BEGIN
INSERT INTO `table1` (`column1`, `column2`, `column3` )
VALUES (param1, param2, param3)
ON DUPLICATE KEY
UPDATE `time_stamp` = UNIX_TIMESTAMP();
RETURN last_insert_id();
END
this would insert into a table a row if it doesn't exist but otherwise update it.
Notice that i returned last_insert_id() which would be correct if the function would insert otherwise would be unpredictable if it updates.
I know the alternative to solving this is using separate SELECTS and identify if it exists; if it exists retrieve the id and update using that id; otherwise just do a plain INSERT.
Now my question: Is there any alternative to doing 2 sql statements as opposed to what i'm doing now?
EDIT 1
Addendum:
there is an auto incremented index.
All of the values to be inserted are unique
I'd rather not alter the index since it is being referred in another table..

If a table contains an AUTO_INCREMENT column and INSERT ... UPDATE inserts a row, the LAST_INSERT_ID() function returns the AUTO_INCREMENT value. If the statement updates a row instead, LAST_INSERT_ID() is not meaningful. However, you can work around this by using LAST_INSERT_ID(expr). Suppose that id is the AUTO_INCREMENT column. To make LAST_INSERT_ID() meaningful for updates, insert rows as follows:
INSERT INTO table (a, b, c) VALUES (1, 2, 3)
ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id), c = 3;
Found it on this link. I've never tried it though, but it might help you.
EDIT 1
You might want to check out REPLACE:
REPLACE INTO table1 (column1, column2, column3) VALUES (param1, param2, param3);
This should work for tables with correct PRIMARY KEY/UNIQUE INDEX.
In the end, you'll just have to stick with:
IF (VALUES EXISTS ON TABLE ...)
UPDATE ...
SELECT Id;
ELSE
INSERT ...
RETURN last_insert_id();
END IF

Just in case anyone shows up here from Google, I ran into a problem where ON DUPLICATE KEY UPDATE kept triggering the same wrong value.
When inserting a user with only a first name and last name, it didn't AUTO_INCREMENT the primary key. The reason is we have a users table with a unique constraint on the username, but it has a default value of ''. So when you insert a user without a username, it triggers it to update the duplicate value of that username, and that random account kept getting returned as the correct one.
The solution is to make sure that only NULL is the default value for a unique key in a table that also has a separate auto-increment primary key, or that you do generate a unique value for the unique constraint.

Related

Output primary key after insert a row in Mysql

I have created a trigger to help me to make the primary key when I insert a row. I want to get the primary key I just insert. But because the primary key is not auto_increment I cannot use
SELECT LAST_INSERT_ID();
So how can I get the primary key?
Here is the trigger
CREATE TRIGGER before_insert_user_info
BEFORE INSERT ON USER_INFO
FOR EACH ROW
SET new.uID = CONCAT('U', ((SELECT MAX(CAST(SUBSTRING(uID, 2, length(uID)) AS UNSIGNED)) FROM USER_INFO)+1));
And here is the insert
INSERT INTO USER_INFO(name) VALUES ('Peter');
It is kind of unusual to rely on a trigger to generate a primary key. There is no way to retreive data back from a trigger (e.g. it has no return value). But you can reuse your tigger's logic to retreive the generated value:
INSERT INTO user_info VALUE (#newUID, ...);
SELECT MAX(CAST(SUBSTRING(#newUID, 2, LENGTH(#newUID)) AS UNSIGNED)) AS last_insert_id
FROM user_info; -- this is the generated value
Wrap these two statements in a transaction to make sure a new user is not inserted in between.
As an alternative, I would create another INT AUTO_INCREMENT column, so that you can retreive your new row after insertion with LAST_INSERT_ID();
And while we are at it, I would make this new field the (surrogate) primary key. The same trigger could still generate the "public" user ID, but then we would be back to a more usual architecture.
Last food for thoughts: do you really need to store your user ID's with the U prefix? Perhaps you could just store a plain INT value, and preppend the U on selection.

Can I use INSERT INTO ... On DUPLICATE KEY without by using auto_increment value?

I am trying to write a query to check if a record exists (based on couple of clause and not unique identifier) if such a search return records then I need to update all the found records if nothing found then I need to INSERT a record. Note that I can't use IF EXISTS because I am trying to make a query for a client side script and not a server side. So I came a cross the idea of INSERT INTO .... ON DUPLICATE KEY
Can I do this without knowing the row key identifier? So if I find a record where accountid = 17 and name = 'Mike' then update it to make the name 'Mike A' if there is no record with these 2 clause then insert a record.
This is an attempt that is giving me a syntax error
INSERT INTO test (name, accountid) VALUES ('Mike', 17)
ON DUPLICATE KEY
UPDATE test SET name='Mike A' WHERE name ='Mike' AND accountid = 17
Can this method handle what I am trying to do? If yes then can you please correct my syntax?
Thank you
The only way you can get this to work is if you have a primary key or unique constraint on the fields. From the documentation:
If you specify ON DUPLICATE KEY UPDATE, and a row is inserted that
would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an
UPDATE of the old row is performed. For example, if column a is
declared as UNIQUE and contains the value 1, the following two
statements have identical effect:
create table test (name varchar(100), accountid int);
insert into test values ('Mike', 17);
alter table test add unique (name, accountid);
insert int test (name, accountid) values ('Mike', 17)
on duplicate key update name='Mike A';
SQL Fiddle Demo
Without the unique key, it will insert a duplicate record.

On Duplicate Key Update same as insert

I've searched around but didn't find if it's possible.
I've this MySQL query:
INSERT INTO table (id,a,b,c,d,e,f,g) VALUES (1,2,3,4,5,6,7,8)
Field id has a "unique index", so there can't be two of them. Now if the same id is already present in the database, I'd like to update it. But do I really have to specify all these field again, like:
INSERT INTO table (id,a,b,c,d,e,f,g) VALUES (1,2,3,4,5,6,7,8)
ON DUPLICATE KEY UPDATE a=2,b=3,c=4,d=5,e=6,f=7,g=8
Or:
INSERT INTO table (id,a,b,c,d,e,f,g) VALUES (1,2,3,4,5,6,7,8)
ON DUPLICATE KEY UPDATE a=VALUES(a),b=VALUES(b),c=VALUES(c),d=VALUES(d),e=VALUES(e),f=VALUES(f),g=VALUES(g)
I've specified everything already in the insert...
A extra note, I'd like to use the work around to get the ID to!
id=LAST_INSERT_ID(id)
I hope somebody can tell me what the most efficient way is.
The UPDATE statement is given so that older fields can be updated to new value. If your older values are the same as your new ones, why would you need to update it in any case?
For eg. if your columns a to g are already set as 2 to 8; there would be no need to re-update it.
Alternatively, you can use:
INSERT INTO table (id,a,b,c,d,e,f,g)
VALUES (1,2,3,4,5,6,7,8)
ON DUPLICATE KEY
UPDATE a=a, b=b, c=c, d=d, e=e, f=f, g=g;
To get the id from LAST_INSERT_ID; you need to specify the backend app you're using for the same.
For LuaSQL, a conn:getlastautoid() fetches the value.
There is a MySQL specific extension to SQL that may be what you want - REPLACE INTO
However it does not work quite the same as 'ON DUPLICATE UPDATE'
It deletes the old row that clashes with the new row and then inserts the new row. So long as you don't have a primary key on the table that would be fine, but if you do, then if any other table references that primary key
You can't reference the values in the old rows so you can't do an equivalent of
INSERT INTO mytable (id, a, b, c) values ( 1, 2, 3, 4)
ON DUPLICATE KEY UPDATE
id=1, a=2, b=3, c=c + 1;
I'd like to use the work around to get the ID to!
That should work — last_insert_id() should have the correct value so long as your primary key is auto-incrementing.
However as I said, if you actually use that primary key in other tables, REPLACE INTO probably won't be acceptable to you, as it deletes the old row that clashed via the unique key.
Someone else suggested before you can reduce some typing by doing:
INSERT INTO `tableName` (`a`,`b`,`c`) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE `a`=VALUES(`a`), `b`=VALUES(`b`), `c`=VALUES(`c`);
There is no other way, I have to specify everything twice. First for the insert, second in the update case.
Here is a solution to your problem:
I've tried to solve problem like yours & I want to suggest to test from simple aspect.
Follow these steps: Learn from simple solution.
Step 1: Create a table schema using this SQL Query:
CREATE TABLE IF NOT EXISTS `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(30) NOT NULL,
`password` varchar(32) NOT NULL,
`status` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `no_duplicate` (`username`,`password`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;
Step 2: Create an index of two columns to prevent duplicate data using following SQL Query:
ALTER TABLE `user` ADD INDEX no_duplicate (`username`, `password`);
or, Create an index of two column from GUI as follows:
Step 3: Update if exist, insert if not using following queries:
INSERT INTO `user`(`username`, `password`) VALUES ('ersks','Nepal') ON DUPLICATE KEY UPDATE `username`='master',`password`='Nepal';
INSERT INTO `user`(`username`, `password`) VALUES ('master','Nepal') ON DUPLICATE KEY UPDATE `username`='ersks',`password`='Nepal';
Just in case you are able to utilize a scripting language to prepare your SQL queries, you could reuse field=value pairs by using SET instead of (a,b,c) VALUES(a,b,c).
An example with PHP:
$pairs = "a=$a,b=$b,c=$c";
$query = "INSERT INTO $table SET $pairs ON DUPLICATE KEY UPDATE $pairs";
Example table:
CREATE TABLE IF NOT EXISTS `tester` (
`a` int(11) NOT NULL,
`b` varchar(50) NOT NULL,
`c` text NOT NULL,
UNIQUE KEY `a` (`a`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
I know it's late, but i hope someone will be helped of this answer
INSERT INTO t1 (a,b,c) VALUES (1,2,3),(4,5,6)
ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);
You can read the tutorial below here :
https://mariadb.com/kb/en/library/insert-on-duplicate-key-update/
http://www.mysqltutorial.org/mysql-insert-or-update-on-duplicate-key-update/
You may want to consider using REPLACE INTO syntax, but be warned, upon duplicate PRIMARY / UNIQUE key, it DELETES the row and INSERTS a new one.
You won't need to re-specify all the fields. However, you should consider the possible performance reduction (depends on your table design).
Caveats:
If you have AUTO_INCREMENT primary key, it will be given a new one
Indexes will probably need to be updated
With MySQL v8.0.19 and above you can do this:
mysql doc
INSERT INTO mytable(fielda, fieldb, fieldc)
VALUES("2022-01-01", 97, "hello")
AS NEW(newfielda, newfieldb, newfieldc)
ON DUPLICATE KEY UPDATE
fielda=newfielda,
fieldb=newfieldb,
fieldc=newfieldc;
SIDENOTE: Also if you want a conditional in the on duplicate key update part there is a twist in MySQL. If you update fielda as the first argument and include it inside the IF clause for fieldb it will already be updated to the new value! Move it to the end or alike. Let's say fielda is a date like in the example and you want to update only if the date is newer than the previous:
INSERT INTO mytable(fielda, fieldb)
VALUES("2022-01-01", 97)
AS NEW(newfielda, newfieldb, newfieldc)
ON DUPLICATE KEY UPDATE
fielda=IF(fielda<STR_TO_DATE(newfielda,'%Y-%m-%d %H:%i:%s'),newfielda,fielda),
fieldb=IF(fielda<STR_TO_DATE(newfielda,'%Y-%m-%d %H:%i:%s'),newfieldb,fieldb);
in this case fieldb would never be updated because of the <! you need to move the update of fielda below it or check with <= or =...!
INSERT INTO mytable(fielda, fieldb)
VALUES("2022-01-01", 97)
AS NEW(newfielda, newfieldb, newfieldc)
ON DUPLICATE KEY UPDATE
fielda=IF(fielda<STR_TO_DATE(newfielda,'%Y-%m-%d %H:%i:%s'),newfielda,fielda),
fieldb=IF(fielda=STR_TO_DATE(newfielda,'%Y-%m-%d %H:%i:%s'),newfieldb,fieldb);
This works as expected with using = since fielda is already updated to its new value before reaching the if clause of fieldb... Personally i like <= the most in such a case if you ever rearrange the statement...
you can use insert ignore for such case, it will ignore if it gets duplicate records
INSERT IGNORE
... ; -- without ON DUPLICATE KEY

check if data exists before insert and return true/false?

Solution:
Place unique constraint on multiple columns
ALTER TABLE rating ADD UNIQUE KEY ( id , id );
I want to insert a score in my rating system. I want to be able to insert data if it doesn't exist. I also want the same query to return true/false if it does exist.
I looked into INSERT IGNORE but that silently fails so I don't know how to check against that.
I know I could attempt a SELECT ... WHERE id = 1 and THAN do the insert, but I want to move this down to a single insert, is this possible?
If you use a unique key constraint on the column or columns you want to check against, then a normal insert will succeed if there is no data already, or fail noisily due to unique key constraint if there is already something matching.
INSERT INTO `rating` (value1)
SELECT 'your rate for value1' FROM `rating`
WHERE NOT EXISTS (SELECT * FROM `rating`
WHERE value1='your rate for value1' ) LIMIT 1;

Get the id of a row when UNIQUE KEY is violated

I have a table user. It has columns id and email.
USER TABLE
id | email
1 | xxx#gmail.com
2 | yyy#gmail.com
The id is a PRIMARY KEY AUTO_INCREMENT and the email is an UNIQUE KEY.
When I insert a new row in the table and there is a DUPLICATE KEY exception thrown. I want to fetch the id on which the DUPLICATE KEY exception was thrown.
Right now I am doing this -
BEGIN
DECLARE EXIT HANDLER FOR 1062
BEGIN
SELECT id
INTO id
FROM user
WHERE email = 'xxx#gmail.com';
END;
INSERT INTO user
(email)
VALUES
('xxx#gmail.com');
SELECT LAST_INSERT_ID() INTO id;
END;
I want to know if there is a better way to do this. That is to avoid scanning the table again to get the id for which it had already scanned to check the uniqueness of the email.
In scaning by UNIQUE KEY BTREE is used so it's quite fast.
Don't you want check for existing of value by yourself in additional select query
Use INSERT ... ON DUPLICATE KEY UPDATE, then get the autoincremented id as usual:
If a table contains an AUTO_INCREMENT column and INSERT ... ON DUPLICATE KEY UPDATE inserts or updates a row, the LAST_INSERT_ID() function returns the AUTO_INCREMENT value. Exception: For updates, LAST_INSERT_ID() is not meaningful prior to MySQL 5.1.12. However, you can work around this by using LAST_INSERT_ID(expr). Suppose that id is the AUTO_INCREMENT column. To make LAST_INSERT_ID() meaningful for updates, insert rows as follows:
INSERT INTO table (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), c=3;