Snowflake Iceberg テーブルデータを BigQuery Iceberg テーブルから参照してみた。
こんにちは、みかみです。
よく行く釣り場が夕日スポットとして認知されはじめているようで、夕暮れ時になると人々が集まってくるようになりました。釣りしているのが恥ずかしいやら申し訳ないやら。
ちなみにこないだの釣果はチョウチョウウオ1匹でした。別の釣り場を探そうと思います。
やりたいこと
- Snowflake 管理の Iceberg テーブルを、BigQuery Iceberg テーブルから参照したい。
- Snowflake 側で追加・更新・削除されたデータを、BigQuery Iceberg テーブルで参照するために必要な手順を確認したい。
前提
Google Cloud SDK(gcloud コマンド)の実行環境は準備済みであるものとします。 本エントリでは、Cloud Shell を使用しました。
また、BigQuery や GCS など各サービス操作に必要な API の有効化と権限は付与済みです。
Snowflake 側でも、必要なユーザーの作成や権限付与は実施済みです。
なお、文中、Google Cloud プロジェクト ID など一部の文字は伏字に変更しています。
本ブログの検証に使用した Snowflake の AWS と BigQuery のデータセットは、ともに東京リージョンです。
Snowflake の Iceberg テーブルを作成
GCS バケットをベース URL とした、Snowflake 管理の Iceberg テーブルを作成します。
GCS バケットはすでに作成済みです。
Snowflake で以下の SQL を実行して、EXTERNAL VOLUME(外部ボリューム)を作成します。
CREATE OR REPLACE EXTERNAL VOLUME MIKAMI_GCS_VOL
STORAGE_LOCATIONS = (
(
NAME = 'gcs-loc',
STORAGE_PROVIDER = 'GCS',
STORAGE_BASE_URL = 'gcs://test-mikami/iceberg/'
)
);
以下の SQL 出力結果のproperty_value 内 STORAGE_GCP_SERVICE_ACCOUNT のサービスアカウント名を取得します。
DESC EXTERNAL VOLUME MIKAMI_GCS_VOL;
以下の gcloud コマンドで、取得した外部ボリュームのサービスアカウントに GCS バケットへのアクセス権を付与します。
gcloud storage buckets add-iam-policy-binding gs://test-mikami \
--member="serviceAccount:mqtuzmgwut@awsapnortheast1-0987.iam.gserviceaccount.com" \
--role="roles/storage.objectAdmin"
続いて以下の SQL で、Snowflake の Iceberg テーブルを作成し、データを投入します。
-- テーブル作成
CREATE OR REPLACE ICEBERG TABLE MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ (
id INT,
name STRING,
created_at TIMESTAMP_NTZ,
updated_at TIMESTAMP_NTZ
)
CATALOG = 'SNOWFLAKE'
EXTERNAL_VOLUME = 'MIKAMI_GCS_VOL'
BASE_LOCATION = 'ICEBERG_TO_BQ/';
-- 初期データ3件投入
INSERT INTO MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ
SELECT 1, 'Alice', CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ,
CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ
UNION ALL
SELECT 2, 'Bob', CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ,
CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ
UNION ALL
SELECT 3, 'Charlie', CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ,
CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ;
データが正常に投入されたことを確認しておきます。
SELECT * FROM MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ ORDER BY id;

