ターゲットとソースのテーブルが同じ列名と列数を持つ場合に、個々の列を指定せずに同期処理を記述できる MERGE ALL BY NAME を試してみた #SnowflakeDB
はじめに
2025年10月のアップデートで、MERGE ALL BY NAME
という機能がパブリックプレビューとなりました。
こちらの機能を試してみた内容を本記事でまとめてみます。
アップデートの概要
これまでのマージ文による操作では、ターゲットとなるテーブルをソーステーブルの内容と同期する際、更新や挿入を行うすべての列名をそれぞれ指定する必要がありました。
特に列数が多いテーブルや、テーブル スキーマが頻繁に変更される場合に、これは手間でした。
今回のアップデートで、MERGE ALL BY NAME
句が追加されました。このMERGE ALL BY NAME
を使用すると、ターゲットとソースのテーブルが同じ列名と列数を持つ場合、列の順序が異なっていても、自動的に対応する列を特定し、更新・挿入を行えます。
これにより、データ同期のクエリをより簡潔に記述できるようになります。
試してみる
こちらの機能を試してみます。
はじめに、上記の公式ドキュメント記載の例で基本的な動作を試してみます。
検証用のターゲット、ソーステーブルを用意します。
-- ターゲットテーブルを作成(列の順序は id, x, y)
CREATE OR REPLACE TABLE merge_example_target_all (
id INTEGER,
x INTEGER,
y VARCHAR);
-- ターゲットテーブルに初期データを挿入
INSERT INTO merge_example_target_all (id, x, y) VALUES
(1, 10, 'Skiing'),
(2, 20, 'Snowboarding');
-- ソーステーブルを作成(ターゲットと列の順序が異なる: id, y, x)
CREATE OR REPLACE TABLE merge_example_source_all (
id INTEGER,
y VARCHAR,
x INTEGER);
-- ソーステーブルにデータを挿入
INSERT INTO merge_example_source_all (id, y, x) VALUES
(1, 'Skiing', 10),
(2, 'Snowboarding', 25),
(3, 'Skating', 30);
変更前のターゲットテーブルを確認
>SELECT * FROM merge_example_target_all;
+----+----+--------------+
| ID | X | Y |
|----+----+--------------|
| 1 | 10 | Skiing |
| 2 | 20 | Snowboarding |
+----+----+--------------+
MERGE 文を実行します。
>MERGE INTO merge_example_target_all
USING merge_example_source_all
ON merge_example_target_all.id = merge_example_source_all.id
WHEN MATCHED THEN
UPDATE ALL BY NAME
WHEN NOT MATCHED THEN
INSERT ALL BY NAME;
+-------------------------+------------------------+
| number of rows inserted | number of rows updated |
|-------------------------+------------------------|
| 1 | 2 |
+-------------------------+------------------------+
-- 更新後のターゲットテーブル
>SELECT * FROM merge_example_target_all;
+----+----+--------------+
| ID | X | Y |
|----+----+--------------|
| 3 | 30 | Skating |
| 1 | 10 | Skiing |
| 2 | 25 | Snowboarding |
+----+----+--------------+
ターゲットテーブルを更新できました。ポイントは以下です。
WHEN MATCHED ... THEN ALL BY NAME
- 結合条件が一致したレコードに対して、ソースの全列を、同じ名前のターゲットの列に上書き
WHEN NOT MATCHED ... THEN ALL BY NAME
- 結合条件が一致しなかったレコードに対して、ソースの全列を、同じ名前のターゲットの列に挿入
ALL BY NAME
を使うことで、列の順序が異なる場合でも、簡潔にデータの同期を実行できます。
通常のマージ文の場合、以下のように列ごとの記述が必要です。
MERGE INTO merge_example_target_all
USING merge_example_source_all
ON merge_example_target_all.id = merge_example_source_all.id
WHEN MATCHED THEN
-- 更新対象列をそれぞれ指定
UPDATE SET merge_example_target_all.x = merge_example_source_all.x,
merge_example_target_all.y = merge_example_source_all.y
WHEN NOT MATCHED THEN
-- 挿入対象の列をそれぞれ指定
INSERT (id, x, y) VALUES (merge_example_source_all.id,
merge_example_source_all.x,
merge_example_source_all.y);
ALL BY NAME
が使える条件として、ターゲットとソースのテーブルが同じ列名と列数を持つ必要があります。
そのため、以下のよう場合、エラーとなります。
- 列数が異なる場合
-- ターゲットテーブル
CREATE OR REPLACE TABLE target_table (
id INTEGER,
x INTEGER);
INSERT INTO target_table (id, x) VALUES
(1, 10),
(2, 20);
-- ソーステーブル
CREATE OR REPLACE TABLE source_table (
id INTEGER,
x INTEGER,
y VARCHAR);
INSERT INTO source_table (id, x, y) VALUES
(1, 100, 'Hiking'),
(3, 300, 'Kayaking');
-- 列数が異なるのでエラー
>MERGE INTO target_table
USING source_table
ON target_table.id = source_table.id
WHEN MATCHED THEN
UPDATE ALL BY NAME
WHEN NOT MATCHED THEN
INSERT ALL BY NAME;
002032 (21000): SQL compilation error: error line 1 at position 0
Column count mismatch in MERGE statement with ALL BY NAME subclause, source table has (3) columns but target table has (2).
- 列名が異なる場合
-- ターゲットテーブル
CREATE OR REPLACE TABLE target_table (
id INTEGER,
x INTEGER,
y VARCHAR);
INSERT INTO target_table (id, x, y) VALUES
(1, 10, 'Target1'),
(2, 20, 'Target2');
-- ソーステーブル
CREATE OR REPLACE TABLE source_table (
id INTEGER,
x INTEGER,
z VARCHAR);
INSERT INTO source_table (id, x, z) VALUES
(1, 100, 'Source1'),
(3, 300, 'Source3');
-- 列名が異なるのでエラー
MERGE INTO target_table
USING source_table
ON target_table.id = source_table.id
WHEN MATCHED THEN
UPDATE ALL BY NAME
WHEN NOT MATCHED THEN
INSERT ALL BY NAME;
002033 (42601): SQL compilation error: error line 1 at position 0
Missing column in MERGE statement with ALL BY NAME subclause, the column Y is in the target table but not the source.
ソース側にターゲットテーブルのカラムがない場合など、サブクエリは問題なく使用できます。
-- ターゲットテーブル
CREATE OR REPLACE TABLE sales (
sale_id INTEGER,
product_name VARCHAR,
quantity INTEGER,
unit_price DECIMAL(10, 2),
total_sale DECIMAL(10, 2)
);
INSERT INTO sales (sale_id, product_name, quantity, unit_price, total_sale) VALUES
(1, 'Laptop', 2, 1200.00, 2400.00),
(2, 'Mouse', 10, 25.00, 250.00);
-- ソーステーブル
CREATE OR REPLACE TABLE new_sales (
sale_id INTEGER,
product_name VARCHAR,
quantity INTEGER
);
INSERT INTO new_sales (sale_id, product_name, quantity) VALUES
(1, 'Laptop', 3),
(3, 'Keyboard', 5);
-- ソーステーブル
CREATE OR REPLACE TABLE product_info (
product_name VARCHAR,
cost DECIMAL(10, 2)
);
INSERT INTO product_info (product_name, cost) VALUES
('Laptop', 1000.00),
('Keyboard', 50.00);
-- マージ
MERGE INTO sales AS s
USING (
SELECT
ns.sale_id,
ns.product_name,
ns.quantity,
NULL::DECIMAL(10, 2) AS unit_price,
ns.quantity * pi.cost AS total_sale
FROM new_sales AS ns
JOIN product_info AS pi
ON ns.product_name = pi.product_name
) AS source_data
ON s.sale_id = source_data.sale_id
WHEN MATCHED THEN
UPDATE ALL BY NAME
WHEN NOT MATCHED THEN
INSERT ALL BY NAME;
さいごに
簡単ではありましたが、MERGE ALL BY NAME
を試してみました。
個々の列を指定せずにマージ文による同期処理を記述できるため、意図しない列への書き込み防止やクエリの可読性・メンテナンス性の向上にも役立つ機能と思います。
こちらの内容が何かの参考になれば幸いです。