{"id":10822,"date":"2023-09-26T22:11:30","date_gmt":"2023-09-26T20:11:30","guid":{"rendered":"https:\/\/monodes.com\/predaelli\/?p=10822"},"modified":"2023-09-26T22:11:33","modified_gmt":"2023-09-26T20:11:33","slug":"upsert-in-sql","status":"publish","type":"post","link":"https:\/\/monodes.com\/predaelli\/2023\/09\/26\/upsert-in-sql\/","title":{"rendered":"Upsert in SQL"},"content":{"rendered":"\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Inserting and updating data with a single query.<\/p>\n<cite>Source: <em><a href=\"https:\/\/antonz.org\/sql-upsert\/\">Upsert in SQL<\/a><\/em><\/cite><\/blockquote>\n\n\n\n<!--nextpage-->\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<h1 class=\"wp-block-heading\">Upsert in SQL<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Upsert<\/em> is an operation that \u278a inserts new records into the database and \u278b updates existing ones. Let&#8217;s see how it works in different DBMS. The examples are interactive, so you can read and practice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We will use the toy <code>employees<\/code> table:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u250c\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 id \u2502 name  \u2502  city  \u2502 department \u2502 salary \u2502\n\u251c\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 11 \u2502 Diane \u2502 London \u2502 hr         \u2502 70     \u2502\n\u2502 12 \u2502 Bob   \u2502 London \u2502 hr         \u2502 78     \u2502\n\u2502 21 \u2502 Emma  \u2502 London \u2502 it         \u2502 84     \u2502\n\u2502 22 \u2502 Grace \u2502 Berlin \u2502 it         \u2502 90     \u2502\n\u2502 23 \u2502 Henry \u2502 London \u2502 it         \u2502 104    \u2502\n\u2502 24 \u2502 Irene \u2502 Berlin \u2502 it         \u2502 104    \u2502\n\u2502 31 \u2502 Cindy \u2502 Berlin \u2502 sales      \u2502 96     \u2502\n\u2502 32 \u2502 Dave  \u2502 London \u2502 sales      \u2502 96     \u2502\n\u2514\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s say we are adding two new employees:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>(25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120)\n(33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Hello, Frank. Hello, Alice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And updating the other two:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- (11, &#039;Diane&#039;, &#039;London&#039;, &#039;hr&#039;, 70)\n+ (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70)\n\n- (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 84)\n+ (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Diane is moving to Berlin, while Emma&#8217;s salary is increasing to 95.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can read this article from start to finish, or you can skip to a section about the specific database engine:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#mysql-replace\">MySQL\/MariaDB<\/a> \u2022 <a href=\"https:\/\/antonz.org\/sql-upsert\/#sqlite-insert-or\">SQLite<\/a> \u2022 <a href=\"https:\/\/antonz.org\/sql-upsert\/#postgresql-inserton-conflict\">PostgreSQL<\/a> \u2022 <a href=\"https:\/\/antonz.org\/sql-upsert\/#sql-standard-merge\">SQL Server<\/a> \u2022 <a href=\"https:\/\/antonz.org\/sql-upsert\/#sql-standard-merge\">Oracle<\/a> \u2022 <a href=\"https:\/\/antonz.org\/sql-upsert\/#other-database-engines\">Others<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">INSERT<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s try the easy way:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert into employees\n(id, name, city, department, salary)\nvalues\n(11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n(21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n(25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n(33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The first table is &#8220;before insert&#8221;, the second is &#8220;after insert&#8221;. We&#8217;ll use this notation for all examples.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As expected, it fails for Diane and Emma: the employee ID is a primary key, so we can&#8217;t have two employees with the same ID. We&#8217;ll have to try something else.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">MySQL: REPLACE<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">MySQL&#8217;s <code>replace into<\/code> inserts records with new primary\/unique keys (in our case, employee IDs) and replaces the ones with existing keys:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>replace into employees\n(id, name, city, department, salary)\nvalues\n(11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n(21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n(25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n(33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Added Frank and Alice, replaced Diane and Emma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The bad thing about <code>replace<\/code> is that it does an actual <code>delete<\/code> followed by <code>insert<\/code>, which has some unexpected <a href=\"https:\/\/stackoverflow.com\/a\/548570\">side effects<\/a> like firing <code>on delete<\/code> triggers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also, you can&#8217;t reference the old values when replacing the record. For example, you can&#8217;t set <code>city<\/code> = <code>Berlin (prev. London)<\/code> for Diane.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2192 <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/replace.html\">Documentation<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To ignore the duplicates and only perform inserts, you can use <code>insert ignore<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert ignore into employees\n(id, name, city, department, salary)\nvalues\n(11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n(21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n(25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n(33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Added Frank and Alice, ignored Diane and Emma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2192 <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/sql-mode.html#ignore-effect-on-execution\">Documentation<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>replace<\/code> is probably not the best way to do upserts in MySQL. There is a better alternative&nbsp;\u2193<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">MySQL: INSERT&#8230;ON DUPLICATE KEY UPDATE<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">MySQL&#8217;s <code>on duplicate key update<\/code> inserts records with new primary\/unique keys (in our case, employee IDs) and updates the ones with existing keys:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert into employees\n(id, name, city, department, salary)\nvalues\n  (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n  (33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100)\non duplicate key update\n  city = values(city),\n  salary = values(salary);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Added Frank and Alice, updated Diane and Emma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When updating a record, you can reference the old value by column name (<code>city<\/code>) and the new value with the <code>values()<\/code> function (<code>values(city)<\/code>). Now we can actually update a value, not just replace it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert into employees\n(id, name, city, department, salary)\nvalues\n  (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70)\non duplicate key update\n  city = concat(values(city), &#039; (prev. &#039;, city, &#039;)&#039;);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Updated the city for Diane and kept the old value for reference.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Starting with MySQL 8.0, you can alias the new values and not use the ugly <code>values()<\/code> function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert into employees\n(id, name, city, department, salary)\nvalues\n  (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n  (33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100)\n  as new\non duplicate key update\n  city = new.city,\n  salary = new.salary;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Using the <code>new<\/code> alias for new records.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2192 <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/insert-on-duplicate.html\">Documentation<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">SQLite: INSERT OR&#8230;<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">SQLite&#8217;s <code>insert or replace<\/code> inserts records with new primary\/unique keys (in our case, employee IDs) and replaces the ones with existing keys:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert or replace into employees\n(id, name, city, department, salary)\nvalues\n(25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n(33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100),\n(11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n(21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Added Frank and Alice, replaced Diane and Emma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Like MySQL&#8217;s <code>replace<\/code>, SQLite&#8217;s <code>or replace<\/code> deletes existing duplicate records before inserting the new ones. It fires delete triggers in some cases and does not allow references to old values.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">SQLite provides a number of alternative strategies instead of <code>replace<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>ignore<\/code>: skips the duplicate records (similar to MySQL&#8217;s <code>ignore<\/code>).<\/li>\n\n\n\n<li><code>rollback<\/code>: aborts the current statement and rolls back the entire transaction.<\/li>\n\n\n\n<li><code>abort<\/code>: aborts the current statement and reverts any changes made by it, but does not roll back the transaction.<\/li>\n\n\n\n<li><code>fail<\/code>: aborts the current statement, but does not revert any changes already made by it, and does not roll back the transaction.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>insert or abort into employees\n(id, name, city, department, salary)\nvalues\n(11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n(21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n(25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n(33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Since there are conflicts, <code>abort<\/code> reverts all changes. The table remains as it was.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2192 <a href=\"https:\/\/sqlite.org\/lang_conflict.html\">Documentation<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>insert or replace<\/code> is probably not the best way to do upserts in SQLite. There is a better alternative&nbsp;\u2193<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">SQLite: INSERT&#8230;ON CONFLICT<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">SQLite&#8217;s <code>on conflict do update<\/code> inserts records with new primary\/unique keys (in our case, employee IDs) and updates the ones with existing keys:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert into employees\n(id, name, city, department, salary)\nvalues\n  (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n  (33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100)\non conflict do update set\n  city = excluded.city,\n  salary = excluded.salary;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Added Frank and Alice, updated Diane and Emma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When updating a record, you can reference the old value by column name (<code>city<\/code>) and the new value with the <code>excluded<\/code> prefix (<code>excluded.city<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can optionally disable some updates using the <code>where<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert into employees\n(id, name, city, department, salary)\nvalues\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (22, &#039;Grace&#039;, &#039;Berlin&#039;, &#039;it&#039;, 105)\non conflict do update set\n  salary = excluded.salary where excluded.salary &lt; 100;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We only allow salaries under 100, so Emma gets updated but Grace does not.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Besides <code>do update<\/code>, there is also <code>do nothing<\/code>, which ignores conflicts and only inserts new records:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert into employees\n(id, name, city, department, salary)\nvalues\n  (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n  (33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100)\non conflict do nothing;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Added Frank and Alice, ignored Diane and Emma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>on conflict<\/code> even allows you to specify different rules for different uniqueness constraints:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>create unique index idx_employees_name on employees(name);\n\ninsert into employees\n(id, name, city, department, salary)\nvalues\n  -- increase Emma&#039;s salary to 95\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  -- change Grace&#039;s name to Diane\n  (22, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;it&#039;, 90)\non conflict (id) do update set\n  city = excluded.city,\n  salary = excluded.salary\non conflict (name) do nothing;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We only allow unique names (stupid, I know), and &#8220;Diane&#8221; is already taken. So Emma&#8217;s salary is updated, but Grace&#8217;s name is not.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2192 <a href=\"https:\/\/sqlite.org\/lang_upsert.html\">Documentation<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><img decoding=\"async\" src=\"https:\/\/antonz.org\/assets\/codapi.svg\"\/> Code playgrounds<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Interactive examples in this post are powered by <a href=\"https:\/\/codapi.org\/\"><strong>codapi<\/strong><\/a> \u2014 the platform I&#8217;m building. Use it to embed code playgrounds into your online course, documentation, or blog.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">PostgreSQL: INSERT&#8230;ON CONFLICT<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PostgreSQL&#8217;s <code>on conflict do update<\/code> is very similar to SQLite&#8217;s (in fact, SQLite&#8217;s implementation was inspired by PostgreSQL&#8217;s). It inserts records with new primary\/unique keys (in our case, employee IDs) and updates the ones with existing keys:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert into employees\n(id, name, city, department, salary)\nvalues\n  (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n  (33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100)\non conflict (id) do update set\n  city = excluded.city,\n  salary = excluded.salary;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Added Frank and Alice, updated Diane and Emma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When updating a record, you can reference the old value by column name (<code>city<\/code>) and the new value with the <code>excluded<\/code> prefix (<code>excluded.city<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Note that the constraint reference after <code>on conflict<\/code> (it is called <em>conflict target<\/em> \u2014 <code>id<\/code> in our case) is required. You can also specify a constraint name:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>alter table employees add constraint uniq_employees_name unique (name);\n\ninsert into employees\n(id, name, city, department, salary)\nvalues\n  -- change Grace&#039;s name to Diane\n  (22, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;it&#039;, 90)\non conflict on constraint uniq_employees_name do nothing;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We only allow unique names, and &#8220;Diane&#8221; is already taken. Grace&#8217;s name remains unchanged.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see from the example above, PostgreSQL supports <code>do nothing<\/code> just like SQLite. It also supports the <code>where<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>insert into employees\n(id, name, city, department, salary)\nvalues\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (22, &#039;Grace&#039;, &#039;Berlin&#039;, &#039;it&#039;, 105)\non conflict (id) do update set\n  salary = excluded.salary where excluded.salary &lt; 100;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We only allow salaries under 100, so Emma gets updated but Grace does not.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Unlike SQLite, PostgreSQL does not allow multiple <code>on conflict<\/code> clauses:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>create unique index idx_employees_name on employees(name);\n\ninsert into employees\n(id, name, city, department, salary)\nvalues\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (22, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;it&#039;, 90)\non conflict (id) do update set\n  city = excluded.city,\n  salary = excluded.salary\non conflict (name) do nothing;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is an error: only one <code>on conflict<\/code> is allowed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2192 <a href=\"https:\/\/www.postgresql.org\/docs\/current\/sql-insert.html#SQL-ON-CONFLICT\">Documentation<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>insert...on conflict<\/code> is a fine way to do upserts on older PostgreSQL versions. For modern ones (15+) there is a better alternative&nbsp;\u2193<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">SQL Standard: MERGE<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now that we&#8217;ve seen all the creative ways vendors have implemented upsert in their systems, let&#8217;s look at the <em>standard<\/em> way \u2014 the <code>merge<\/code> statement:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>merge into employees\nusing (\n  values\n  (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n  (33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100)\n) as new (id, name, city, department, salary)\non employees.id = new.id\nwhen matched then update set\n  city = new.city,\n  salary = new.salary\nwhen not matched then insert\n  values (id, name, city, department, salary);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Added Frank and Alice, updated Diane and Emma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is what happened:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We specified the merge source in the <code>using<\/code> clause (here we use the <code>values<\/code> as the source, but <code>using<\/code> also accepts tables, views and subqueries).<\/li>\n\n\n\n<li>We gave the merge source an alias (<code>new<\/code>) for future reference.<\/li>\n\n\n\n<li>We defined the match criteria between the merge source (our <code>new<\/code> values) and the merge target (the <code>employees<\/code> table) using the <code>on<\/code> clause (<code>employees.id = new.id<\/code>).<\/li>\n\n\n\n<li>We set the action to take when the merge source and merge target <em>match<\/em> by the given criteria (update <code>city<\/code> and <code>salary<\/code> with new values).<\/li>\n\n\n\n<li>We set the action to take when the merge source and merge target <em>don&#8217;t match<\/em> (i.e., there are new records in the source, so we should insert them).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Whew. That&#8217;s a lot to process. But the <code>merge<\/code> statement can do much more. For example, you can change the inserted values:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>merge into employees\nusing (\n  values\n  (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n  (33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100)\n) as new (id, name, city, department, salary)\non employees.id = new.id\nwhen matched then update set\n  city = new.city,\n  salary = new.salary\nwhen not matched then insert\n  values (id, name || &#039; (new!)&#039;, city, department, salary);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We still update Diane and Emma, but we also change Frank and Alice&#8217;s names on insert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Or even delete the matching records:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>merge into employees\nusing (\n  values\n  (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n  (33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100)\n) as new (id, name, city, department, salary)\non employees.id = new.id\nwhen matched then delete\nwhen not matched then insert\n  values (id, name, city, department, salary);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Added Frank and Alice, deleted Diane and Emma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Or ignore new records altogether by omitting the <code>when not matched<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>merge into employees\nusing (\n  values\n  (11, &#039;Diane&#039;, &#039;Berlin&#039;, &#039;hr&#039;, 70),\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (25, &#039;Frank&#039;, &#039;Berlin&#039;, &#039;it&#039;, 120),\n  (33, &#039;Alice&#039;, &#039;Berlin&#039;, &#039;sales&#039;, 100)\n) as new (id, name, city, department, salary)\non employees.id = new.id\nwhen matched then update set\n  city = new.city,\n  salary = new.salary;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Updated Diane and Emma, ignored Frank and Alice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can perform conditional inserts, updates and deletes using <code>when...and<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>merge into employees\nusing (\n  values\n  (21, &#039;Emma&#039;, &#039;London&#039;, &#039;it&#039;, 95),\n  (22, &#039;Grace&#039;, &#039;Berlin&#039;, &#039;it&#039;, 105)\n) as new (id, name, city, department, salary)\non employees.id = new.id\nwhen matched and new.salary &lt; 100 then update set\n  city = new.city,\n  salary = new.salary;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We only allow salaries under 100, so Emma gets updated but Grace does not.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>merge<\/code> is supported in PostgreSQL (15+), SQL Server (2008+) and Oracle (11+). Of course, each of them has its own quirks. For example, PostgreSQL adds the non-standard <code>do nothing<\/code> action, while SQL Server supports different <code>when not matched<\/code> actions depending on where the value is missing \u2014 in a source or target dataset.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2192 Documentation: <a href=\"https:\/\/modern-sql.com\/caniuse\/merge\">Standard<\/a> \u2022 <a href=\"https:\/\/www.postgresql.org\/docs\/current\/sql-merge.html\">PostgreSQL<\/a> \u2022 <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/statements\/merge-transact-sql\">SQL Server<\/a> \u2022 <a href=\"https:\/\/docs.oracle.com\/en\/database\/oracle\/oracle-database\/23\/sqlrf\/MERGE.html\">Oracle<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Other database engines<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/cloud.google.com\/bigquery\/docs\/reference\/standard-sql\/dml-syntax#merge_statement\">BigQuery<\/a>, <a href=\"https:\/\/www.h2database.com\/html\/commands.html#merge_using\">H2<\/a>, <a href=\"https:\/\/docs.snowflake.com\/en\/sql-reference\/sql\/merge\">Snowflake<\/a>, <a href=\"https:\/\/www.ibm.com\/docs\/en\/db2\/11.5?topic=statements-merge\">DB2<\/a> \u2014 all support <code>merge<\/code> (each to some extent).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">MariaDB supports <a href=\"https:\/\/mariadb.com\/kb\/en\/replace\/\"><code>replace<\/code><\/a> and <a href=\"https:\/\/mariadb.com\/kb\/en\/insert-on-duplicate-key-update\/\"><code>on duplicate key update<\/code><\/a>, just like MySQL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Clickhouse does not have upsert SQL statements, but it does provide a <a href=\"https:\/\/clickhouse.com\/docs\/en\/guides\/developer\/deduplication#using-replacingmergetree-for-upserts\">ReplacingMergeTree<\/a> table engine that automatically performs a replace-style upsert on insert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cockroachdb supports <a href=\"https:\/\/www.cockroachlabs.com\/docs\/v23.1\/insert#on-conflict-clause\"><code>insert...on conflict<\/code><\/a> (like PostgreSQL), but with an optional conflict target. It also provides an <a href=\"https:\/\/www.cockroachlabs.com\/docs\/v23.1\/upsert\"><code>upsert<\/code><\/a> statement (ta-da!) that works like an automatic <code>on conflict do update<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DuckDB supports <a href=\"https:\/\/duckdb.org\/docs\/sql\/statements\/insert.html\"><code>insert or replace<\/code><\/a> and <code>insert or ignore<\/code> (like SQLite) as well as <a href=\"https:\/\/duckdb.org\/docs\/sql\/statements\/insert.html#on-conflict-clause\"><code>insert...on conflict<\/code><\/a> (also like SQLite), but without multiple clauses.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We&#8217;ve reviewed ways to do upserts in each of the major databases:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>replace<\/code> or <code>on duplicate key update<\/code> in MySQL (MariaDB).<\/li>\n\n\n\n<li><code>insert or replace<\/code> and <code>on conflict do<\/code> in SQLite.<\/li>\n\n\n\n<li><code>on conflict do<\/code> in older PostgreSQL versions.<\/li>\n\n\n\n<li><code>merge<\/code> in modern PostgreSQL, SQL Server and Oracle.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s funny that (almost) no DBMS has actually named the statement <code>upsert<\/code>, despite the popularity of the name.<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p class=\"excerpt\">Inserting and updating data with a single query. Source: Upsert in SQL<\/p>\n<p class=\"more-link-p\"><a class=\"more-link\" href=\"https:\/\/monodes.com\/predaelli\/2023\/09\/26\/upsert-in-sql\/\">Read more &rarr;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":4,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[72],"tags":[355,356,303,354,349],"class_list":["post-10822","post","type-post","status-publish","format-standard","hentry","category-documentations","tag-mariadb","tag-mysql","tag-postgresql","tag-sql","tag-sqlite"],"jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p6daft-2Oy","jetpack-related-posts":[{"id":2209,"url":"https:\/\/monodes.com\/predaelli\/2017\/02\/28\/how-to-backup-sql-server-to-sql-file-stack-overflow\/","url_meta":{"origin":10822,"position":0},"title":"How to backup Sql Server to sql file? &#8211; Stack Overflow","author":"Paolo Redaelli","date":"2017-02-28","format":"link","excerpt":"In \"Back UP\" I only get a bak file, but I would like to create .sql file Sorgente: How to backup Sql Server to sql file? - Stack Overflow I may need it pretty soon up vote 25 down vote Use SQL Server's Generate Scripts commend right click on the\u2026","rel":"","context":"In &quot;Senza categoria&quot;","block_context":{"text":"Senza categoria","link":"https:\/\/monodes.com\/predaelli\/category\/senza-categoria\/"},"img":{"alt_text":"apple-touch-icon@2","src":"https:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2017\/02\/apple-touch-icon%402-150x150-1.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":9172,"url":"https:\/\/monodes.com\/predaelli\/2022\/03\/23\/sql-fiddle\/","url_meta":{"origin":10822,"position":1},"title":"SQL Fiddle","author":"Paolo Redaelli","date":"2022-03-23","format":"link","excerpt":"SQL Fiddle Application for testing and sharing SQL queries.","rel":"","context":"In &quot;Tricks&quot;","block_context":{"text":"Tricks","link":"https:\/\/monodes.com\/predaelli\/category\/documentations\/tricks\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":8531,"url":"https:\/\/monodes.com\/predaelli\/2021\/06\/23\/take-your-sql-from-good-to-great-part-3\/","url_meta":{"origin":10822,"position":2},"title":"Take your SQL from Good to Great: Part 3","author":"Paolo Redaelli","date":"2021-06-23","format":"link","excerpt":"\u00a0 A quick inforgrafic of SQL joins: Time to join the JOIN movement. Take your SQL from Good to Great: Part 3 | by Taylor Brownlow | Jun, 2021 | Towards Data Science","rel":"","context":"In &quot;Documentations&quot;","block_context":{"text":"Documentations","link":"https:\/\/monodes.com\/predaelli\/category\/documentations\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":14199,"url":"https:\/\/monodes.com\/predaelli\/2025\/10\/26\/against-sql\/","url_meta":{"origin":10822,"position":3},"title":"Against SQL","author":"Paolo Redaelli","date":"2025-10-26","format":false,"excerpt":"Against SQL Against SQL Published 2021-07-09 TLDR The relational model is great: A shared universal data model allows cooperation between programs written in many different languages, running on different machines and with different lifespans. Normalization allows updating data without worrying about forgetting to update derived data. Physical data independence allows\u2026","rel":"","context":"In &quot;Documentations&quot;","block_context":{"text":"Documentations","link":"https:\/\/monodes.com\/predaelli\/category\/documentations\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":3522,"url":"https:\/\/monodes.com\/predaelli\/2017\/11\/22\/sql-server-why-should-an-application-not-use-the-sa-account-database-administrators-stack-exchange\/","url_meta":{"origin":10822,"position":4},"title":"sql server &#8211; Why should an application not use the sa account &#8211; Database Administrators Stack Exchange","author":"Paolo Redaelli","date":"2017-11-22","format":"status","excerpt":"sql server - Why should an application not use the sa account - Database Administrators Stack Exchange I understand that the sa account enables complete control over a SQL Server and all the databases, users, permissions etc. I have an absolute belief that applications should not use the sa password\u2026","rel":"","context":"In &quot;Proprietary software&quot;","block_context":{"text":"Proprietary software","link":"https:\/\/monodes.com\/predaelli\/category\/software\/proprietary-software\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2191,"url":"https:\/\/monodes.com\/predaelli\/2017\/02\/21\/hellish\/","url_meta":{"origin":10822,"position":5},"title":"Hellish\u00a0","author":"Paolo Redaelli","date":"2017-02-21","format":false,"excerpt":"Hell must be full of Microsoft servers that must be administered and reinstalled over and over again. I thought of being non proficient in Ms administration since I tried for two full days to install SQL server 2008 on a freshly installed Windows server 2008. I discovered that a whole-life\u2026","rel":"","context":"In &quot;Mood&quot;","block_context":{"text":"Mood","link":"https:\/\/monodes.com\/predaelli\/category\/mood\/"},"img":{"alt_text":"","src":"http:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2017\/02\/wp-image-1265243403jpeg.jpeg?w=528&resize=350%2C200","width":350,"height":200,"srcset":"http:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2017\/02\/wp-image-1265243403jpeg.jpeg?w=528&resize=350%2C200 1x, http:\/\/i0.wp.com\/monodes.com\/predaelli\/wp-content\/uploads\/sites\/4\/2017\/02\/wp-image-1265243403jpeg.jpeg?w=528&resize=525%2C300 1.5x"},"classes":[]}],"jetpack_likes_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/posts\/10822","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/comments?post=10822"}],"version-history":[{"count":0,"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/posts\/10822\/revisions"}],"wp:attachment":[{"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/media?parent=10822"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/categories?post=10822"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/monodes.com\/predaelli\/wp-json\/wp\/v2\/tags?post=10822"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}