データ基盤
Snowflake VARIANTとは?JSONを扱う半構造化データの仕組み
シリーズSnowflake 入門全13回
- 第1回Snowflakeとは?仕組み・料金・使い方をわかりやすく解説
- 第2回Snowflakeのウェアハウスとは?サイズとクレジット・自動停止
- 第3回Snowflakeの料金の仕組みとは?計算・保存・転送とコスト管理
- 第4回Snowflake マイクロパーティションとは?プルーニングとクラスタリング
- 第5回Snowflake VARIANTとは?JSONを扱う半構造化データの仕組み
- 第6回Snowflake Time Travel(タイムトラベル)とは?保持期間と復元
- 第7回Snowflake レプリケーションとは?別リージョンに複製して切り替える
- 第8回Snowflake ロールとは?権限をロールに与えて階層で渡す仕組み
- 第9回Snowflake Dynamic Tablesとは?SELECTだけで結果の表を最新に保つ
- 第10回Snowflake データ共有とは?コピーせずに取引先と同じ表を使う
- 第11回Snowflake Icebergテーブルとは?同じデータを他のエンジンでも読む
- 第12回Snowflake Cortexとは?データを外に出さずにSQLでAIを使う
- 第13回Snowflake Well-Architectedとは?5つの柱で自分の判断を見直す
- VARIANT
- JSON
- 半構造化データ
- Snowflake
- FLATTEN
- PARSE_JSON
- ARRAY
- OBJECT
- サブカラム化
- プルーニング
- キャスト
- COPY INTO
- STRIP_OUTER_ARRAY
- INFER_SCHEMA
- スキーマ進化
- 構造化データ型
- JSON null
- ELT
- データ基盤
目次
半構造化データは、項目の中に項目が入れ子になっていて、項目の並びが前もって決まっていないデータである。前もって決めなくてよいので、新しい項目をいつでも足せる1。アプリのログや API(Application Programming Interface)の応答に使われる JSON(JavaScript Object Notation)が、その代表である。
ふつうのデータベースの表は、どの列にどの型の値を入れるかという決まり(スキーマ)を先に作る。JSON をそのまま表にするには、入れ子をほどいて列に分ける作業が要る。
Snowflake では、JSON をそのまま入れられる型を VARIANT という。VARIANT には、ほかのどの型の値も入る2。
Snowflake の VARIANT は、JSON をどんな形で持ち、利用者はその値をどう読み出すのか。
VARIANT は、JSON の値をその型とともに一つの列に持つ型である。Snowflake はその中の項目をできるだけ自動で列の形に分けて持ち、利用者は読むときに 列:項目 の形で値をたどって型を決める。
形がそろい JSON 本来の型だけを使うデータなら、問い合わせは VARIANT の列を表とほぼ同じ速さで読める。利用者は、扱い方がまだ決まっていない JSON を VARIANT の列にそのまま置いてよいが、日付や数値を文字列で持つ項目、配列の項目、null が混じる項目は別の列に展開するのがよい。
車の販売の記録を、JSON のまま問い合わせる
JSON は、列に分けなくても VARIANT のまま問い合わせられる。ただし、文字列で入った数値は、数値として扱うために型を決めて(キャストして)から計算する。車の販売の記録を JSON のまま入れた表 car_sales を例に考える3。1 件の販売は次のような JSON で、売った日、販売店、担当者、顧客、車を持つ。値段や年式は、JSON の中で文字列として書かれている3。
CREATE OR REPLACE TABLE car_sales
(
src variant
)
AS
SELECT PARSE_JSON(column1) AS src
FROM VALUES
('{
"date" : "2017-04-28",
"dealership" : "Valley View Auto Sales",
"salesperson" : {
"id": "55",
"name": "Frank Beasley"
},
"customer" : [
{"name": "Joyce Ridgely", "phone": "16504378889", "address": "San Francisco, CA"}
],
"vehicle" : [
{"make": "Honda", "model": "Civic", "year": "2017", "price": "20275", "extras":["ext warranty", "paint protection"]}
]
}'),
('{ ... }') v;
-- 2 件目は省略:Tindel Toyota、顧客 Bradley Greenbloom(New York, NY)、
-- Toyota Camry、"price": "23500"、extras は 3 つ。省略したままでは実行できない
PARSE_JSON は、文字列を JSON として読み、VARIANT の値にする4。表の列は src の一つだけで、販売の 2 件はそれぞれ 1 行に入る。
項目を取り出す: 列の名前と項目の名前をコロン(:)でつなぐ。配列の中の何番目かは [0] のように 0 から数えて指定し、その下の項目は点(.)でつなぐ3。
SELECT src:customer[0].name, src:vehicle[0].price
FROM car_sales
ORDER BY 1;
+----------------------+----------------------+
| SRC:CUSTOMER[0].NAME | SRC:VEHICLE[0].PRICE |
|----------------------+----------------------|
| "Bradley Greenbloom" | "23500" |
| "Joyce Ridgely" | "20275" |
+----------------------+----------------------+
取り出した値は VARIANT のままなので、文字列は引用符つきで返る。数値として計算するには、:: で型を決める(キャストする)32。
SELECT src:vehicle[0].price::NUMBER * 0.10 AS tax
FROM car_sales
ORDER BY tax;
+--------+
| TAX |
|--------|
| 2027.5 |
| 2350.0 |
+--------+
配列を行に展開する: 顧客は配列に入っている。FLATTEN は配列の要素ごとに 1 行を返し、LATERAL でもとの行とつなぐ3。
SELECT
value:name::string as "Customer Name",
value:address::string as "Address"
FROM
car_sales
, LATERAL FLATTEN(INPUT => SRC:customer);
+--------------------+-------------------+
| Customer Name | Address |
|--------------------+-------------------|
| Joyce Ridgely | San Francisco, CA |
| Bradley Greenbloom | New York, NY |
+--------------------+-------------------+
FLATTEN を二重に使うと、車の配列の中のオプション(extras)の配列まで展開できる。販売 2 件から、オプションごとに 5 行が返る3。
この表では、値段が "20275" のような文字列として入っている。値段で絞り込むたびに、文字列を数値にキャストすることになる。値段を数値の列に展開しておけば、プルーニングが効きやすくなり、保存の量も減る5。顧客や車のように配列で入った項目も、Snowflake が別の列への展開を勧める値にあたる5。
VARIANT・ARRAY・OBJECT:値と型をいっしょに持つ
半構造化データを扱う型は三つある2。
- VARIANT: どの型の値も入る。値と、その値の型をいっしょに持つ。
- ARRAY: 値の並び。一つひとつの値は VARIANT である(値の型を決めた ARRAY は最後の節で扱う)。
- OBJECT: 名前と値の組の集まり。名前は文字列(VARCHAR)、値は VARIANT である。
一つの VARIANT の値は、圧縮する前で最大 128 MB まで持てるが、内部で管理に使う分があるため、実際にはそれより小さくなる2。JSON には日付や時刻の型がないので、日付や時刻は文字列として入る24。
Snowflake は、JSON のほかに Avro、ORC、Parquet、XML の形式の半構造化データも取り込める1。
取り込む:PARSE_JSON と COPY INTO
文字列から VARIANT を作るのが PARSE_JSON である。入力は圧縮した大きさで 64 MB まで受け付ける4。ファイルから読み込むときは、ステージ(読み込むファイルを置く場所)のファイルを表に読み込む命令 COPY INTO で、ファイル形式の TYPE を JSON にする6。読み込みの形式には、いちばん外側の角かっこ([ ])を外す STRIP_OUTER_ARRAY や、値が null の項目を落とす STRIP_NULL_VALUES を指定できる7。
Snowflake の創業者らが 2016 年の国際会議 SIGMOD で発表した論文は、VARIANT があるので、読み込むときに JSON の形を決めたり変換したりしなくてよく、先に読み込んでから変換する ELT(Extract-Load-Transform、抽出・読み込み・変換)の使い方ができると書いた8。Snowflake は、JSON を扱う方法として三つを挙げている9。
- VARIANT の列にそのまま置く。
- いったん VARIANT のまま中間の表に置き、FLATTEN で項目を別の列に取り出した表を作る。
- 読み込むときに、JSON の項目をそのまま表の列に変換する。
取り出す:パスとキャスト、FLATTEN
パスで項目をたどるとき、列の名前は大文字と小文字を区別しないが、JSON の項目の名前は区別する3。
FLATTEN は、まとまった値を複数の行に展開する関数で、次の列を返す10。
| FLATTEN が | 中身 |
|---|---|
| VALUE | 展開した |
| INDEX | 配列なら |
| KEY | OBJECT なら、 |
| PATH | データの |
| SEQ | もとの |
| THIS | 展開している |
空の配列の行も残したいときは OUTER を TRUE にし、入れ子の下まですべて展開するときは RECURSIVE を TRUE にする10。FLATTEN を続けて使うときは、LATERAL を付けると、後の FLATTEN が前の FLATTEN の結果を使える10。
中での持ち方:よく出る項目を列に分ける
VARIANT の列に入れたデータは、文字の塊のまま置かれるのではない。Snowflake は、できるだけ多くの項目を列の形に取り出して持ち、残りを解析済みの一つの列に入れる5。Snowflake の日本語ドキュメントでは、これをサブカラム化という11。
2016 年の論文は、この仕組みを説明している。Snowflake は、表を分けて持つファイル(今のマイクロパーティションにあたる)ごとに JSON を統計的に調べて型を推定し、よく出る項目を見つける。その項目を JSON から取り出し、ふつうの表と同じ圧縮した列の形で別に持つ。その列には、プルーニング(読まなくてよい部分を飛ばすこと)に使う集計の値も作る8。論文によれば、日付が文字列で入っている問題には、日付への変換をあらかじめ試し、元に戻せる変換でない限り、変換の結果ともとの文字列を別の列に両方持つことで対処する8。これは 2016 年時点の説明で、論文ではこの変換を Snowflake 自身が行う8。一方、今の Snowflake のドキュメントは、文字列で入った日付を利用者が別の列に展開するよう勧めている5。
論文は、形が比較的安定して単純なデータなら、半構造化データへの問い合わせの速さは、ふつうの表とほぼ同じだと書いた。測定では、TPC(Transaction Processing Performance Council)という団体が管理する業界の基準 TPC-H にならったデータを JSON にして読み込んだので、日付は文字列になった。項目や型の手がかりはシステムに何も与えていない。ふつうの表に入れた場合と、スキーマを決めずに入れた場合で同じ問い合わせを比べると、増えた時間は、2 つの問い合わせを除いて約 10% だったとしている。例外の 2 つ(1 TB のデータでの Q9 と Q17)について、論文は、異なる値の数を見積もる処理の既知のバグで結合の順序がまずくなったためだと書いている。ただし、この測定は 2016 年のもので、TPC の確認は受けていない8。
この自動の取り出しには、今の Snowflake で次の制限がある5。
- 取り出すのは、一つの表のマイクロパーティション(Snowflake が表を分けて持つ保存の単位)ごとに、既定で 200 項目まで。
- 値が JSON の null の要素を一つでも含む項目は、列に取り出さない(項目がないだけなら取り出される)。JSON の null は、値がないことを表す SQL(Structured Query Language)の NULL とは別の、「null」という値である。
- 値の型が混じる項目も、取り出さない。
- 列に取り出されなかった項目は、問い合わせのたびに JSON の全体を読み、行ごとにたどって値を出すので、遅くなる。
列に展開するべきとき
Snowflake は、半構造化データでどんな操作をするかまだ決まっていないなら、ひとまず VARIANT の列に置くよう勧めている5。形がほぼそろっていて、文字列や整数のような JSON がもともと持つ型だけを使うデータなら、表と VARIANT の列とで、保存の量も問い合わせの速さもほとんど変わらない5。
一方で Snowflake は、次のような値を含むなら、項目を別の列に展開するよう勧めている。プルーニングが効きやすくなり、保存の量も減るからである5。
- 文字列で書かれた日付や時刻(とくに ISO 8601 の形でないもの)
- 文字列の中の数値
- 配列
使い方が分かっているなら、両方の形の表を作り、ふだんの問い合わせを実行して比べるよう、Snowflake は勧めている5。JSON の null を含む項目は、読み込む前に別の列に取り出すよう勧めている。null が値のないことを表すだけなら、読み込みの形式で STRIP_NULL_VALUES を TRUE にして落としてもよい5。
null には二種類ある。SQL の NULL は値がないことを表す。JSON null は、VARIANT の列の中で「null」という値として持たれ、SQL の NULL と区別される。文字列にキャストすると、JSON null は SQL の NULL になる5。
展開せずに VARIANT の中の項目での絞り込みを速くする手として、Snowflake には検索最適化もある。VARIANT・OBJECT・ARRAY の中の要素に対する等値や IN、NULL を調べる条件に効き、Enterprise Edition 以上で使える12。
形を推定する、型を決める
読み込む前のファイルの形は、INFER_SCHEMA で推定できる。ステージ(読み込むファイルを置く場所)にある JSON や Parquet などのファイルから、列の定義を取り出す13。ただし、JSON と CSV ではすべての列が NULL を許す列として推定され、入れ子は 1 段目までしか扱えない13。推定した定義から、CREATE TABLE … USING TEMPLATE で表を作れる13。
データに新しい項目が増えたときに、表の列を自動で足す機能もある。これをスキーマ進化という。表の ENABLE_SCHEMA_EVOLUTION を TRUE にしておくと、列の名前で合わせる COPY INTO や Snowpipe での読み込みで列が足される。一回の COPY で足せるのは既定で 100 列までで、INSERT では表の形は自動では変わらない14。
項目の型を先に決めたい場合は、構造化データ型を使う。ARRAY、OBJECT、MAP の中の値に、決まった型を指定できる15。ふつうの表で構造化データ型が一般提供になったのは、2025 年 7 月のリリース(9.19)からである16。
出典16件
-
Snowflake「Introduction to loading semi-structured data | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/user-guide/semistructured-intro — 半構造化データは形を先に決めなくてよい。 ↩ ↩2
-
Snowflake「Semi-structured data types | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/sql-reference/data-types-semistructured ↩ ↩2 ↩3 ↩4 ↩5
-
Snowflake「Querying Semi-structured Data | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/user-guide/querying-semistructured — 表 car_sales と問い合わせの結果。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Snowflake「PARSE_JSON | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/sql-reference/functions/parse_json ↩ ↩2 ↩3
-
Snowflake「Considerations for semi-structured data stored in VARIANT | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/user-guide/semistructured-considerations — サブカラム化の限りと、列に展開するべきとき。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11
-
Snowflake「COPY INTO <table> | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/sql-reference/sql/copy-into-table ↩
-
Snowflake「CREATE FILE FORMAT | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/sql-reference/sql/create-file-format ↩
-
Benoit Dageville ほか「The Snowflake Elastic Data Warehouse」SIGMOD 2016、2016年6月、2026年10月27日取得. https://info.snowflake.net/rs/252-RFO-227/images/Snowflake_SIGMOD.pdf — 4.3 節、半構造化データの持ち方と測定。 ↩ ↩2 ↩3 ↩4 ↩5
-
Snowflake「Tutorial: Loading JSON data into a relational table | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/user-guide/tutorials/script-data-load-transform-json — JSON を扱う三つの方法。 ↩
-
Snowflake「FLATTEN | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/sql-reference/functions/flatten ↩ ↩2 ↩3
-
Snowflake「VARIANT に格納されている半構造化データに関する考慮事項 | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/ja/user-guide/semistructured-considerations — 「サブカラム化」の語。 ↩
-
Snowflake「Search optimization service | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/user-guide/search-optimization-service — VARIANT の中の要素への条件にも効く。 ↩
-
Snowflake「INFER_SCHEMA | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/sql-reference/functions/infer_schema — 入れ子は 1 段目まで。 ↩ ↩2 ↩3
-
Snowflake「Enable automatic table schema evolution | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/user-guide/data-load-schema-evolution ↩
-
Snowflake「Structured data types | Snowflake Documentation」日付なし、2026年10月27日取得. https://docs.snowflake.com/en/sql-reference/data-types-structured ↩
-
Snowflake「9.19 Release notes: Jul 14, 2025-Jul 17, 2025 | Snowflake Documentation」2025年7月、2026年10月27日取得. https://docs.snowflake.com/en/release-notes/2025/9_19 ↩
この記事はAIを使用して執筆されました。内容には誤りが含まれる可能性があります。事実や数字の出典は脚注から確かめられます。