[新機能]Snowflakeでテーブルに値を格納するのではなくクエリ実行時に式から値を計算する列を定義できるVirtual Columnsを試してみた
さがらです。
Snowflakeの新機能として、Virtual Columns(仮想列) がGAになっています。テーブルに値を格納するのではなく、クエリ実行時に式から値を計算する列を定義できる機能です。これまでViewで実現していた派生列をテーブル本体に定義できるようになり、テーブルとViewのペアをテーブル1つで代替できます。
活用方法を複数のパターンに分けて試してみましたので、手順と確認結果をまとめます。
機能概要
Virtual Columnsは、テーブル定義時に AS (<expr>) 句を指定することで定義できる仮想列です。値はテーブルに保存されず、クエリ実行時にSnowflakeが式から計算します。
CREATE TABLEまたはALTER TABLEの構文で定義します。
-- CREATE TABLE時に定義
CREATE OR REPLACE TABLE <table> (
<col_name> <col_type> AS ( <expr> )
);
-- ALTER TABLEで後から追加
ALTER TABLE <table> ADD COLUMN <col_name> <col_type> AS ( <expr> );
ViewのSELECT句で実現していた派生列と似た目的を果たしますが、テーブル自体に定義されるため、別途Viewを作成・管理する必要がありません。
制限事項
2026年6月6日時点での制限事項・注意点をまとめます。
仮想列の式で使えないもの:
- 非決定的関数:
RANDOM()、CURRENT_TIMESTAMP、CURRENT_DATE、UUID_STRINGなど、実行のたびに異なる値を返す関数 - 集計関数:
SUM、AVG、COUNTなど。仮想列は行単位で評価されるため集計不可 - ウィンドウ関数:
OVER句を使う関数 - サブクエリ: ネストした
SELECT文 - ユーザー定義関数(UDF): SQL、JavaScript、外部UDFを問わず不可
- バインド変数:
?、:1、:valueなどのバインドパラメータ - セッション変数:
SETで設定した変数 - 位置指定カラム参照:
$1、$2などの参照(外部テーブルの仮想列では$1擬似列は許可) - DEFAULT値を持つ列の参照: DEFAULT値が設定された列は仮想列の式から参照できない。また、仮想列自体にDEFAULT値は設定できない
その他の制限:
NOT NULLおよびCHECK制約は設定できない- 仮想列はテーブルのクラスタリングキーには設定できない
- 仮想列が参照している列(ベース列・仮想列どちらも)は
DROP COLUMNできない - 仮想列から他の仮想列を参照する場合は、参照される仮想列が先に定義されている必要がある(前方参照は不可)
- データ型の互換性ルールがあり、宣言した型が式の推論型と互換性がある必要がある(数値のスケール、文字列の長さ、タイムスタンプの精度など)
既知の問題(Known Issue):
COALESCEなど NULL を非 NULL に変換する式を仮想列に定義し、外部結合(LEFT JOIN)で使用すると、結合が一致しない保存側の行でも非 NULL 値が返る場合がある。ワークアラウンドは「仮想列の式にCOALESCEを入れず、SELECT句側で適用する」です
事前準備
データベース・スキーマ・ウェアハウスの作成
今回の検証用に、データベース・スキーマ・ウェアハウスを作成します。既存のものを使う場合は適宜置き換えてください。
USE ROLE SYSADMIN;
-- ウェアハウス作成
CREATE WAREHOUSE IF NOT EXISTS VIRTUAL_COL_WH
WAREHOUSE_SIZE = 'XSMALL'
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE;
-- データベース・スキーマ作成
CREATE DATABASE IF NOT EXISTS VIRTUAL_COL_DB;
CREATE SCHEMA IF NOT EXISTS VIRTUAL_COL_DB.PUBLIC;
-- 作業コンテキストを設定
USE WAREHOUSE VIRTUAL_COL_WH;
USE DATABASE VIRTUAL_COL_DB;
USE SCHEMA PUBLIC;
サンプルテーブルの作成・データ投入
ECサイトの注文データを想定したサンプルテーブル orders_raw を作成し、検証データを投入します。
CREATE OR REPLACE TABLE orders_raw (
order_id INT,
customer_name STRING,
email STRING,
order_date DATE,
product_name STRING,
quantity INT,
unit_price NUMBER(10, 2)
);
検証用データのINSERT文(クリックで展開)
INSERT INTO orders_raw
(order_id, customer_name, email, order_date, product_name, quantity, unit_price)
VALUES
(1, '山田太郎', 'taro.yamada@example.com', '2026-01-05', 'ワイヤレスマウス', 2, 2500.00),
(2, '鈴木花子', 'hanako.suzuki@dev.co.jp', '2026-01-07', 'メカニカルキーボード', 1, 12800.00),
(3, '佐藤次郎', 'jiro.sato@mail.example.org', '2026-01-10', 'USBハブ', 3, 1980.00),
(4, '田中美咲', 'misaki.tanaka@example.com', '2026-01-12', 'ノートPC用スタンド', 1, 4500.00),
(5, '中村健一', 'kenichi.nakamura@company.jp', '2026-01-15', 'HDMI変換アダプタ', 5, 890.00),
(6, '渡辺優子', 'yuko.watanabe@example.com', '2026-01-18', 'ウェブカメラ', 1, 8900.00),
(7, '伊藤誠', 'makoto.ito@dev.co.jp', '2026-01-20', 'ワイヤレスマウス', 1, 2500.00),
(8, '加藤由美', 'yumi.kato@mail.example.org', '2026-01-22', 'SSDポータブル', 2, 15800.00),
(9, '吉田孝', 'takashi.yoshida@example.com', '2026-01-25', 'キーボード用リストレスト', 1, 2200.00),
(10, '山本裕美子', 'yumiko.yamamoto@company.jp', '2026-01-28', 'LEDデスクライト', 1, 5500.00),
(11, '松本大輔', 'daisuke.matsumoto@example.com', '2026-02-02', 'メカニカルキーボード', 2, 12800.00),
(12, '井上さくら', 'sakura.inoue@dev.co.jp', '2026-02-05', 'ノートPC用スタンド', 2, 4500.00),
(13, '木村朝子', 'asako.kimura@mail.example.org', '2026-02-08', 'USBハブ', 1, 1980.00),
(14, '林拓也', 'takuya.hayashi@example.com', '2026-02-10', 'HDMI変換アダプタ', 3, 890.00),
(15, '斎藤倫子', 'michiko.saito@company.jp', '2026-02-12', 'SSDポータブル', 1, 15800.00),
(16, '清水宏', 'hiroshi.shimizu@example.com', '2026-02-15', 'ウェブカメラ', 2, 8900.00),
(17, '山口美奈', 'mina.yamaguchi@dev.co.jp', '2026-02-18', 'LEDデスクライト', 3, 5500.00),
(18, '池田勝', 'masaru.ikeda@mail.example.org', '2026-02-20', 'ワイヤレスマウス', 4, 2500.00),
(19, '橋本あかり', 'akari.hashimoto@example.com', '2026-02-22', 'キーボード用リストレスト', 2, 2200.00),
(20, '石川裕太', 'yuta.ishikawa@company.jp', '2026-02-25', 'メカニカルキーボード', 1, 12800.00),
(21, '前田咲良', 'sakura.maeda@example.com', '2026-03-01', 'ノートPC用スタンド', 3, 4500.00),
(22, '藤田翔', 'sho.fujita@dev.co.jp', '2026-03-04', 'SSDポータブル', 2, 15800.00);
22件のデータが投入されていればOKです。

