1171

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
  • 15
    SQL needs an official syntax for this use case that doesn't force duplication of values in the syntax and preserves the primary key.
    – Pete Alvin
    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)
    – dreftymac
    Commented Jul 10, 2019 at 21:01

13 Answers 13

2097

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
7
  • 121
    +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. Commented May 11, 2012 at 21:27
  • 66
    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.
    – neuquen
    Commented Dec 4, 2013 at 21:12
  • 20
    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).
    – Vatev
    Commented Mar 31, 2014 at 11:53
  • 91
    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) Commented Mar 1, 2016 at 10:07
  • 8
    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.
    – twhitney
    Commented Mar 22, 2022 at 17:52
339

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
  • 17
    @Piontek Because this one is shorter and easier to understand and no-one explained why "insert on duplicate" is better.
    – Mr_Chimp
    Commented Jun 13, 2013 at 13:55
  • 111
    it changes the IDs of the record and thus may destroy foreign references.
    – boh
    Commented Sep 13, 2013 at 5:32
  • 142
    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.
    – Dale
    Commented Dec 8, 2013 at 8:05
  • 50
    This is actually DELETE the entire row and perform new INSERT.
    – mjb
    Commented Jun 12, 2014 at 4:05
  • 9
    all the comments here are true but... this might be exactly what is needed sometimes
    – IceFire
    Commented Feb 8, 2022 at 20:42
78

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
  • 1
    If VALUES(name) does not work, you can try name = 'name_value',...;
    – Son Pham
    Commented Apr 21, 2020 at 6:23
  • 10
    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;
    – Liam
    Commented Jun 4, 2021 at 7:37
  • 1
    all the name will be updated to same value
    – Umair Ayub
    Commented Dec 2, 2021 at 11:47
51

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
  • 12
    I downvoted because this doesn’t explain the differences between these solutions and the drawbacks (if any) of each one.
    – bfontaine
    Commented Aug 18, 2022 at 12:43
  • 2
    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.
    – Greg Brown
    Commented Jan 15, 2023 at 15:48
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
  • I want to work without using id. Have you tried without primary key?
    – ersks
    Commented Jul 6, 2017 at 6:41
  • 1
    @ersks see the question. user asked about when there is an unique key
    – Rasel
    Commented Jul 9, 2017 at 8:44
  • 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.
    – ersks
    Commented Jul 13, 2017 at 8:33
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
  • 7
    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)
    – Keshan
    Commented Nov 17, 2010 at 14:21
  • 62
    I don't think he wants to increase the id by one on duplicates.
    – Donnie
    Commented Nov 17, 2010 at 14:22
  • 7
    "transgress" is not the word you're looking for :) Unfortunately, now I've seen "transgress", I can no longer visualise the actual word..
    – mwfearnley
    Commented Apr 13, 2017 at 7:47
  • Updating an ID like this might mess with its sequence or get an error if there is an existing id+1
    – El Gucs
    Commented Mar 27, 2023 at 23:50
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
  • 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
    Commented Oct 21, 2020 at 14:32
  • 1
    @Natalia kindly create a dbfiddle so I can check
    – Rich
    Commented Oct 21, 2020 at 14:34
  • 1
    I will try, but basically, you can not make text type of a column unique in MySQL. I made it varchar.
    – Natalia
    Commented Oct 21, 2020 at 15:22
  • "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"
    – Nosajimiki
    Commented Oct 26, 2021 at 20:31
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.

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
  • What replace into does is exactly "insert into, or update when existing". @Owl
    – DawnSong
    Commented Nov 17, 2016 at 15:38
  • +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. Commented Jan 30, 2017 at 1:21
  • ( 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(); Commented Jan 30, 2017 at 1:22
  • ( 3 of 3 ) Related question/answer stackoverflow.com/questions/41767517/… Commented Jan 30, 2017 at 1:23
  • 2
    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 Commented Mar 21, 2018 at 13:37
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;
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
  • 6
    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.
    – user2607743
    Commented May 15, 2019 at 13:50
5

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");
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;

Not the answer you're looking for? Browse other questions tagged or ask your own question.