PostgreSQL MERGE Statement
newSummary: In this tutorial, you will learn how to use the PostgreSQLMERGE
statement to conditionally insert, update, and delete rows of a table.
Introduction to the PostgreSQL MERGE statement
Have you ever needed to update a table but weren't sure whether to insert new records or update existing ones? PostgreSQL'sMERGE
command solves this common problem. Think ofMERGE
as a smart helper that can look at your data and decide whether to add new records, update existing ones, or even delete records, all in a single command.
Basic Concepts
Before we dive intoMERGE
, let's understand some basic terms:
- Target Table: The table you want to modify
- Source Table: The table containing your new or updated data
- Match Condition: The rule that determines if records match between your tables
Basic MERGE Syntax
Here's the basic structure of aMERGE
command:
MERGE INTO target_tableUSING source_tableON match_conditionWHEN MATCHED AND conditionTHEN UPDATE SET column1= value1, column2= value2WHEN MATCHED AND NOT conditionTHEN DELETEWHEN NOT MATCHED THEN INSERT (column1, column2)VALUES (value1, value2)RETURNING merge_action(), target_table.*;
ThisMERGE
statement performs three conditional actions ontarget_table
based on rows fromsource_table
:
- Update rows: If a match is found (
ON match_condition
) andcondition
is true, it updatescolumn1
andcolumn2
intarget_table
. - Delete rows: If a match is found but
condition
is false, it deletes the matching rows intarget_table
. - Insert rows: If no match is found, it inserts new rows into
target_table
using values fromsource_table
. - The
RETURNING
clause provides details of the operation (merge_action()
) and the affected rows.
Key Features in PostgreSQL 17
The new RETURNING clause support in PostgreSQL 17 offers several advantages:
- Action Tracking: The
merge_action()
function tells you exactly what happened to each row - Complete Row Access: You can return both old and new values for affected rows
- Immediate Feedback: No need for separate queries to verify the results
Setting Up Our Example
Let's create a sample database tracking a company's products and their inventory status:
-- Create the main products tableCREATE TABLE products ( product_idSERIAL PRIMARY KEY, name TEXT UNIQUE, priceDECIMAL(10,2), stockINTEGER, status TEXT, last_updatedTIMESTAMP DEFAULT CURRENT_TIMESTAMP);-- Insert some initial dataINSERT INTO products (name, price, stock,status)VALUES ('Laptop',999.99,50,'active'), ('Keyboard',79.99,100,'active'), ('Mouse',29.99,200,'active');-- Create a table for our updatesCREATE TABLE product_updates ( name TEXT, priceDECIMAL(10,2), stockINTEGER, status TEXT);-- Insert mixed update data (new products, updates, and discontinuations)INSERT INTO product_updatesVALUES ('Laptop',1099.99,75,'active'),-- Update: price and stock change ('Monitor',299.99,30,'active'),-- Insert: new product ('Keyboard',NULL,0,'discontinued'),-- Delete: mark as discontinued ('Headphones',89.99,50,'active');-- Insert: another new product
Using MERGE with RETURNING
Now let's see how PostgreSQL 17's enhancedMERGE
command can handle all three operations (INSERT
,UPDATE
,DELETE
) while providing detailed feedback through theRETURNING
clause:
MERGE INTO products pUSING product_updates uON p.name= u.nameWHEN MATCHED AND u.status= 'discontinued' THEN DELETEWHEN MATCHED AND u.status= 'active' THEN UPDATE SET price= COALESCE(u.price, p.price), stock= u.stock, status = u.status, last_updated= CURRENT_TIMESTAMPWHEN NOT MATCHED AND u.status= 'active' THEN INSERT (name, price, stock,status) VALUES (u.name, u.price, u.stock, u.status)RETURNING merge_action()as action, p.product_id, p.name, p.price, p.stock, p.status, p.last_updated;
Understanding the Output
TheRETURNING
clause will provide detailed information about each operation:
action | product_id | name | price | stock | status | last_updated---------+------------+------------+----------+-------+-------------+------------------------ UPDATE | 1 | Laptop | 1099.99 | 75 | active | 2024-12-04 17:41:58.226807 INSERT | 4 | Monitor | 299.99 | 30 | active | 2024-12-04 17:41:58.226807 DELETE | 2 | Keyboard | 79.99 | 100 | active | 2024-12-04 17:41:47.816064 INSERT | 5 | Headphones | 89.99 | 50 | active | 2024-12-04 17:41:58.226807
Let's break down what happened:
UPDATE
: The Laptop's price and stock were updatedDELETE
: The Keyboard is deleted from the products tableINSERT
: New Monitor and Headphones products were added
We can confirm the changes by querying the products table:
SELECT * FROM productsORDER BY product_id;
product_id | name | price | stock | status | last_updated------------+------------+----------+-------+-------------+------------------------ 1 | Laptop | 1099.99 | 75 | active | 2024-12-04 17:41:58.226807 3 | Mouse | 29.99 | 200 | active | 2024-12-04 17:41:47.816064 4 | Monitor | 299.99 | 30 | active | 2024-12-04 17:41:58.226807 5 | Headphones | 89.99 | 50 | active | 2024-12-04 17:41:58.226807
Advanced Usage with Conditions
You can add more complex conditions to yourMERGE
statement:
MERGE INTO products pUSING ( SELECT name, price, stock, status, CASE WHEN priceIS NULL AND status = 'discontinued' THEN 'DELETE' WHEN stock= 0 THEN 'OUT_OF_STOCK' ELSE status END as action_type FROM product_updates) uON p.name= u.nameWHEN MATCHED AND u.action_type= 'DELETE' THEN DELETEWHEN MATCHED AND u.action_type= 'OUT_OF_STOCK' THEN UPDATE SET status = 'inactive', stock= 0, last_updated= CURRENT_TIMESTAMPWHEN MATCHED THEN UPDATE SET price= COALESCE(u.price, p.price), stock= u.stock, status = u.status, last_updated= CURRENT_TIMESTAMPWHEN NOT MATCHED AND u.action_type!= 'DELETE' THEN INSERT (name, price, stock,status) VALUES (u.name, u.price, u.stock, u.status)RETURNING merge_action()as action, p.*, u.action_type;
Best Practices
Handle Source Data Carefully:
- Validate input data before the
MERGE
- Use subqueries to transform or clean data
- Consider using CTEs for complex data preparation
- Validate input data before the
Leverage RETURNING for Validation:
- Include the
merge_action()
for operation tracking - Consider returning both old and new values for logging purposes and validation
- Include the
Common Pitfalls to Avoid
- Ambiguous Matches: Ensure your
ON
clause creates unique matches - NULL Handling: Use
COALESCE
orIS NOT DISTINCT FROM
forNULL
values - Missing Conditions: Always handle all possible cases in your
WHEN
clauses
Conclusion
PostgreSQL 17's enhancedMERGE
command withRETURNING
clause support provides a powerful tool for data synchronization and maintenance. The ability to perform multiple operations in a single statement while getting immediate feedback makes it an invaluable feature for modern applications.
Frequently Asked Questions (FAQ)
- —What is the purpose of the
MERGE
statement in PostgreSQL? - The
MERGE
statement allows you to conditionallyINSERT
,UPDATE
, orDELETE
rows in a target table based on the presence of matching records in a source table. This consolidates multiple operations into a single, efficient command. - —When was the
MERGE
statement introduced in PostgreSQL? - The
MERGE
statement was officially introduced in PostgreSQL version 15, released in October 2022. - —How does the
MERGE
statement determine which operation to perform? - The
MERGE
statement uses a specifiedON
condition to match rows between the source and target tables. Based on whether a match is found (MATCHED
) or not (NOT MATCHED
), and any additional conditions, it executes the correspondingINSERT
,UPDATE
,DELETE
, orDO NOTHING
actions. - —Can I use the
MERGE
statement with views in PostgreSQL? - Yes, starting from PostgreSQL 17, the
MERGE
command can be used with updatable views. ForMERGE
to work with views, the views must be consistent: - Trigger-updatable views need
INSTEAD OF
triggers for all actions. - Auto-updatable views cannot have any triggers.
- Mixing types of views or using rule-updatable views is not allowed.
- —What privileges are required to execute a
MERGE
statement? - To execute a
MERGE
statement, you need: SELECT
privilege on the source table or query.- Appropriate privileges on the target table:
INSERT
privilege for insert actions. UPDATE
privilege for update actions.DELETE
privilege for delete actions.- —Is the
MERGE
statement atomic in PostgreSQL? - Yes, the
MERGE
statement in PostgreSQL is atomic. This means all specified actions (INSERT
,UPDATE
,DELETE
) are performed as a single unit. If an error occurs during execution, the entire operation is rolled back, ensuring data integrity. - —Can I use the
RETURNING
clause with theMERGE
statement? - Yes, starting from PostgreSQL 17, the
MERGE
statement supports theRETURNING
clause. This allows you to retrieve information about the rows affected by theMERGE
operation, including the specific action performed (INSERT
,UPDATE
, orDELETE
) on each row. - —How does the
MERGE
statement handle concurrent data modifications? - The
MERGE
statement ensures data consistency during concurrent operations by acquiring the necessary locks on the target table. This prevents other transactions from modifying the same rows simultaneously, thereby avoiding conflicts. - —Are there any performance considerations when using the
MERGE
statement? - While the
MERGE
statement simplifies complex operations into a single command, it's essential to ensure that theON
condition is well-optimized, typically by indexing the columns involved. Proper indexing can significantly enhance performance. - —Can I perform different actions based on additional conditions within the
MERGE
statement? - Yes, the
MERGE
statement allows for multipleWHEN
clauses with additional conditions. This enables you to specify different actions (INSERT
,UPDATE
,DELETE
, orDO NOTHING
) based on various criteria, providing fine-grained control over the operation.
Last updated on