PostgreSQL MERGE文の全て:構文、事例、パフォーマンスまで
PostgreSQLのMERGE
文は、異なるテーブル間でデータを同期させるための強力なツールです。SQL:2003規格で導入され、PostgreSQL 15以降で利用可能になったこの文は、INSERT
、UPDATE
、DELETE
の操作を1つのステートメントに統合し、複雑なデータ同期処理を簡素化します。本稿では、MERGE
文の構文、様々なユースケース、パフォーマンスに関する考慮事項について、詳細に解説します。
1. MERGE
文の基本構文
MERGE
文の基本的な構文は以下の通りです。
sql
MERGE INTO target_table AS target
USING source_table AS source
ON (join_condition)
WHEN MATCHED THEN
-- 一致する場合の処理 (UPDATEまたはDELETE)
UPDATE SET
column1 = source.column1,
column2 = source.column2,
...
[DELETE WHERE condition]
WHEN NOT MATCHED THEN
-- 一致しない場合の処理 (INSERT)
INSERT (column1, column2, ...)
VALUES (source.column1, source.column2, ...);
各要素の意味は以下の通りです。
MERGE INTO target_table AS target
: データを挿入、更新、または削除する対象となるテーブルを指定します。AS target
は、ターゲットテーブルにエイリアスを付けることができます(オプション)。USING source_table AS source
: データソースとなるテーブルまたはビューを指定します。AS source
は、ソーステーブルにエイリアスを付けることができます(オプション)。ON (join_condition)
: ターゲットテーブルとソーステーブルを結合するための条件を指定します。この条件が真となる行が「一致」とみなされます。WHEN MATCHED THEN ...
:ON
句の条件が真となる(一致する行が存在する)場合に実行される処理を記述します。UPDATE
句でターゲットテーブルの列を更新するか、DELETE
句で一致する行を削除できます。UPDATE SET ...
: ターゲットテーブルの列をソーステーブルの値に基づいて更新します。DELETE WHERE condition
: 指定された条件を満たす場合、一致する行を削除します。UPDATE
句と同時に使用することはできません。
WHEN NOT MATCHED THEN ...
:ON
句の条件が偽となる(一致する行が存在しない)場合に実行される処理を記述します。INSERT
句でソーステーブルの値をターゲットテーブルに挿入します。
補足:
WHEN MATCHED
句とWHEN NOT MATCHED
句は、それぞれ複数指定することができます。その場合、AND
条件を追加して、より詳細な条件分岐を行うことができます。target
エイリアスとsource
エイリアスは必須ではありませんが、特に複数の列が同じ名前を持つ場合に、コードの可読性を向上させるために推奨されます。DELETE
句を使用する場合、UPDATE
句と同時に使用することはできません。一致する行を更新するか削除するかのいずれかを選択する必要があります。INSERT
句では、DEFAULT
キーワードを使用して、デフォルト値を挿入できます。MERGE
文はトランザクション内で実行されることが推奨されます。
2. 様々なユースケース
MERGE
文は、様々なデータ同期シナリオで活用できます。以下に、代表的なユースケースをいくつか紹介します。
2.1. ETL(Extract, Transform, Load)処理
ETL処理は、様々なソースからデータを抽出、変換、そしてデータウェアハウスなどのターゲットシステムにロードするプロセスです。MERGE
文は、このプロセスにおいて、既存のデータを更新し、新しいデータを挿入する際に非常に役立ちます。
例えば、顧客情報を外部システムから定期的にインポートする場合を考えます。
“`sql
— 顧客情報をインポートするターゲットテーブル
CREATE TABLE customers (
customer_id INT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255),
last_update TIMESTAMP
);
— インポート元の顧客情報テーブル
CREATE TABLE staging_customers (
customer_id INT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255)
);
— MERGE文を使用して、顧客情報を同期する
MERGE INTO customers AS target
USING staging_customers AS source
ON (target.customer_id = source.customer_id)
WHEN MATCHED THEN
UPDATE SET
name = source.name,
email = source.email,
last_update = NOW()
WHEN NOT MATCHED THEN
INSERT (customer_id, name, email, last_update)
VALUES (source.customer_id, source.name, source.email, NOW());
“`
この例では、staging_customers
テーブルからcustomers
テーブルにデータを同期しています。customer_id
が一致する場合は、名前とメールアドレスを更新し、last_update
を現在時刻に更新します。一致しない場合は、新しい顧客情報をcustomers
テーブルに挿入します。
2.2. 次元テーブルの更新
データウェアハウスにおける次元テーブルは、分析に必要なコンテキスト情報を提供します。これらのテーブルは、通常、頻繁には変更されませんが、時間の経過とともに更新が必要になる場合があります。MERGE
文は、既存の情報を更新し、新しい情報を追加する際に効果的です。
例えば、製品情報を格納する次元テーブルを更新する場合を考えます。
“`sql
— 製品情報を格納する次元テーブル
CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(255),
category VARCHAR(255),
price DECIMAL(10, 2)
);
— 更新された製品情報を格納するテーブル
CREATE TABLE updated_products (
product_id INT PRIMARY KEY,
product_name VARCHAR(255),
category VARCHAR(255),
price DECIMAL(10, 2)
);
— MERGE文を使用して、製品情報を更新する
MERGE INTO products AS target
USING updated_products AS source
ON (target.product_id = source.product_id)
WHEN MATCHED THEN
UPDATE SET
product_name = source.product_name,
category = source.category,
price = source.price
WHEN NOT MATCHED THEN
INSERT (product_id, product_name, category, price)
VALUES (source.product_id, source.product_name, source.category, source.price);
“`
この例では、updated_products
テーブルからproducts
テーブルにデータを同期しています。product_id
が一致する場合は、製品名、カテゴリ、価格を更新します。一致しない場合は、新しい製品情報をproducts
テーブルに挿入します。
2.3. 監査ログの作成
MERGE
文は、データの変更を追跡するために、監査ログを作成する際にも利用できます。WHEN MATCHED
句とWHEN NOT MATCHED
句で、データの変更内容をログテーブルに記録することができます。
例えば、顧客情報の変更を記録する監査ログを作成する場合を考えます。
“`sql
— 顧客情報を格納するテーブル
CREATE TABLE customers (
customer_id INT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255)
);
— 顧客情報の変更履歴を記録する監査ログテーブル
CREATE TABLE customer_audit_log (
log_id SERIAL PRIMARY KEY,
customer_id INT,
change_type VARCHAR(20), — ‘INSERT’, ‘UPDATE’
old_name VARCHAR(255),
new_name VARCHAR(255),
old_email VARCHAR(255),
new_email VARCHAR(255),
change_date TIMESTAMP
);
— 更新前の顧客情報を格納するテーブル
CREATE TABLE staging_customers (
customer_id INT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255)
);
— MERGE文を使用して、顧客情報を同期し、監査ログを記録する
MERGE INTO customers AS target
USING staging_customers AS source
ON (target.customer_id = source.customer_id)
WHEN MATCHED THEN
UPDATE SET
name = source.name,
email = source.email
— UPDATE処理が発生した場合の監査ログ記録
INSERT INTO customer_audit_log (customer_id, change_type, old_name, new_name, old_email, new_email, change_date)
VALUES (target.customer_id, ‘UPDATE’, target.name, source.name, target.email, source.email, NOW())
WHEN NOT MATCHED THEN
INSERT (customer_id, name, email)
VALUES (source.customer_id, source.name, source.email)
— INSERT処理が発生した場合の監査ログ記録
INSERT INTO customer_audit_log (customer_id, change_type, old_name, new_name, old_email, new_email, change_date)
VALUES (source.customer_id, ‘INSERT’, NULL, source.name, NULL, source.email, NOW());
“`
この例では、customer_audit_log
テーブルに、顧客情報の変更履歴を記録しています。WHEN MATCHED
句では、UPDATE
処理が発生した場合に、変更前の名前とメールアドレス、変更後の名前とメールアドレス、そして変更日時を記録します。WHEN NOT MATCHED
句では、INSERT
処理が発生した場合に、新しい顧客情報と挿入日時を記録します。
2.4. データ重複排除
MERGE
文は、テーブル内の重複データを排除する際にも利用できます。
例えば、重複したメールアドレスを持つ顧客情報を排除する場合を考えます。
“`sql
— 顧客情報を格納するテーブル
CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255)
);
— 重複したメールアドレスを持つ顧客情報を削除する
WITH RankedCustomers AS (
SELECT
customer_id,
email,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY customer_id) AS rn
FROM
customers
)
DELETE FROM customers
WHERE customer_id IN (SELECT customer_id FROM RankedCustomers WHERE rn > 1);
“`
この例では、ROW_NUMBER()
ウィンドウ関数を使用して、各メールアドレスに対して連番を割り当てています。そして、連番が1より大きい(重複している)顧客情報を削除しています。MERGE
文を用いてより複雑な条件で重複排除を行うことも可能です。
3. パフォーマンスに関する考慮事項
MERGE
文は、複数の操作を1つのステートメントにまとめることで、パフォーマンスを向上させる可能性があります。しかし、不適切な使い方をすると、パフォーマンスが低下する可能性もあります。以下に、MERGE
文のパフォーマンスに関する考慮事項をいくつか紹介します。
3.1. インデックスの利用
ON
句で使用する結合条件には、インデックスを設定することが重要です。インデックスを使用することで、結合処理の効率が向上し、MERGE
文の実行速度が向上します。特に、大規模なテーブルに対してMERGE
文を実行する場合は、インデックスの利用が不可欠です。
3.2. ロック競合
MERGE
文は、ターゲットテーブルに対して排他ロックを取得するため、他のトランザクションとのロック競合が発生する可能性があります。特に、頻繁に更新されるテーブルに対してMERGE
文を実行する場合は、ロック競合を最小限に抑えるために、トランザクションの期間を短くしたり、ロックの種類を調整したりすることを検討する必要があります。
3.3. 統計情報の更新
PostgreSQLのオプティマイザは、テーブルの統計情報を使用して、最適な実行計画を決定します。MERGE
文を実行する前に、ターゲットテーブルとソーステーブルの統計情報を更新することで、オプティマイザがより適切な実行計画を選択し、パフォーマンスを向上させることができます。ANALYZE
コマンドを使用して、統計情報を更新できます。
3.4. データの分割
大規模なテーブルに対してMERGE
文を実行する場合は、データを小さなチャンクに分割し、チャンクごとにMERGE
文を実行することで、ロック競合を緩和し、パフォーマンスを向上させることができます。パーティショニングテーブルを使用すると、この処理をより効率的に行うことができます。
3.5. EXPLAIN ANALYZEによる実行計画の分析
EXPLAIN ANALYZE
コマンドを使用することで、MERGE
文の実行計画を詳細に分析し、ボトルネックとなっている箇所を特定することができます。実行計画を分析することで、インデックスの追加、クエリの書き換え、データの分割など、パフォーマンスを改善するための対策を講じることができます。
3.6. WHEN MATCHED
句の条件分岐
WHEN MATCHED
句に複数の条件分岐がある場合、それぞれの条件分岐の評価順序がパフォーマンスに影響を与える可能性があります。より頻繁に発生する条件を先に評価することで、全体的な実行時間を短縮できる場合があります。
3.7. トリガー
MERGE
文によってトリガーが実行される場合、トリガーの処理内容がパフォーマンスに影響を与える可能性があります。トリガーの処理を最適化することで、MERGE
文全体のパフォーマンスを向上させることができます。
4. MERGE
文の制限事項
MERGE
文は非常に強力なツールですが、いくつかの制限事項があります。
- PostgreSQL 15以降でのみ利用可能:
MERGE
文は、PostgreSQL 15以降でのみ利用できます。古いバージョンのPostgreSQLでは、INSERT ... ON CONFLICT DO UPDATE
などの代替手段を使用する必要があります。 - ソーステーブルへの複数回のアクセス:
MERGE
文は、ソーステーブルに複数回アクセスする可能性があります。これにより、ソーステーブルが非常に大きい場合に、パフォーマンスが低下する可能性があります。 - ロック競合: 前述の通り、
MERGE
文はターゲットテーブルに対して排他ロックを取得するため、他のトランザクションとのロック競合が発生する可能性があります。 - 複雑な条件分岐:
WHEN MATCHED
句に非常に複雑な条件分岐がある場合、クエリの可読性が低下し、デバッグが困難になる可能性があります。
5. まとめ
PostgreSQLのMERGE
文は、異なるテーブル間でデータを同期させるための強力なツールです。INSERT
、UPDATE
、DELETE
の操作を1つのステートメントに統合することで、複雑なデータ同期処理を簡素化し、パフォーマンスを向上させる可能性があります。
本稿では、MERGE
文の構文、様々なユースケース、パフォーマンスに関する考慮事項について詳細に解説しました。MERGE
文を効果的に活用することで、データウェアハウス、ETL処理、監査ログの作成など、様々なデータ管理シナリオを効率化することができます。
ただし、MERGE
文は、いくつかの制限事項があります。ON
句のインデックス設定、ロック競合の回避、統計情報の更新など、パフォーマンスに関する考慮事項を十分に理解した上で、MERGE
文を適切に使用することが重要です。EXPLAIN ANALYZE
コマンドを使用して実行計画を分析し、必要に応じてクエリを最適化することで、MERGE
文のパフォーマンスを最大限に引き出すことができます。
この記事が、PostgreSQLのMERGE
文を理解し、効果的に活用するための助けになることを願っています。