GCS 上には以下のファイルが出力されている状態です。
$ gcloud storage ls --recursive --long gs://test-mikami/iceberg/
0 2026-05-16T18:35:19Z gs://test-mikami/iceberg/
gs://test-mikami/iceberg/:
0 2026-05-16T18:35:19Z gs://test-mikami/iceberg/
gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/:
gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/:
1536 2026-05-16T19:34:50Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/snow_K9FFHEshBV0_AO6g2LsisBg_0_1_002.parquet
gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/:
924 2026-05-16T19:34:40Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00000-59617882-7f0a-4fc3-9306-bed1acf8a636.metadata.json
2006 2026-05-16T19:34:52Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00001-b24f3456-9989-4db4-9b68-daaa16dbda5d.metadata.json
7177 2026-05-16T19:34:51Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/4ab5f637-ef27-415c-9590-1945ea3fc588-m0.avro
4461 2026-05-16T19:34:51Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/snap-2619304162310719079-1-4ab5f637-ef27-415c-9590-1945ea3fc588.avro
TOTAL: 7 objects, 16104 bytes (15.73kiB)
BigQuery の Iceberg テーブルを作成
以下のコマンドで、BigQuery Connection を作成します。
bq mk --connection \
--location=asia-northeast1 \
--project_id=[PROJECT_ID] \
--connection_type=CLOUD_RESOURCE sf_iceberg_conn
作成した Connection のサービスアカウントを確認し、GCS バケットへのアクセス権を付与します。
# サービスアカウント確認
bq show --connection [PROJECT_ID].asia-northeast1.sf_iceberg_conn
# 権限付与
gcloud storage buckets add-iam-policy-binding gs://test-mikami \
--member="serviceAccount:bqcx-[PROJECT_NUMBER]-i94x@gcp-sa-bigquery-condel.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
Snowflake で以下の SQL を実行し、metadata ファイルパスを確認しておきます。
SELECT SYSTEM$GET_ICEBERG_TABLE_INFORMATION('MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ');
BigQuery で以下の SQL を実行し、Iceberg テーブルを作成します。
uris に、Snowflake 側で確認したメタデータファイルパスを指定しています。
なお GCS パスのプレフィクスは、Snowflake では gcs:// ですが、Google Cloud では gs:// となるのでご注意ください。
CREATE OR REPLACE EXTERNAL TABLE
dataset_1.ieberg_from_SF
WITH CONNECTION `[PROJECT_ID].asia-northeast1.sf_iceberg_conn`
OPTIONS (
format = 'ICEBERG',
uris = ['gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00001-b24f3456-9989-4db4-9b68-daaa16dbda5d.metadata.json']
);
データを確認してみます。
SELECT * FROM dataset_1.ieberg_from_SF ORDER BY id;
Snowflake の Iceberg テーブルと同じデータが、BigQuery の Iceberg テーブルから参照できることが確認できました。

Snowflake Iceberg テーブルデータを追加・更新・削除
Snowflake で以下の SQL を実行して、Iceberg テーブルにデータを追加・更新・削除します。
-- INSERT: id=4 (Dave) を追加
INSERT INTO MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ
SELECT 4, 'Dave',
CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ,
CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ;
-- UPDATE: id=1 のnameを変更し、updated_atを更新
UPDATE MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ
SET name = 'Alice Updated',
updated_at = CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ
WHERE id = 1;
-- DELETE: id=2 (Bob) を削除
DELETE FROM MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ WHERE id = 2;
テーブルデータを確認します。
SELECT * FROM MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ ORDER BY id;

