
【新機能】Dynamic TableのPrimary Keyサポートが一般提供となったのでチュートリアルを試してみた
かわばたです。
2026年4月16日にDynamic TableのPrimary Keyサポートが一般提供(GA)となりました。
従来、INSERT OVERWRITEで定期的に書き換えられるベーステーブルでは、Snowflakeが変更を検知できずフルリフレッシュが必要でした。Primary Key(RELYプロパティ付き)を設定することで、行レベルの変更追跡 が可能となり、下流のDynamic Tableでインクリメンタルリフレッシュが実現できるようになりました。
本記事では、この新機能を SQLによる基本動作検証 および パフォーマンス比較 の観点から検証します。
概要
背景
従来の Dynamic Table パイプラインでは、以下のようなケースでインクリメンタルリフレッシュが機能しない課題がありました。
- ベーステーブルが INSERT OVERWRITE で定期的に全件書き換えされると、変更追跡カラムが使えなくなる
- フルリフレッシュモードの上流 Dynamic Table がある場合、下流はインクリメンタルリフレッシュを利用できない
- 全件結合の再計算が発生し、ウェアハウスコストとリフレッシュ時間が増大する
新機能の内容
2026年4月16日にGAとなった Primary Key サポートにより、以下の機能が利用可能になりました。
| 機能 | 説明 |
|---|---|
| ベーステーブルのPrimary Key(RELY) | RELY プロパティ付きPKにより、INSERT OVERWRITE後も行レベルの変更追跡が可能 |
| クエリ導出のUnique Key | GROUP BY や QUALIFY ROW_NUMBER() = 1 から自動的にユニークキーを導出 |
| フルリフレッシュDT下流のインクリメンタルリフレッシュ | 上流DTにユニークキーがあれば、下流DTで REFRESH_MODE = INCREMENTAL を明示指定可能 |
構文
-- ベーステーブルにPrimary Key(RELY付き)を設定
CREATE TABLE my_dimension (
product_id INT PRIMARY KEY RELY,
product_name VARCHAR(200),
category VARCHAR(100),
price NUMBER(10, 2)
) CHANGE_TRACKING = TRUE;
-- 既存テーブルにRELYプロパティを付与
ALTER TABLE my_dimension ALTER CONSTRAINT my_pk_constraint RELY;
-- Primary Keyを活用したDynamic Table(インクリメンタルリフレッシュ)
CREATE DYNAMIC TABLE my_enriched_orders
TARGET_LAG = DOWNSTREAM
WAREHOUSE = my_wh
REFRESH_MODE = INCREMENTAL
AS
SELECT f.order_id, d.product_name, f.quantity, d.price
FROM fact_orders f
JOIN my_dimension d ON f.product_id = d.product_id;
-- Dynamic Tableの導出Unique Keyを確認
SHOW UNIQUE KEYS IN my_enriched_orders;
検証環境
| 項目 | 値 |
|---|---|
| ロール | ACCOUNTADMIN |
| ウェアハウス | COMPUTE_WH |
| データベース | TEMP(検証用に作成) |
| ディメンションテーブル | 100,000件(10カテゴリ) |
| ファクトテーブル | 10,000,000件 |
Primary Key による変更追跡の基本動作検証(SQL)
検証用データベースとテーブルの作成
CREATE DATABASE IF NOT EXISTS TEMP;
CREATE SCHEMA IF NOT EXISTS TEMP.TUTORIAL;
-- Primary Key付きディメンションテーブル
CREATE OR REPLACE TABLE TEMP.TUTORIAL.DIMENSION_PRODUCTS_WITH_PK (
PRODUCT_ID INT PRIMARY KEY RELY,
PRODUCT_NAME VARCHAR(200),
CATEGORY VARCHAR(100),
PRICE NUMBER(10, 2)
) CHANGE_TRACKING = TRUE;
-- Primary Keyなしディメンションテーブル(比較用)
CREATE OR REPLACE TABLE TEMP.TUTORIAL.DIMENSION_PRODUCTS_NO_PK (
PRODUCT_ID INT,
PRODUCT_NAME VARCHAR(200),
CATEGORY VARCHAR(100),
PRICE NUMBER(10, 2)
) CHANGE_TRACKING = TRUE;
-- ファクトテーブル
CREATE OR REPLACE TABLE TEMP.TUTORIAL.FACT_ORDERS (
ORDER_ID INT,
PRODUCT_ID INT,
QUANTITY INT,
ORDER_DATE TIMESTAMP_NTZ
) CHANGE_TRACKING = TRUE;
テストデータの投入
-- ディメンション(PK付き): 100,000件
INSERT INTO TEMP.TUTORIAL.DIMENSION_PRODUCTS_WITH_PK (PRODUCT_ID, PRODUCT_NAME, CATEGORY, PRICE)
SELECT
SEQ4() + 1 AS PRODUCT_ID,
'Product ' || LPAD(TO_VARCHAR(SEQ4() + 1), 6, '0') AS PRODUCT_NAME,
'Category ' || LPAD(TO_VARCHAR(MOD(SEQ4(), 10) + 1), 2, '0') AS CATEGORY,
ROUND(5.00 + MOD(SEQ4(), 500) * 0.50, 2) AS PRICE
FROM TABLE(GENERATOR(ROWCOUNT => 100000));
-- ディメンション(PKなし): 同一データをコピー
INSERT INTO TEMP.TUTORIAL.DIMENSION_PRODUCTS_NO_PK
SELECT * FROM TEMP.TUTORIAL.DIMENSION_PRODUCTS_WITH_PK;
-- ファクト: 10,000,000件
INSERT INTO TEMP.TUTORIAL.FACT_ORDERS (ORDER_ID, PRODUCT_ID, QUANTITY, ORDER_DATE)
SELECT
SEQ4() + 1 AS ORDER_ID,
MOD(SEQ4(), 100000) + 1 AS PRODUCT_ID,
MOD(SEQ4(), 10) + 1 AS QUANTITY,
DATEADD(SECOND, SEQ4(), '2025-01-01 00:00:00') AS ORDER_DATE
FROM TABLE(GENERATOR(ROWCOUNT => 10000000));
Dynamic Table の作成(PK あり vs なし)
-- PK付きディメンションを使用(インクリメンタルリフレッシュ可能)
CREATE OR REPLACE DYNAMIC TABLE TEMP.TUTORIAL.DT_ENRICHED_ORDERS_WITH_PK
TARGET_LAG = DOWNSTREAM
WAREHOUSE = COMPUTE_WH
REFRESH_MODE = INCREMENTAL
AS
SELECT
F.ORDER_ID,
F.PRODUCT_ID,
D.PRODUCT_NAME,
D.CATEGORY,
F.QUANTITY,
D.PRICE,
F.QUANTITY * D.PRICE AS ORDER_TOTAL,
F.ORDER_DATE
FROM TEMP.TUTORIAL.FACT_ORDERS F
INNER JOIN TEMP.TUTORIAL.DIMENSION_PRODUCTS_WITH_PK D
ON F.PRODUCT_ID = D.PRODUCT_ID;
-- PKなしディメンションを使用(比較用)
CREATE OR REPLACE DYNAMIC TABLE TEMP.TUTORIAL.DT_ENRICHED_ORDERS_NO_PK
TARGET_LAG = DOWNSTREAM
WAREHOUSE = COMPUTE_WH
REFRESH_MODE = INCREMENTAL
AS
SELECT
F.ORDER_ID,
F.PRODUCT_ID,
D.PRODUCT_NAME,
D.CATEGORY,
F.QUANTITY,
D.PRICE,
F.QUANTITY * D.PRICE AS ORDER_TOTAL,
F.ORDER_DATE
FROM TEMP.TUTORIAL.FACT_ORDERS F
INNER JOIN TEMP.TUTORIAL.DIMENSION_PRODUCTS_NO_PK D
ON F.PRODUCT_ID = D.PRODUCT_ID;
初回リフレッシュ(ベースライン確立)
ALTER DYNAMIC TABLE TEMP.TUTORIAL.DT_ENRICHED_ORDERS_WITH_PK REFRESH;
ALTER DYNAMIC TABLE TEMP.TUTORIAL.DT_ENRICHED_ORDERS_NO_PK REFRESH;
INSERT OVERWRITE によるディメンション書き換え(10%変更)
-- PK付きディメンションを書き換え(Category 01 の価格を10%値上げ)
INSERT OVERWRITE INTO TEMP.TUTORIAL.DIMENSION_PRODUCTS_WITH_PK
SELECT
PRODUCT_ID,
PRODUCT_NAME,
CATEGORY,
CASE
WHEN CATEGORY = 'Category 01' THEN ROUND(PRICE * 1.10, 2)
ELSE PRICE
END AS PRICE
FROM TEMP.TUTORIAL.DIMENSION_PRODUCTS_WITH_PK;
-- PKなしディメンションも同様に書き換え
INSERT OVERWRITE INTO TEMP.TUTORIAL.DIMENSION_PRODUCTS_NO_PK
SELECT
PRODUCT_ID,
PRODUCT_NAME,
CATEGORY,
CASE
WHEN CATEGORY = 'Category 01' THEN ROUND(PRICE * 1.10, 2)
ELSE PRICE
END AS PRICE
FROM TEMP.TUTORIAL.DIMENSION_PRODUCTS_NO_PK;
ポイント: 全行が書き換えられますが、実際に値が変わったのは Category 01 の約10,000件(10%)のみです。
リフレッシュ実行とパフォーマンス比較
-- PKなし(先に実行して比較)
ALTER DYNAMIC TABLE TEMP.TUTORIAL.DT_ENRICHED_ORDERS_NO_PK REFRESH;
-- PK付き
ALTER DYNAMIC TABLE TEMP.TUTORIAL.DT_ENRICHED_ORDERS_WITH_PK REFRESH;
リフレッシュ履歴の確認
-- リフレッシュ履歴の確認(パフォーマンス比較)
SELECT
NAME,
REFRESH_START_TIME,
REFRESH_END_TIME,
TIMESTAMPDIFF('SECOND', REFRESH_START_TIME, REFRESH_END_TIME) AS DURATION_SECONDS,
STATISTICS:numInsertedRows::INT AS ROWS_INSERTED,
STATISTICS:numDeletedRows::INT AS ROWS_DELETED,
FROM TABLE(INFORMATION_SCHEMA.DYNAMIC_TABLE_REFRESH_HISTORY(
NAME_PREFIX => 'TEMP.TUTORIAL.DT_ENRICHED_ORDERS'
))
ORDER BY REFRESH_START_TIME DESC
LIMIT 10;

