1185

I want to add a row to a database table, but if a row exists with the same unique key I want to update the row.

For example:

INSERT INTO table_name (ID, NAME, AGE) VALUES(1, "A", 19);

Let’s say the unique key is ID, and in my Database, there is a row with ID = 1. In that case, I want to update that row with these values. Normally this gives an error.
If I use INSERT IGNORE it will ignore the error, but it still won’t update.

3
  • 16
    SQL needs an official syntax for this use case that doesn't force duplication of values in the syntax and preserves the primary key. Commented Jan 26, 2018 at 13:02
  • 1
    To get the influenced id refer to MySQL ON DUPLICATE KEY - last insert id? Commented Apr 24, 2019 at 6:30
  • 1
    Caveat: as of version 5.7 this approach does not directly support WHERE clause as part of the INSERT/UPDATE operation. Also, an UPDATE actually counts as two separate operations (DELETE and INSERT) ... in case that matters for audit purposes. (Learnbit) Commented Jul 10, 2019 at 21:01

13 Answers 13

2127

Use INSERT ... ON DUPLICATE KEY UPDATE

QUERY:

INSERT INTO table (id, name, age) VALUES(1, "A", 19) ON DUPLICATE KEY UPDATE    
name="A", age=19
Sign up to request clarification or add additional context in comments.

11 Comments

+1 From what I've found, this method is less problematic for auto-increment keys and other unique key collisions than REPLACE INTO, and it is more efficient.
I know all of you allude to this, but I want to be explicit for others. If the ID you insert is NOT the PRIMARY KEY or UNIQUE, then this will not work. This didn't initially work for me because my ID was not unique.
This is a bit late, but anyway: it is stated in the manual that updates in ON DUPLICATE KEY UPDATE increase the affected rows by 2. It reports 0 if nothing is actually updated (same as the regular UPDATE).
Also note that you can use VALUES (name) to reference to the value you attempt to insert e.g. INSERT INTO table (id, name, age) VALUES(1, "A", 19) ON DUPLICATE KEY UPDATE name=VALUES(name) , age=VALUES(age)
Unfortunately ON DUPLICATE KEY UPDATE causes the autoincrement to increment even on updates, making this pretty much unusable if you need to do hundreds or thousands of these queries per day, since then the autoincrement will steadily increase by x rows every time even if no new rows are being added.
Then use a 64 bit autoinc field. A million a second then and it would still take 584,000 years to run out of keys.
|
351

Check out REPLACE:

REPLACE works exactly like INSERT, except that if an old row in the table has the same value as a new row for a PRIMARY KEY or a UNIQUE index, the old row is deleted before the new row is inserted.

Example:

REPLACE INTO `tablename` (`id`, `name`, `age`) VALUES (1, "A", 19)

6 Comments

@Piontek Because this one is shorter and easier to understand and no-one explained why "insert on duplicate" is better.
it changes the IDs of the record and thus may destroy foreign references.
The other problem with REPLACE INTO is that you must specify values for ALL fields...otherwise fields will get lost or replaced with default values. REPLACE INTO essentially deletes the row if it exists, and inserts the new row. In the example, if you did 'REPLACE INTO table (id, age) values (1, 19) then the name field would become null.
This is actually DELETE the entire row and perform new INSERT.
all the comments here are true but... this might be exactly what is needed sometimes
|
81

When using batch insert use the following syntax:

INSERT INTO TABLE (id, name, age) VALUES (1, "A", 19), (2, "B", 17), (3, "C", 22)
ON DUPLICATE KEY UPDATE
    name = VALUES (name),
    ...

3 Comments

If VALUES(name) does not work, you can try name = 'name_value',...;
VALUES is now deprecated - you should use insert into TABLE (Id, name, age) values (1, "A", 19), (2, "B", 20) as ins on duplicate key update name=ins.name, age=ins.age;
all the name will be updated to same value
50

Any of these solution will work regarding your question:

INSERT IGNORE INTO table (id, name, age) VALUES (1, "A", 19);

or

INSERT INTO TABLE (id, name, age) VALUES(1, "A", 19) 
    ON DUPLICATE KEY UPDATE NAME = "A", AGE = 19;  

or

REPLACE INTO table (id, name, age) VALUES(1, "A", 19);

2 Comments

