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) andconditionis true, it updatescolumn1andcolumn2intarget_table. - Delete rows: If a match is found but
conditionis false, it deletes the matching rows intarget_table. - Insert rows: If no match is found, it inserts new rows into
target_tableusing values fromsource_table. - The
RETURNINGclause 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 productUsing 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.226807Let'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.226807Advanced 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
ONclause creates unique matches - NULL Handling: Use
COALESCEorIS NOT DISTINCT FROMforNULLvalues - Missing Conditions: Always handle all possible cases in your
WHENclauses
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
MERGEstatement in PostgreSQL? - The
MERGEstatement allows you to conditionallyINSERT,UPDATE, orDELETErows 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
MERGEstatement introduced in PostgreSQL? - The
MERGEstatement was officially introduced in PostgreSQL version 15, released in October 2022. - —How does the
MERGEstatement determine which operation to perform? - The
MERGEstatement uses a specifiedONcondition 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 NOTHINGactions. - —Can I use the
MERGEstatement with views in PostgreSQL? - Yes, starting from PostgreSQL 17, the
MERGEcommand can be used with updatable views. ForMERGEto work with views, the views must be consistent: - Trigger-updatable views need
INSTEAD OFtriggers 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
MERGEstatement? - To execute a
MERGEstatement, you need: SELECTprivilege on the source table or query.- Appropriate privileges on the target table:
INSERTprivilege for insert actions. UPDATEprivilege for update actions.DELETEprivilege for delete actions.- —Is the
MERGEstatement atomic in PostgreSQL? - Yes, the
MERGEstatement 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
RETURNINGclause with theMERGEstatement? - Yes, starting from PostgreSQL 17, the
MERGEstatement supports theRETURNINGclause. This allows you to retrieve information about the rows affected by theMERGEoperation, including the specific action performed (INSERT,UPDATE, orDELETE) on each row. - —How does the
MERGEstatement handle concurrent data modifications? - The
MERGEstatement 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
MERGEstatement? - While the
MERGEstatement simplifies complex operations into a single command, it's essential to ensure that theONcondition 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
MERGEstatement? - Yes, the
MERGEstatement allows for multipleWHENclauses 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