試してみた
1. 基本:算術演算で仮想列を作る
まず最もシンプルなパターンとして、quantity × unit_price で合計金額を算出する仮想列 total_price を ALTER TABLE で追加します。
ALTER TABLE orders_raw
ADD COLUMN total_price NUMBER(12, 2) AS ((quantity * unit_price)::NUMBER(12, 2));
追加後にSELECTして確認します。
SELECT order_id, customer_name, quantity, unit_price, total_price
FROM orders_raw
ORDER BY order_id
LIMIT 5;
total_price に quantity × unit_price の計算結果が表示されていればOKです。テーブルに値は保存されておらず、クエリのたびに計算されています。

2. 仮想列を仮想列から参照する(チェーン定義)
続いて、先ほど定義した仮想列 total_price を参照する形で、消費税額 tax_amount(10%)を計算する仮想列を追加します。
ALTER TABLE orders_raw
ADD COLUMN tax_amount NUMBER(12, 2) AS ((total_price * 0.1)::NUMBER(12, 2));
SELECT order_id, total_price, tax_amount
FROM orders_raw
ORDER BY order_id
LIMIT 5;
tax_amount が total_price × 0.1 の値になっていればOKです。このように、先に定義した仮想列を参照する形で別の仮想列を定義できます。

3. 文字列関数で派生列を作る
次に、文字列操作の例として、email 列からメールドメインを抽出する仮想列 email_domain を追加します。POSITION で @ の位置を特定し、そこより後の文字列を SUBSTR で取り出します。
ALTER TABLE orders_raw
ADD COLUMN email_domain STRING AS (SUBSTR(email, POSITION('@', email) + 1));
SELECT customer_name, email, email_domain
FROM orders_raw
ORDER BY order_id
LIMIT 5;
email_domain 列に example.com、dev.co.jp などのドメイン部分が表示されていればOKです。SUBSTR や POSITION のような決定的な組み込み文字列関数であれば仮想列の式に使用できます。

