{"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>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><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>We will use the toy <code class=\"\" data-line=\"\">employees<\/code> table:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">\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>Let&#8217;s say we are adding two new employees:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">(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>Hello, Frank. Hello, Alice.<\/p>\n\n\n\n<p>And updating the other two:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">- (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>Diane is moving to Berlin, while Emma&#8217;s salary is increasing to 95.<\/p>\n\n\n\n<p>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><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>Let&#8217;s try the easy way:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>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>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>MySQL&#8217;s <code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Added Frank and Alice, replaced Diane and Emma.<\/p>\n\n\n\n<p>The bad thing about <code class=\"\" data-line=\"\">replace<\/code> is that it does an actual <code class=\"\" data-line=\"\">delete<\/code> followed by <code class=\"\" data-line=\"\">insert<\/code>, which has some unexpected <a href=\"https:\/\/stackoverflow.com\/a\/548570\">side effects<\/a> like firing <code class=\"\" data-line=\"\">on delete<\/code> triggers.<\/p>\n\n\n\n<p>Also, you can&#8217;t reference the old values when replacing the record. For example, you can&#8217;t set <code class=\"\" data-line=\"\">city<\/code> = <code class=\"\" data-line=\"\">Berlin (prev. London)<\/code> for Diane.<\/p>\n\n\n\n<p>\u2192 <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/replace.html\">Documentation<\/a><\/p>\n\n\n\n<p>To ignore the duplicates and only perform inserts, you can use <code class=\"\" data-line=\"\">insert ignore<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Added Frank and Alice, ignored Diane and Emma.<\/p>\n\n\n\n<p>\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><code class=\"\" data-line=\"\">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>MySQL&#8217;s <code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Added Frank and Alice, updated Diane and Emma.<\/p>\n\n\n\n<p>When updating a record, you can reference the old value by column name (<code class=\"\" data-line=\"\">city<\/code>) and the new value with the <code class=\"\" data-line=\"\">values()<\/code> function (<code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Updated the city for Diane and kept the old value for reference.<\/p>\n\n\n\n<p>Starting with MySQL 8.0, you can alias the new values and not use the ugly <code class=\"\" data-line=\"\">values()<\/code> function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Using the <code class=\"\" data-line=\"\">new<\/code> alias for new records.<\/p>\n\n\n\n<p>\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>SQLite&#8217;s <code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Added Frank and Alice, replaced Diane and Emma.<\/p>\n\n\n\n<p>Like MySQL&#8217;s <code class=\"\" data-line=\"\">replace<\/code>, SQLite&#8217;s <code class=\"\" data-line=\"\">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>SQLite provides a number of alternative strategies instead of <code class=\"\" data-line=\"\">replace<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code class=\"\" data-line=\"\">ignore<\/code>: skips the duplicate records (similar to MySQL&#8217;s <code class=\"\" data-line=\"\">ignore<\/code>).<\/li>\n\n\n\n<li><code class=\"\" data-line=\"\">rollback<\/code>: aborts the current statement and rolls back the entire transaction.<\/li>\n\n\n\n<li><code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Since there are conflicts, <code class=\"\" data-line=\"\">abort<\/code> reverts all changes. The table remains as it was.<\/p>\n\n\n\n<p>\u2192 <a href=\"https:\/\/sqlite.org\/lang_conflict.html\">Documentation<\/a><\/p>\n\n\n\n<p><code class=\"\" data-line=\"\">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>SQLite&#8217;s <code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Added Frank and Alice, updated Diane and Emma.<\/p>\n\n\n\n<p>When updating a record, you can reference the old value by column name (<code class=\"\" data-line=\"\">city<\/code>) and the new value with the <code class=\"\" data-line=\"\">excluded<\/code> prefix (<code class=\"\" data-line=\"\">excluded.city<\/code>).<\/p>\n\n\n\n<p>You can optionally disable some updates using the <code class=\"\" data-line=\"\">where<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>We only allow salaries under 100, so Emma gets updated but Grace does not.<\/p>\n\n\n\n<p>Besides <code class=\"\" data-line=\"\">do update<\/code>, there is also <code class=\"\" data-line=\"\">do nothing<\/code>, which ignores conflicts and only inserts new records:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Added Frank and Alice, ignored Diane and Emma.<\/p>\n\n\n\n<p><code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>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>\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>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>PostgreSQL&#8217;s <code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Added Frank and Alice, updated Diane and Emma.<\/p>\n\n\n\n<p>When updating a record, you can reference the old value by column name (<code class=\"\" data-line=\"\">city<\/code>) and the new value with the <code class=\"\" data-line=\"\">excluded<\/code> prefix (<code class=\"\" data-line=\"\">excluded.city<\/code>).<\/p>\n\n\n\n<p>Note that the constraint reference after <code class=\"\" data-line=\"\">on conflict<\/code> (it is called <em>conflict target<\/em> \u2014 <code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>We only allow unique names, and &#8220;Diane&#8221; is already taken. Grace&#8217;s name remains unchanged.<\/p>\n\n\n\n<p>As you can see from the example above, PostgreSQL supports <code class=\"\" data-line=\"\">do nothing<\/code> just like SQLite. It also supports the <code class=\"\" data-line=\"\">where<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>We only allow salaries under 100, so Emma gets updated but Grace does not.<\/p>\n\n\n\n<p>Unlike SQLite, PostgreSQL does not allow multiple <code class=\"\" data-line=\"\">on conflict<\/code> clauses:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>This is an error: only one <code class=\"\" data-line=\"\">on conflict<\/code> is allowed.<\/p>\n\n\n\n<p>\u2192 <a href=\"https:\/\/www.postgresql.org\/docs\/current\/sql-insert.html#SQL-ON-CONFLICT\">Documentation<\/a><\/p>\n\n\n\n<p><code class=\"\" data-line=\"\">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>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 class=\"\" data-line=\"\">merge<\/code> statement:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Added Frank and Alice, updated Diane and Emma.<\/p>\n\n\n\n<p>Here is what happened:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We specified the merge source in the <code class=\"\" data-line=\"\">using<\/code> clause (here we use the <code class=\"\" data-line=\"\">values<\/code> as the source, but <code class=\"\" data-line=\"\">using<\/code> also accepts tables, views and subqueries).<\/li>\n\n\n\n<li>We gave the merge source an alias (<code class=\"\" data-line=\"\">new<\/code>) for future reference.<\/li>\n\n\n\n<li>We defined the match criteria between the merge source (our <code class=\"\" data-line=\"\">new<\/code> values) and the merge target (the <code class=\"\" data-line=\"\">employees<\/code> table) using the <code class=\"\" data-line=\"\">on<\/code> clause (<code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">city<\/code> and <code class=\"\" data-line=\"\">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>Whew. That&#8217;s a lot to process. But the <code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>We still update Diane and Emma, but we also change Frank and Alice&#8217;s names on insert.<\/p>\n\n\n\n<p>Or even delete the matching records:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Added Frank and Alice, deleted Diane and Emma.<\/p>\n\n\n\n<p>Or ignore new records altogether by omitting the <code class=\"\" data-line=\"\">when not matched<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>Updated Diane and Emma, ignored Frank and Alice.<\/p>\n\n\n\n<p>You can perform conditional inserts, updates and deletes using <code class=\"\" data-line=\"\">when...and<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">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><a href=\"https:\/\/antonz.org\/sql-upsert\/#edit\">Edit<\/a><\/p>\n\n\n\n<p>We only allow salaries under 100, so Emma gets updated but Grace does not.<\/p>\n\n\n\n<p><code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">do nothing<\/code> action, while SQL Server supports different <code class=\"\" data-line=\"\">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>\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><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 class=\"\" data-line=\"\">merge<\/code> (each to some extent).<\/p>\n\n\n\n<p>MariaDB supports <a href=\"https:\/\/mariadb.com\/kb\/en\/replace\/\"><code class=\"\" data-line=\"\">replace<\/code><\/a> and <a href=\"https:\/\/mariadb.com\/kb\/en\/insert-on-duplicate-key-update\/\"><code class=\"\" data-line=\"\">on duplicate key update<\/code><\/a>, just like MySQL.<\/p>\n\n\n\n<p>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>Cockroachdb supports <a href=\"https:\/\/www.cockroachlabs.com\/docs\/v23.1\/insert#on-conflict-clause\"><code class=\"\" data-line=\"\">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 class=\"\" data-line=\"\">upsert<\/code><\/a> statement (ta-da!) that works like an automatic <code class=\"\" data-line=\"\">on conflict do update<\/code>.<\/p>\n\n\n\n<p>DuckDB supports <a href=\"https:\/\/duckdb.org\/docs\/sql\/statements\/insert.html\"><code class=\"\" data-line=\"\">insert or replace<\/code><\/a> and <code class=\"\" data-line=\"\">insert or ignore<\/code> (like SQLite) as well as <a href=\"https:\/\/duckdb.org\/docs\/sql\/statements\/insert.html#on-conflict-clause\"><code class=\"\" data-line=\"\">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>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 class=\"\" data-line=\"\">replace<\/code> or <code class=\"\" data-line=\"\">on duplicate key update<\/code> in MySQL (MariaDB).<\/li>\n\n\n\n<li><code class=\"\" data-line=\"\">insert or replace<\/code> and <code class=\"\" data-line=\"\">on conflict do<\/code> in SQLite.<\/li>\n\n\n\n<li><code class=\"\" data-line=\"\">on conflict do<\/code> in older PostgreSQL versions.<\/li>\n\n\n\n<li><code class=\"\" data-line=\"\">merge<\/code> in modern PostgreSQL, SQL Server and Oracle.<\/li>\n<\/ul>\n\n\n\n<p>It&#8217;s funny that (almost) no DBMS has actually named the statement <code class=\"\" data-line=\"\">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":{"inline_featured_image":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_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}},"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_featured_media_url":"","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,"_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}]}}