【新機能】Dynamic TableのPrimary Keyサポートが一般提供となったのでチュートリアルを試してみた

【新機能】Dynamic TableのPrimary Keyサポートが一般提供となったのでチュートリアルを試してみた

2026.04.21

かわばたです。

2026年4月16日にDynamic TableのPrimary Keyサポートが一般提供(GA)となりました。

従来、INSERT OVERWRITEで定期的に書き換えられるベーステーブルでは、Snowflakeが変更を検知できずフルリフレッシュが必要でした。Primary Key(RELYプロパティ付き)を設定することで、行レベルの変更追跡 が可能となり、下流のDynamic Tableでインクリメンタルリフレッシュが実現できるようになりました。

本記事では、この新機能を SQLによる基本動作検証 および パフォーマンス比較 の観点から検証します。

https://docs.snowflake.com/en/user-guide/dynamic-tables-primary-keys

概要

背景

従来の 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;

2026-04-21_10h24_05

GUIでも比較することは可能です。

確認手順:

  1. Snowsight → Transformation → Dynamic Tables に移動
  2. TEMP データベースでフィルタし、各DTの Refresh History タブを確認
  3. 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
2026-04-21_10h04_53

DT_ENRICHED_ORDERS_NO_PK

2026-04-21_10h05_30

クエリ導出 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 カラムがユニークキーとして導出されます。

2026-04-21_10h08_58

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 カラムがユニークキーとして導出されます。

2026-04-21_10h09_41

フルリフレッシュ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 を設定することで、既存パイプラインのパフォーマンスを改善できます。

この記事が何かの参考になれば幸いです!

この記事をシェアする

関連記事