4. CASE 式で分類列を作る
CASE 式を使って、合計金額の金額帯を分類する仮想列 price_tier を追加します。total_price(仮想列)を参照して金額区分を算出します。
ALTER TABLE orders_raw
ADD COLUMN price_tier STRING AS (
CASE
WHEN total_price >= 20000 THEN 'high'
WHEN total_price >= 5000 THEN 'mid'
ELSE 'low'
END
);
SELECT order_id, total_price, price_tier
FROM orders_raw
ORDER BY total_price DESC;
total_price の金額に応じて high / mid / low に分類されていればOKです。

5. DESC TABLE / SHOW COLUMNS で仮想列を識別する
テーブルの定義を確認してみます。DESC TABLE を使うと KIND 列で通常の列か仮想列かを識別できます。
DESC TABLE orders_raw;
通常の列は KIND が COLUMN、仮想列は KIND が VIRTUAL と表示され、EXPRESSION 列に定義した式が入っていればOKです。

SHOW COLUMNS でも同様の情報を確認できます。こちらは kind 列が VIRTUAL_COLUMN、expression 列に式が入ります。
SHOW COLUMNS IN TABLE orders_raw;

6. CTAS で仮想列付きテーブルを作る
CREATE TABLE AS SELECT(CTAS) でも仮想列付きテーブルを作成できます。仮想列は SELECT 句の列数にカウントされないため、非仮想列の数だけ SELECT に指定すればOKです。
商品カテゴリマスタと orders_raw を JOIN して、カテゴリ名と税込み価格(仮想列)を持つ集計テーブルを作ります。
まず、商品カテゴリマスタを作成します。
CREATE OR REPLACE TABLE product_categories (
product_name STRING,
category STRING
);
INSERT INTO product_categories VALUES
('ワイヤレスマウス', 'ポインティングデバイス'),
('メカニカルキーボード', '入力デバイス'),
('USBハブ', '周辺機器'),
('ノートPC用スタンド', 'アクセサリ'),
('HDMI変換アダプタ', '変換器'),
('ウェブカメラ', '映像機器'),
('SSDポータブル', 'ストレージ'),
('キーボード用リストレスト', 'アクセサリ'),
('LEDデスクライト', '照明');
次に、CTASで仮想列 tax_included_price(税込み価格)付きのテーブルを作成します。
CREATE OR REPLACE TABLE orders_with_category (
order_id INT,
customer_name STRING,
product_name STRING,
category STRING,
total_price NUMBER(12, 2),
tax_included_price NUMBER(12, 2) AS ((total_price * 1.1)::NUMBER(12, 2))
)
AS SELECT o.order_id, o.customer_name, o.product_name, c.category, o.total_price
FROM orders_raw o
LEFT JOIN product_categories c ON o.product_name = c.product_name;
SELECT * FROM orders_with_category ORDER BY order_id LIMIT 5;
tax_included_price に total_price × 1.1 の税込み金額が表示されていればOKです。CTASでも問題なく仮想列を定義できます。

7. 禁止される式を試してエラーを確認する
仮想列で使用できない式を実際に試し、エラーメッセージを確認します。
非決定的関数(RANDOM)
ALTER TABLE orders_raw
ADD COLUMN random_val INT AS (RANDOM());

NOT NULL 制約
ALTER TABLE orders_raw
ADD COLUMN total_price2 NUMBER(12, 2) AS ((quantity * unit_price)::NUMBER(12, 2)) NOT NULL;

依存列の DROP COLUMN
仮想列が依存している列を DROP COLUMN しようとするとブロックされます。
ALTER TABLE orders_raw DROP COLUMN quantity;

最後に
Snowflake の Virtual Columns(仮想列)を複数のパターンで試してみました。
これまで「テーブル+View」のペアで実現していた計算列をテーブル本体に統合でき、管理するオブジェクト数を減らせるのは大きなメリットだと感じました。ストレージコストも追加発生しないため、積極的に使っていける機能だと思います。
一方で、使用できない式の種類(非決定的関数・集計・UDFなど)、NOT NULL / CHECK 制約の非対応、COALESCE × LEFT JOIN の既知問題など、押さえておくべき制限は多めのため注意は必要です。
GAになったばかりの機能なので、今後の改善にも期待したいところです。ぜひ試してみてください。