I downvoted because this doesn’t explain the differences between these solutions and the drawbacks (if any) of each one.
I don't believe the first version produces the same results as the others. As I understand it, "IGNORE" will simply cause the DB to treat the conflict as a warning instead of an error. It will not update the conflicting row. Further, as others have pointed out, REPLACE INTO is effectively a DELETE/INSERT whereas ON DUPLICATE KEY will update an existing row.
26

Try this:

INSERT INTO table (id,name,age) VALUES('1','Mohammad','21') ON DUPLICATE KEY UPDATE name='Mohammad',age='21'

Note:
Here if id is the primary key then after first insertion with id='1' every time attempt to insert id='1' will update name and age and previous name age will change.

3 Comments

I want to work without using id. Have you tried without primary key?
@ersks see the question. user asked about when there is an unique key
I got that, but I am trying to solve my problem in which these only values are know. So, in such situation I wrote above comment hoping correct solution.
25

Try this out:

INSERT INTO table (id, name, age) VALUES (1, 'A', 19) ON DUPLICATE KEY UPDATE id = id + 1;

Hope this helps.

4 Comments

actually i don't need to add the new values to another row with a new ID instead i want to replace the existing values of id = 1 with this values. (as i understand this increments the id and add the data)
I don't think he wants to increase the id by one on duplicates.
"transgress" is not the word you're looking for :) Unfortunately, now I've seen "transgress", I can no longer visualise the actual word..
Updating an ID like this might mess with its sequence or get an error if there is an existing id+1
22

In case that you wanted to make a non-primary fields as criteria/condition for ON DUPLICATE, you can make a UNIQUE INDEX key on that table to trigger the DUPLICATE.

ALTER TABLE `table` ADD UNIQUE `unique_index`(`name`);

And in case you want to combine two fields to make it unique on the table, you can achieve this by adding more on the last parameter.

ALTER TABLE `table` ADD UNIQUE `unique_index`(`name`, `age`);

Note, just make sure to delete first all the data that has the same name and age value across the other rows.

DELETE table FROM table AS a, table AS b WHERE a.id < b.id 
AND a.name <=> b.name AND a.age <=> b.age;

After that, it should trigger the ON DUPLICATE event.

INSERT INTO table (id, name, age) VALUES(1, "A", 19) ON DUPLICATE KEY UPDATE    
name = VALUES(name), age = VALUES(age)

4 Comments

I have tried that, I am gettting an error that says "BLOB/TEXT column 'column_name' used in key specification without a key length"
@Natalia kindly create a dbfiddle so I can check
I will try, but basically, you can not make text type of a column unique in MySQL. I made it varchar.
"DELETE table FROM table AS a, table AS b WHERE a.id < b.id AND a.name <=> b.name AND a.age <=> b.age;" gives me error "Unknown table 'table' in MULTI DELETE"
17

Just because I was here looking for this solution but for updating from another identically-structured table (in my case website test DB to live DB):

INSERT  live-db.table1
SELECT  *
FROM    test-db.table1 t
ON DUPLICATE KEY UPDATE
        ColToUpdate1 = t.ColToUpdate1,
        ColToUpdate2 = t.ColToUpdate2,
        ...

As mentioned elsewhere, only the columns you want to update need to be included after ON DUPLICATE KEY UPDATE.

No need to list the columns in the INSERT or SELECT, though I agree it's probably better practice.

Comments

16

When using SQLite:

REPLACE into table (id, name, age) values(1, "A", 19)

Provided that id is the primary key. Or else it just inserts another row. See INSERT (SQLite).

5 Comments

What replace into does is exactly "insert into, or update when existing". @Owl
+1 ( 1 of 3 ) I found this to work for my situation - I needed to replace an existing row with a unique key or if not there then add the row. Simplest of solutions here.
( 2 of 3 ) My query: CREATE TRIGGER worklog_update AFTER INSERT ON worklog FOR EACH ROW REPLACE INTO support_time_monthly_hours ( ProjectID, monthTotalTime, Year, Month ) SELECT jiraissue.PROJECT, SUM(worklog.timeworked), YEAR(CURRENT_DATE()), MONTH(CURRENT_DATE()) FROM worklog, jiraissue WHERE worklog.issueid = jiraissue.ID AND jiraissue.PROJECT = (SELECT PROJECT FROM jiraissue WHERE NEW.issueid = jiraissue.ID ) AND worklog.startdate BETWEEN DATE_FORMAT(NOW() ,'%Y-%m-01 00:00:00') AND NOW();
( 3 of 3 ) Related question/answer stackoverflow.com/questions/41767517/…
The problem with this in mysql is that replace will remove other values in case they are not provided. However @fabiano-souza solution is more appropriate
6