id = 4 が追加され、id = 1 が更新、id = 2 が削除されていることが確認できました。
GCS 上のファイルは以下の状態です。
$ gcloud storage ls --recursive --long gs://test-mikami/iceberg/
0 2026-05-16T18:35:19Z gs://test-mikami/iceberg/
gs://test-mikami/iceberg/:
0 2026-05-16T18:35:19Z gs://test-mikami/iceberg/
gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/:
gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/:
1536 2026-05-16T19:39:33Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/snow_K9FFHEshBV0_AFFy_wAjsBg_0_1_002.parquet
1536 2026-05-16T19:34:50Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/snow_K9FFHEshBV0_AO6g2LsisBg_0_1_002.parquet
1024 2026-05-16T19:39:17Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/snow_K9FFHEshBV0_xLYbB_MisBg_0_1_002.parquet
3072 2026-05-16T19:39:23Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/snow_K9FFHEshBV0_xrYbB_MisBg_0_1_002.parquet
gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/:
924 2026-05-16T19:34:40Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00000-59617882-7f0a-4fc3-9306-bed1acf8a636.metadata.json
2006 2026-05-16T19:34:52Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00001-b24f3456-9989-4db4-9b68-daaa16dbda5d.metadata.json
3034 2026-05-16T19:39:19Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00002-d3394ad2-3b97-471b-88ae-0b5bdea6bff0.metadata.json
4146 2026-05-16T19:39:25Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00003-2a69fc31-572e-451e-a595-f566f0ed89af.metadata.json
5257 2026-05-16T19:39:35Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00004-424edbf5-f044-48df-9bed-4e78490ee305.metadata.json
7163 2026-05-16T19:39:19Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/01678a50-1b75-4cda-9037-61a5c08eda70-m0.avro
7178 2026-05-16T19:39:25Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/0c95b293-0b89-4229-b7a6-fcf8ebf351f4-m0.avro
7193 2026-05-16T19:39:25Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/0c95b293-0b89-4229-b7a6-fcf8ebf351f4-m1.avro
7195 2026-05-16T19:39:34Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/1bdb0645-5c96-4719-be57-afcde9addef8-m0.avro
7193 2026-05-16T19:39:35Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/1bdb0645-5c96-4719-be57-afcde9addef8-m1.avro
7177 2026-05-16T19:34:51Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/4ab5f637-ef27-415c-9590-1945ea3fc588-m0.avro
4461 2026-05-16T19:34:51Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/snap-2619304162310719079-1-4ab5f637-ef27-415c-9590-1945ea3fc588.avro
4553 2026-05-16T19:39:25Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/snap-5295394461694027022-1-0c95b293-0b89-4229-b7a6-fcf8ebf351f4.avro
4532 2026-05-16T19:39:19Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/snap-830033498443085298-1-01678a50-1b75-4cda-9037-61a5c08eda70.avro
4550 2026-05-16T19:39:35Z gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/snap-8843058784580407502-1-1bdb0645-5c96-4719-be57-afcde9addef8.avro
TOTAL: 21 objects, 83730 bytes (81.77kiB)
BQ側で確認
そのまま BigQuery Iceberg テーブルデータを確認しても、先程の追加・更新・削除データは確認できませんでした。
Snowflake で現在のメタデータファイルを確認し、BigQuery Iceberg テーブルの metadata ファイルパスを更新します。
SELECT SYSTEM$GET_ICEBERG_TABLE_INFORMATION('MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ');
# テーブル定義ファイルを作成
cat > iceberg_def.json <<'EOF'
{
"sourceFormat": "ICEBERG",
"sourceUris": [
"gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00004-424edbf5-f044-48df-9bed-4e78490ee305.metadata.json"
],
"connectionId": "[PROJECT_ID].asia-northeast1.sf_iceberg_conn"
}
EOF
# 既存テーブルの定義を更新
bq update \
--external_table_definition=iceberg_def.json \
[PROJECT_ID]:dataset_1.ieberg_from_SF
BigQuery Iceberg テーブルデータを確認してみます。

最新のデータが反映されるようになりました。
また、タイムトラベルで追加・更新・削除前のテーブルデータを参照できることも確認できました。
SELECT *
FROM dataset_1.ieberg_from_SF
FOR SYSTEM_TIME AS OF TIMESTAMP '2026-05-17 04:35:00+09:00'
ORDER BY id;

なお、現在のところ、SQL でメタデータファイルパスの更新はできないようです。
以下の ALTER TABLE 文で更新しようとしたところ、エラーが発生してしまいました。
ALTER TABLE dataset_1.ieberg_from_SF
SET OPTIONS (
uris = ['gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00004-424edbf5-f044-48df-9bed-4e78490ee305.metadata.json']
);

SQL レイヤで処理する必要がある場合はテーブルを作り直す必要がありますが、その場合タイムトラベルは使えなくなってしまうので、ご留意ください。
BigQuery 外部テーブル経由で Snowflake テーブルデータを参照
Iceberg テーブル経由ではなく、BigQuery で外部テーブルを作成して、GCS 上のファイルを参照することも可能です。
Snowflake で以下の SQL を実行して、テーブルデータを CSV ファイルで GCS にエクスポートします。
なお、ファイル出力先 GCS のストレージ統合と外部ステージはすでに作成済みです。新規作成が必要な場合は以下をご参照ください。
COPY INTO @MIKAMI_DB.PUBLIC.MIKAMI_GCS_TEST_STAGE/iceberg_to_bq/
FROM MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ
FILE_FORMAT = (
TYPE = CSV
FIELD_DELIMITER = ','
FIELD_OPTIONALLY_ENCLOSED_BY = '"'
NULL_IF = ('NULL', 'null', '')
EMPTY_FIELD_AS_NULL = TRUE
COMPRESSION = NONE
)
HEADER = TRUE
OVERWRITE = TRUE;
GCS に CSV ファイルがエクスポートできました。