GUIでも比較することは可能です。
確認手順:
- Snowsight → Transformation → Dynamic Tables に移動
TEMPデータベースでフィルタし、各DTの Refresh History タブを確認- REFRESH DURATION と ROWS INSERTED / ROWS DELETED を比較
期待される結果:
| Dynamic Table | リフレッシュ方式 | 処理行数 | リフレッシュ時間 |
|---|---|---|---|
| DT_ENRICHED_ORDERS_WITH_PK | インクリメンタル(変更行のみ) | 約200万行(約100万 delete + 約100万 insert) | 短い |
| DT_ENRICHED_ORDERS_NO_PK | フル再計算(全行) | 約2,000万行(約1000万 delete + 約1000万 insert) | 長い |
DT_ENRICHED_ORDERS_WITH_PK

DT_ENRICHED_ORDERS_NO_PK

クエリ導出 Unique Key の検証
GROUP BY や QUALIFY ROW_NUMBER() = 1 を使ったクエリでは、Snowflakeが自動的にユニークキーを導出します。
GROUP BY によるユニークキー導出
CREATE OR REPLACE DYNAMIC TABLE TEMP.TUTORIAL.DT_CATEGORY_SUMMARY
TARGET_LAG = DOWNSTREAM
WAREHOUSE = COMPUTE_WH
REFRESH_MODE = INCREMENTAL
AS
SELECT
D.CATEGORY,
COUNT(*) AS ORDER_COUNT,
SUM(F.QUANTITY * D.PRICE) AS TOTAL_REVENUE
FROM TEMP.TUTORIAL.FACT_ORDERS F
INNER JOIN TEMP.TUTORIAL.DIMENSION_PRODUCTS_WITH_PK D
ON F.PRODUCT_ID = D.PRODUCT_ID
GROUP BY D.CATEGORY;
-- ユニークキーの確認(CATEGORY が導出される)
SHOW UNIQUE KEYS IN TEMP.TUTORIAL.DT_CATEGORY_SUMMARY;
結果: GROUP BY の CATEGORY カラムがユニークキーとして導出されます。