In case, you want to keep old field (For ex: name). The query will be:

INSERT INTO table (id, name, age) VALUES(1, "A", 19) ON DUPLICATE KEY UPDATE    
name=name, age=19;

Comments

6

Following are some of the possible approaches:

Using INSERT INTO

The INSERT statement allows you to insert one or more rows into a table

  • First, specify the table name and a list of comma-separated columns inside parentheses after the INSERT INTO clause.
  • Secondly, put a comma-separated list of values of the corresponding columns inside the parentheses following the VALUES keyword.
INSERT INTO table_name(column_name1, column_name2, column_name3) VALUES("col_value_1", "col_value_2", "col_value_3");

Using INSERT INTO with WHERE NOT EXISTS clause

INSERT INTO table_name (column_name_1, column_name_2, column_name_3)
SELECT * FROM (SELECT "col_value_1", "col_value_2","col_value_3") AS tmp_name
WHERE NOT EXISTS (
    SELECT column_name2 FROM table_name WHERE column_name = "sample_name"
) LIMIT 1;

Using REPLACE INTO

REPLACE works exactly like INSERT, except that if an old row in the table has the same value as a new row for a PRIMARY KEY or a UNIQUE index, the old row is deleted before the new row is inserted.

REPLACE INTO table_name(column_name1, column_name2, column_name3) VALUES("col_value_1", "col_value_2", "col_value_3");

Comments

5

In my case i created below queries but in the first query if id 1 is already exists and age is already there, after that if you create first query without age than the value of age will be none

REPLACE into table SET `id` = 1, `name` = 'A', `age` = 19

for avoiding above issue create query like below

INSERT INTO table SET `id` = '1', `name` = 'A', `age` = 19 ON DUPLICATE KEY UPDATE `id` = "1", `name` = "A",`age` = 19

may it will help you ...

1 Comment

Does anyone know why we must assign the values twice? Why doesn't MySQL allow us to end the query at ON DUPLICATE KEY UPDATE without duplicating all the assignment statements? Some database tables have many columns, and this seems redundant / gratuitous. I understand why we have the option for alternate assignments, but why not have the option to omit them as well? Just curious if anyone knows.
0

Here is a better way to accomplish an INSERT and UPDATE without affecting the auto increment and on top of that getting back the primary key in either scenario in a consistent way. It does involve multiple commands but can be done on one line if desired. This would be in a case where one or more fields make up a unique constraint like in this case country.

If you want the primary key back from INSERT or UPDATE only null values

SET @now=NOW();
SET @country='Example Country',@country_code='EC',@country_overview='An example country overview.';
INSERT INTO country (country, country_code, country_overview, added_at, updated_at)
SELECT @country,@country_code,@country_overview, @now, @now
FROM dual
WHERE NOT EXISTS (
    SELECT 1 FROM country WHERE country=@country
);
SELECT countryid INTO @countryid FROM country WHERE country=@country;
UPDATE country 
SET 
country_code = @country_code, 
country_overview = @country_overview, 
updated_at = @now 
WHERE countryid = @countryid AND added_at <> @now;

If you want the primary key back from INSERT or UPDATE only null values

SET @now=NOW();
SET @country='Example Country',@country_code='EC',@country_overview='An example country overview.';
INSERT INTO country (country, country_code, country_overview, added_at, updated_at)
SELECT @country,@country_code, @now, @now
FROM dual
WHERE NOT EXISTS (
    SELECT 1 FROM country WHERE country=@country
);
SELECT countryid INTO @countryid FROM country WHERE country=@country;
UPDATE country 
SET 
country_code = COALESCE(country_code, @country_code), 
country_overview = COALESCE(country_overview, @country_overview), 
updated_at = @now 
WHERE countryid = @countryid AND added_at <> @now;

There are few other ways you can work with this to get the primary key back you could do the following as your last statement.

SELECT @countryid;

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.