PostgreSQL Upsert: Update if Exists, Insert if Not

Updated: January 4, 2024 By: Guest Contributor Post a comment

Overview

Performing upsert operations (update existing records or insert new ones if they don’t exist) can be essential for managing database integrity and ensuring efficient data storage. In PostgreSQL, this procedure is conveniently facilitated by the ON CONFLICT clause, which is capable of handling potential conflicts arising during insert operations. Overview

The upsert command in PostgreSQL is a powerful feature allowing you to easily manage data by either updating existing rows if they match on unique constraint or inserting new ones if no match is found. This tutorial will guide you through using PostgreSQL’s upsert feature with comprehensive examples.

Mastering Upserts in PostgreSQL

Before diving into the various coding examples, it’s important to understand the underlying principle of upsert. PostgreSQL utilizes the INSERT INTO ... ON CONFLICT syntax to perform an upsert. If during an insert operation a conflict occurs (typically a violation of a unique constraint), the ON CONFLICT clause dictates how PostgreSQL should resolve it.

Basic Upsert Operation

INSERT INTO table_name(column1, column2) VALUES(value1, value2) ON CONFLICT (column1) DO UPDATE SET column2 = excluded.column2;

This basic upsert example attempts to insert a new row into table_name. If a row with the same column1 already exists, PostgreSQL updates column2 with the new value instead.

Specifying Conditional Upserts

INSERT INTO table_name(column1, column2) VALUES(value1, value2) ON CONFLICT (column1) DO UPDATE SET column2 = excluded.column2 WHERE table_name.column2 IS NULL OR table_name.column2 != excluded.column2;

In this example, the upsert operation is made conditional. The update only happens if column2 is null or different from the value we are trying to insert, preventing unnecessary updates.

Handling Multiple Conflicts

INSERT INTO table_name(column1, column2, column3) VALUES(value1, value2, value3) ON CONFLICT (column1) DO UPDATE SET column2 = excluded.column2, column3 = excluded.column3 WHERE table_name.column3 IS NULL OR table_name.column3 != excluded.column3;

This upsert handles potential conflicts on multiple columns. Here, both column2 and column3 can be conditionally updated based on the evaluation of column3 against the new incoming value.

Advanced Upsert Techniques

Using Upserts with Return Statements

INSERT INTO table_name(column1, column2) VALUES(value1, value2) ON CONFLICT (column1) DO UPDATE SET column2 = excluded.column2 RETURNING *;

The RETURNING keyword allows retrieving the record that was either inserted or updated, giving you immediate feedback on the operation’s result.

Concatenating String Values on Conflict

INSERT INTO table_name(id, comments) VALUES(1, 'Initial comment') ON CONFLICT (id) DO UPDATE SET comments = table_name.comments || ';' || excluded.comments;

For fields that contain string data, you sometimes want to concatenate the existing value with the new one upon conflict instead of replacing it entirely. The || operator facilitates this type of string concatenation.

Automating Timestamps on Upsert

INSERT INTO table_name(id, data, last_update) VALUES(1, 'new data', NOW()) ON CONFLICT (id) DO UPDATE SET data = excluded.data, last_update = NOW();

In this more advanced scenario, you can use the NOW() function to automatically update a timestamp column on each upsert operation, helping keep track of when the last insert or update occurred.

Custom Conflict Actions

INSERT INTO table_name(id, account_balance) VALUES(1, 100) ON CONFLICT (id) DO UPDATE SET account_balance = table_name.account_balance + excluded.account_balance;

When handling numerical data, upserts allow for custom conflict resolution strategies like incrementing existing values. In our example, we add the new amount to the existing balance upon conflict.

Excluding Columns from Upsert

INSERT INTO table_name(id, data, last_update) VALUES(1, 'new data', NOW()) ON CONFLICT(id) DO UPDATE SET data = excluded.data WHERE table_name.last_update <= excluded.last_update;

It’s often important to preserve certain column values during an upsert. By placing conditions in the WHERE clause, we can ensure the last_update column, for example, only gets updated if the new value is more recent.

Conclusion

Understanding and mastering the upsert operation in PostgreSQL is key to maintaining a clean, efficient database. Whether handling conflicts with basic or advanced techniques, upsert provides the flexibility and power necessary to deal with data insertions and updates effectively. All the examples given should help you navigate the intricacies of upserts and apply them to your PostgreSQL operations with confidence.