QUALIFY ROW_NUMBER() = 1 によるユニークキー導出
CREATE OR REPLACE DYNAMIC TABLE TEMP.TUTORIAL.DT_LATEST_ORDER_PER_PRODUCT
TARGET_LAG = DOWNSTREAM
WAREHOUSE = COMPUTE_WH
REFRESH_MODE = INCREMENTAL
AS
SELECT
F.PRODUCT_ID,
F.ORDER_ID,
F.QUANTITY,
F.ORDER_DATE
FROM TEMP.TUTORIAL.FACT_ORDERS F
QUALIFY ROW_NUMBER() OVER (PARTITION BY F.PRODUCT_ID ORDER BY F.ORDER_DATE DESC) = 1;
-- ユニークキーの確認(PRODUCT_ID が導出される)
SHOW UNIQUE KEYS IN TEMP.TUTORIAL.DT_LATEST_ORDER_PER_PRODUCT;
結果: PARTITION BY の PRODUCT_ID カラムがユニークキーとして導出されます。

フルリフレッシュDT下流のインクリメンタルリフレッシュ
フルリフレッシュモードの上流DTに導出ユニークキーがある場合、下流DTで REFRESH_MODE = INCREMENTAL を明示指定できます。
-- 上流: フルリフレッシュ(GROUP BYにより導出ユニークキーあり)
CREATE OR REPLACE DYNAMIC TABLE TEMP.TUTORIAL.DT_UPSTREAM_FULL_REFRESH
TARGET_LAG = DOWNSTREAM
WAREHOUSE = COMPUTE_WH
REFRESH_MODE = FULL
AS
SELECT
D.CATEGORY,
COUNT(*) AS ORDER_COUNT,
SUM(F.QUANTITY * D.PRICE) AS TOTAL_REVENUE
FROM TEMP.TUTORIAL.FACT_ORDERS F
INNER JOIN TEMP.TUTORIAL.DIMENSION_PRODUCTS_WITH_PK D
ON F.PRODUCT_ID = D.PRODUCT_ID
GROUP BY D.CATEGORY;
-- 上流のユニークキーを確認
SHOW UNIQUE KEYS IN TEMP.TUTORIAL.DT_UPSTREAM_FULL_REFRESH;
-- 下流: インクリメンタルリフレッシュ(明示指定)
CREATE OR REPLACE DYNAMIC TABLE TEMP.TUTORIAL.DT_DOWNSTREAM_INCREMENTAL
TARGET_LAG = DOWNSTREAM
WAREHOUSE = COMPUTE_WH
REFRESH_MODE = INCREMENTAL
AS
SELECT
CATEGORY,
ORDER_COUNT,
TOTAL_REVENUE,
TOTAL_REVENUE / ORDER_COUNT AS AVG_ORDER_VALUE
FROM TEMP.TUTORIAL.DT_UPSTREAM_FULL_REFRESH
WHERE ORDER_COUNT > 100;
注意:
REFRESH_MODE = AUTOを指定した場合は FULL に解決されます。インクリメンタルリフレッシュを利用するにはINCREMENTALを明示指定する必要があります。
基本動作まとめ
| 検証項目 | 結果 |
|---|---|
| PK(RELY)付きテーブルからのインクリメンタルリフレッシュ | INSERT OVERWRITE後も変更行のみ処理(大幅な性能向上) |
| PKなしテーブルからのリフレッシュ | INSERT OVERWRITE後は全行再処理(従来動作) |
| GROUP BY による Unique Key 導出 | グルーピングカラムが自動的にユニークキーとして導出 |
| QUALIFY ROW_NUMBER() = 1 による Unique Key 導出 | パーティションカラムが自動的にユニークキーとして導出 |
| フルリフレッシュDT下流のインクリメンタル | 上流に導出ユニークキーがあれば REFRESH_MODE = INCREMENTAL 指定可能 |
| SHOW UNIQUE KEYS IN での確認 | 導出ユニークキーの有無を確認可能 |
クリーンアップ
DROP DATABASE IF EXISTS TEMP;
まとめ
Dynamic Table の Primary Key サポートにより、「INSERT OVERWRITEで全件書き換えされるベーステーブルでも、実際に変更された行のみをインクリメンタルに処理する」パイプラインが実現可能になりました。
特に以下のユースケースで大きな効果が期待できます。
- ETLコネクタやバッチロードでディメンションテーブルが定期的に全件書き換えされるパイプライン
- ファクトテーブルが大規模で、ディメンションの変更割合が小さいケース
- フルリフレッシュモードのDTが含まれるパイプラインで、下流のインクリメンタル化を実現したいケース
SHOW UNIQUE KEYS IN <dt_name> でDTに導出ユニークキーがあるかを確認し、必要に応じて上流テーブルに PRIMARY KEY RELY を設定することで、既存パイプラインのパフォーマンスを改善できます。
この記事が何かの参考になれば幸いです!