$ gcloud storage ls --recursive --long gs://test-mikami/ext/
gs://test-mikami/ext/:
gs://test-mikami/ext/iceberg_to_bq/:
233 2026-05-17T17:11:44Z gs://test-mikami/ext/iceberg_to_bq/data_0_0_0.csv
TOTAL: 1 objects, 233 bytes (233.00B)
BigQuery で以下の SQL を実行して、外部テーブルを作成してデータを確認してみます。
CREATE OR REPLACE EXTERNAL TABLE
dataset_1.ext_from_SF_csv
OPTIONS (
format = 'CSV',
uris = ['gs://test-mikami/ext/iceberg_to_bq/*'],
skip_leading_rows = 1,
field_delimiter = ',',
quote = '"'
);
SELECT * FROM dataset_1.ext_from_SF_csv ORDER BY id;

GCS 上の CSV ファイルのデータが参照できました。
続いて Snowflake で以下の SQL を実行して、データファイルを追加します。
COPY INTO @MIKAMI_DB.PUBLIC.MIKAMI_GCS_TEST_STAGE/iceberg_to_bq/
FROM (
SELECT
5 AS id,
'Eve' AS name,
CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ AS created_at,
CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ AS updated_at
)
FILE_FORMAT = (
TYPE = CSV
FIELD_DELIMITER = ','
FIELD_OPTIONALLY_ENCLOSED_BY = '"'
NULL_IF = ('NULL', 'null', '')
EMPTY_FIELD_AS_NULL = TRUE
COMPRESSION = NONE
)
HEADER = TRUE
INCLUDE_QUERY_ID = TRUE;
$ gcloud storage ls --recursive --long gs://test-mikami/ext/
gs://test-mikami/ext/:
gs://test-mikami/ext/iceberg_to_bq/:
98 2026-05-17T17:23:51Z gs://test-mikami/ext/iceberg_to_bq/data_01c46ed3-0003-d34c-0000-00b40a5efcf6_0_0_0.csv
233 2026-05-17T17:11:44Z gs://test-mikami/ext/iceberg_to_bq/data_0_0_0.csv
TOTAL: 2 objects, 331 bytes (331.00B)
再度 BigQuery 外部テーブルデータを確認すると

追加データも正常に参照できることが確認できました。
外部テーブル経由であれば、Iceberg テーブル経由で参照する場合と比べて比較的容易に実装することが可能です。
Iceberg テーブルの場合はデータ追加時に metadata ファイルパスを更新する必要がありますが、外部テーブルであればそのまま参照可能です。
データファイルをダウンロードして開けばすぐ内容を確認できる CSV や JSON 形式もサポートされているので、運用面でのメリットもあるのではないかと思います。
しかし、GCS 上の指定パス配下の全ファイルが参照されるため、データの更新・削除時には、ファイル出力(Snowflake)側または参照(BigQuery)側で制御する必要があり、タイムトラベルも利用できません。
また、クエリのパフォーマンスを考慮すると、高頻度や大量データ連携時の利用は慎重に検討する必要があるかと思います。
まとめ(所感)
Snowflake の Iceberg テーブルデータを、BigQuery の Iceberg テーブルで参照できました。
なお、BigQuery の Iceberg テーブルを Snowflake から参照することも可能です。
オープンテーブルフォーマットとして互換性が高い印象のある Iceberg テーブルですが、現在のところ BigQuery・Snowflake それぞれが独自のメタデータ管理方式を採用しており、完全な互換性があるわけではありません。
metadata ファイルパスの更新が必要だったり、データの書き込みができなかったりと、利用時にはいくつか制約があります。
とはいえ、データの整合性を考慮した複雑なバッチ処理を実装することなく、更新・削除含むデータの連携が可能な Iceberg テーブルは、異なるプラットフォーム間のデータ連携の構成をシンプルに構築できる嬉しい選択肢だと思いました。









