🐿 こども経済新聞 Python入門18歳までに読む本声で覚える英語英語の試験ガイド
段階
0 / 14 ステップ

まず、全体の地図

どこまで行けば「実務で使える」のかを先に知っておく

Snowflakeの学習でよくある行き止まりは、「SELECT文は書けるようになったが、データを入れる方法も、 誰にどこまで権限を渡すかも、いくらかかっているのかも分からない」という状態です。 実務でSnowflakeを任される人に求められるのは、SQLの読み書きだけではなく、 データを取り込む・きれいに保つ・自動で回す・権限を守る・お金を使いすぎないという運用の全体です。 このページは、その全体を4つの段階に分けて並べています。

段階ステップ身につくこと目安この段階を終えると
準備 STEP 0〜1 トライアル作成、画面操作、仕組みと課金、ロールとオブジェクトの階層 3〜7日 自分のアカウントで安全にクエリを実行できる
基礎 STEP 2〜5 SELECT、集計、結合とCTE、テーブル作成とデータ更新 3〜6週間 手元のデータを自力で集計し、表を作れる
応用 STEP 6〜9 ファイルのロード、JSONなど半構造化データ、ウィンドウ関数、Time Travel・クローン 4〜8週間 外部データを取り込み、Snowflakeらしい機能を使える
実務 STEP 10〜13 ビュー・ストリーム・タスクによる自動化、権限設計、コストと性能、外部連携、チーム開発 1〜2か月 データ基盤の運用を任せてもらえる

※ 目安は1日1時間ほど学習した場合です。SQLの経験がある方は STEP 2〜4 を数日で通過できます。まったく初めての方は、STEP 2〜4 にいちばん時間をかけてください。ここが土台です。

先に結論 ── Snowflakeでいちばん大事な考え方は「データを置く場所(ストレージ)と、計算する機械(ウェアハウス)が完全に分かれている」ことです。 置いておくだけならほとんどお金はかかりません。お金がかかるのは、計算機を動かした秒数です。 この1点さえ体に入れば、Snowflakeの設計も課金も、驚くほど素直に理解できます。

目次(クリックでジャンプ)

14ステップの学習ロードマップ

STEP 0 準備

Snowflakeとは何か。無料トライアルで1本クエリを流す

目安 1〜2日
このステップの到達点 ── 無料トライアルのアカウントを作り、ブラウザの画面(Snowsight)から SELECT を実行して結果が表示される。ソフトのインストールは一切ありません。

Snowflakeとは何をするものか

Snowflakeは、クラウド上のデータウェアハウス(DWH)です。データウェアハウスとは、社内のあちこちにある大量のデータを1か所に集めて、分析のために使う「データの倉庫」のことをいいます。

ふだん使うExcelは、数万行を超えたあたりから重くなり、ファイルが人ごとに分かれて「どれが正しいのか分からない」状態になります。Snowflakeは数億行でも数秒で集計でき、全員が同じ1つのデータを見るための仕組みです。しかも、サーバーを買う・置く・アップデートするといった作業が一切ありません。ブラウザさえあれば使えます。

道具得意なこと限界
Excel / スプレッドシート手元の数千行を、目で見ながら加工する行数が増えると重い。人ごとにファイルが分裂する
MySQL / PostgreSQLアプリの裏側でデータを読み書きする(業務システム用)大量データの集計は苦手。サーバー管理が必要
Snowflake大量データの集計・分析。複数人での共有1件ずつの高速な書き込みには向かない

※ MySQLなどを「業務システムの心臓」、Snowflakeを「分析用の倉庫」と考えると役割の違いが分かりやすくなります。実務では、業務システムのデータを毎晩Snowflakeへコピーして分析する、という組み合わせがよく使われます。

1. トライアルアカウントを作る

signup.snowflake.com にアクセスし、氏名・会社名・メールアドレスを入力します。クレジットカードの登録は不要です。途中で3つ選ぶ項目があります。

  • エディション ── 学習用なら Enterprise を選んでください。Standardでは Time Travel が1日までに制限され、STEP 9・10 で扱う機能の一部が試せません。トライアル期間中は、どれを選んでも料金は発生しません。
  • クラウド ── AWS / Azure / Google Cloud から選びます。迷ったら AWS で問題ありません。
  • リージョン ── 日本から使うなら Asia Pacific (Tokyo) が速いです。

登録すると、メールにアカウント専用のURL(https://xxxxxxx-yyyyyyy.snowflakecomputing.com のような形)が届きます。このURLは毎日使うのでブックマークしてください。初回ログイン時にパスワードを設定し、多要素認証(MFA)の登録を求められたら画面の指示にしたがってスマートフォンの認証アプリを登録します。

トライアルの中身 30日間、または $400相当のクレジットを使い切るまで無料です。このページの学習をひととおり行っても、使うのはそのごく一部です。ただし、大きなウェアハウスを起動したまま放置すると一気に減るので、STEP 1 の自動停止の設定は必ず行ってください。

2. Snowsight(画面)の見方

ログイン後に表示されるブラウザ画面を Snowsight(スノーサイト)といいます。最初に覚える場所は4つだけです。

場所役割使う場面
Projects › WorksheetsSQLを書いて実行する紙(ワークシート)いちばん長く居る場所。ここで学習を進める
Data › Databasesデータベース・スキーマ・テーブルの一覧どんなデータがあるかを探すとき
Admin › Warehouses計算機(ウェアハウス)の一覧と設定サイズ変更、自動停止の設定
Admin › Cost Managementクレジットの消費状況いくら使ったかの確認。週1回は見る

3. はじめてのクエリ

「Projects › Worksheets」を開き、右上の「+」で新しいワークシートを作ります。右上にロールとウェアハウスを選ぶ欄があるので、ロールは ACCOUNTADMIN、ウェアハウスは COMPUTE_WH(最初から用意されています)を選んでください。そこに次を貼り付けて、Ctrl + Enter(Macは ⌘ + Enter)を押します。

Snowsight ワークシート
SELECT
    CURRENT_VERSION()   AS version,      -- Snowflakeのバージョン
    CURRENT_USER()      AS me,           -- ログインしている自分
    CURRENT_ROLE()      AS my_role,      -- 今の権限(ロール)
    CURRENT_WAREHOUSE() AS my_wh,        -- 今使っている計算機
    CURRENT_TIMESTAMP() AS now;
実行結果(例)
VERSION   ME       MY_ROLE        MY_WH       NOW
9.x.x     UEKI     ACCOUNTADMIN   COMPUTE_WH  2026-08-14 09:12:33.412 +0900

表が出れば成功です。この5つは「自分は今、誰として、どの計算機を使っているのか」を確認する呪文で、うまく動かないときの最初の点検にもなります。

4. 練習用のデータを見てみる

Snowflakeのアカウントには、最初から練習用の巨大なサンプルデータ SNOWFLAKE_SAMPLE_DATA が入っています。自分でデータを用意しなくても、すぐに本物の分析を試せます。

サンプルデータをのぞく
-- 顧客テーブルの中身を10行だけ見る
SELECT *
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER
LIMIT 10;

-- 注文テーブルは何行あるか
SELECT COUNT(*) AS 行数
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS;
実行結果(例)
行数
1500000

150万行の件数を数えるのに、おそらく1秒もかかっていないはずです。この速さが、Snowflakeを使う最大の理由です。

最初にやっておくこと 画面右上のアカウントメニューから、言語や時刻の表示形式(タイムゾーン)を確認しておきましょう。既定のタイムゾーンは America/Los_Angeles です。日本時間で日付を扱いたい場合は、STEP 3 で設定方法を説明します。「集計結果の日付が1日ずれる」の原因はほぼこれです。
練習問題

SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.LINEITEM テーブルの行数を数えてください。また、そのテーブルにどんな列があるかを調べてください。

解答を見る
answer.sql
SELECT COUNT(*) FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.LINEITEM;   -- 約600万行

-- 列の一覧と型を見る
DESC TABLE SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.LINEITEM;

DESC TABLE(DESCRIBE の略)は、実務でも毎日使うコマンドです。左メニューの Data からテーブルをクリックしても同じ情報が見られます。

STEP 1 準備

仕組みと料金 ── ウェアハウス・ストレージ・ロール

目安 2〜5日
このステップの到達点 ── なぜSnowflakeが速いのか、どこにお金がかかるのかを説明できる。自分専用のウェアハウスとデータベースを作り、自動停止を設定できる。ここを飛ばすと、後で高額請求と権限エラーで必ずつまずきます。

3つの層に分かれている

Snowflakeは、次の3つの層でできています。この分離が、他のデータベースとの決定的な違いです。

役割お金
ストレージ層データそのものを置いておく場所。圧縮して保管される容量 × 期間(月におよそ$20〜25/TB。安い)
コンピュート層
(仮想ウェアハウス)
SQLを実際に計算する機械。必要なときだけ起動する動いた秒数 × サイズ(ここが料金の大半)
クラウドサービス層権限の確認、クエリの最適化、メタデータの管理原則無料(1日のウェアハウス使用量の10%を超えた分のみ課金)

重要なのは、データを置いておくだけではほとんど課金されないことと、ウェアハウスは停止していれば1円もかからないことです。逆に言えば、起動しっぱなしにすると、何もしていなくても課金され続けます。

ウェアハウスのサイズとクレジット

ウェアハウスの利用量は「クレジット」という単位で数えます。1クレジットの価格はエディションとリージョンで変わりますが、日本のAWS環境ではおおよそ1クレジット=$3前後と考えておくと感覚がつかめます。

サイズ1時間あたりのクレジット目安使いどころ
X-Small1約$3/時学習・開発はこれで十分
Small2約$6/時日常的な集計、BIツールからの接続
Medium4約$12/時数千万〜億行の加工
Large8約$24/時大規模なバッチ処理
X-Large 以上16、32、64…1段階ごとに倍特別に重い処理だけ。安易に上げない

※ 課金は秒単位ですが、起動するたびに最低60秒は課金されます。1秒のクエリを10回別々に流すより、まとめて流すほうが安く済みます。

自分の作業場所を作る

いきなり本番のような構成を作る必要はありません。まずは学習用のウェアハウスとデータベースを作ります。以下をワークシートに貼って、上から順に実行してください。

setup.sql
USE ROLE SYSADMIN;   -- 物を作るときのロール(ACCOUNTADMINで作業しない)

-- 学習用の計算機。使い終わったら60秒で自動停止する
CREATE WAREHOUSE IF NOT EXISTS LEARN_WH
    WAREHOUSE_SIZE     = 'XSMALL'
    AUTO_SUSPEND       = 60      -- 60秒アイドルで停止(重要)
    AUTO_RESUME        = TRUE    -- クエリが来たら自動で起動
    INITIALLY_SUSPENDED = TRUE
    COMMENT = '学習用';

-- 学習用のデータベースとスキーマ
CREATE DATABASE IF NOT EXISTS LEARN_DB;
CREATE SCHEMA   IF NOT EXISTS LEARN_DB.SALES;

-- これから使う場所を宣言する(ワークシートの右上でも選べる)
USE WAREHOUSE LEARN_WH;
USE DATABASE  LEARN_DB;
USE SCHEMA    SALES;

SELECT CURRENT_WAREHOUSE(), CURRENT_DATABASE(), CURRENT_SCHEMA();
実行結果
CURRENT_WAREHOUSE()  CURRENT_DATABASE()  CURRENT_SCHEMA()
LEARN_WH             LEARN_DB            SALES
AUTO_SUSPEND を必ず設定する Snowsightの画面から作ると、自動停止が10分に設定されることがあります。学習用なら60秒で十分です。「昨日つけっぱなしだった」を防ぐ、いちばん効くコスト対策がこれです。

データの住所は「3階建て」

Snowflakeのテーブルは、必ず データベース › スキーマ › テーブル の3階層のどこかにあります。フルネームは LEARN_DB.SALES.ORDERS のように、ピリオドでつなぎます。USE で場所を宣言していれば、ORDERS だけで書けます。

スキーマは「フォルダ」だと思ってよい パソコンでいえば、データベースがドライブ、スキーマがフォルダ、テーブルがファイルです。実務では RAW(取り込んだままの生データ)、STAGING(整形中)、MART(分析用の完成品)のようにスキーマを分けるのが定番です。

ロール(権限)の基本

Snowflakeでは、権限は個人ではなくロールに付きます。人はロールを「着替える」ことで、できることが変わります。最初から次の5つが用意されています。

ロールできること使う場面
ACCOUNTADMINすべて。請求情報も見られるふだん使わない。初期設定と課金確認のときだけ
SECURITYADMIN権限の付与・剥奪、ロールの管理権限まわりの管理
USERADMINユーザーとロールの作成人が増えたとき
SYSADMINデータベース・ウェアハウス・テーブルの作成物を作るときの基本ロール
PUBLIC全員が自動的に持つ最低限の権限全社に公開してよいものだけ
ロールを確かめる
SHOW ROLES;                    -- どんなロールがあるか
SELECT CURRENT_AVAILABLE_ROLES();  -- 自分が着替えられるロール
USE ROLE SYSADMIN;             -- 着替える
ACCOUNTADMINで日常作業をしない これは全権限を持つロールです。うっかり本番テーブルを削除できてしまいますし、そのロールで作ったオブジェクトは他の人から見えず、後で「なぜか誰も触れないテーブル」が生まれます。作業は SYSADMIN 以下で行うのが鉄則です。
練習問題

① 今あるウェアハウスの一覧と、その自動停止時間を確認してください。② 最初から用意されている COMPUTE_WH の自動停止も60秒に変更してください。

解答を見る
answer.sql
SHOW WAREHOUSES;   -- 結果の auto_suspend 列(秒)を見る

USE ROLE SYSADMIN;
ALTER WAREHOUSE COMPUTE_WH SET AUTO_SUSPEND = 60;

-- 今すぐ止めたいとき
ALTER WAREHOUSE COMPUTE_WH SUSPEND;

SHOW 系のコマンドは、直後に SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())) と書くと、結果をSQLで絞り込めます。

STEP 2 基礎

SELECT ── データを取り出す

目安 1週間
このステップの到達点 ── 必要な列と行だけを取り出し、並べ替えて表示できる。SQLの文の形が頭に入り、エラーメッセージを読んで直せる。

SQLは「何がほしいか」を書く言語

SQLは、手順ではなく結果の条件を書く言語です。「注文テーブルから、金額が1万円以上の行を、日付の新しい順に10件」と日本語で言えれば、それがほぼそのままSQLになります。文の順番は、いつもこの形です。

SQLの基本の形
SELECT   欲しい列
FROM     どのテーブルから
WHERE    どんな条件の行だけ
ORDER BY 何の順に並べる
LIMIT    何件まで;

まずはサンプルデータで動かします。SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER は架空の顧客データです。

select_basic.sql
USE WAREHOUSE LEARN_WH;

SELECT
    C_CUSTKEY   AS 顧客番号,
    C_NAME      AS 顧客名,
    C_NATIONKEY AS 国コード,
    C_ACCTBAL   AS 残高
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER
WHERE C_ACCTBAL > 9000          -- 残高が9000より大きい行だけ
ORDER BY C_ACCTBAL DESC         -- 残高の大きい順(DESC=降順)
LIMIT 10;
実行結果(例)
顧客番号  顧客名               国コード  残高
143500    Customer#000143500        2   9999.72
 42324    Customer#000042324       17   9999.24
 40114    Customer#000040114       14   9998.99
 ...

AS は列に別名を付ける書き方です。日本語の見出しにしたいときは AS "売上金額" のように二重引用符で囲みます。

WHERE ── 行をしぼる書き方

書き方意味
= <>等しい/等しくないWHERE status = '完了'
> >=より大きい/以上WHERE amount >= 10000
BETWEEN範囲(両端を含む)WHERE d BETWEEN '2026-01-01' AND '2026-03-31'
INいずれかに一致WHERE pref IN ('東京都','大阪府')
LIKEあいまい検索(%=任意の文字列)WHERE name LIKE '田中%'
ILIKE大文字小文字を区別しないLIKEWHERE mail ILIKE '%@Example.com'
IS NULL値が入っていないWHERE memo IS NULL
AND OR NOT条件の組み合わせWHERE a = 1 AND (b = 2 OR c = 3)
NULLは「不明」であって、0でも空文字でもありません WHERE memo = NULL と書いても、1行も返りません(「不明と等しいか」は判定できないため)。必ず IS NULL / IS NOT NULL を使います。計算でも、100 + NULL の答えは NULL です。これはSQL初学者が最も多く踏む落とし穴です。
null_handling.sql
SELECT
    COALESCE(memo, '(記載なし)')  AS memo,   -- NULLなら代わりの値を返す
    IFNULL(point, 0) + 10           AS point,  -- COALESCEの2引数版
    NVL2(memo, '有', '無')          AS 有無     -- NULLでない/NULLで出し分け
FROM (SELECT NULL AS memo, NULL AS point);
実行結果
MEMO        POINT  有無
(記載なし)    10   無

データ型を知っておく

入るもの実務での使い方
NUMBER(38,0)整数(INT / INTEGER も同じもの)件数、ID
NUMBER(12,2)小数点以下2桁までの数金額はこれ。FLOATは誤差が出るので避ける
FLOAT浮動小数点数科学計算、割合。金額には使わない
VARCHAR文字列(最大16MB)名前、コード。文字数指定は任意
DATE日付売上日、締め日
TIMESTAMP_NTZ日時(タイムゾーンなし)既定はこれ。ログの時刻
TIMESTAMP_TZ / LTZタイムゾーン付きの日時海外拠点をまたぐとき
BOOLEANTRUE / FALSEフラグ
VARIANTJSONなど何でもSTEP 7で詳しく
cast.sql
SELECT
    '1234'::NUMBER               AS 文字を数値に,   -- :: が型変換の書き方
    CAST('2026-08-14' AS DATE)   AS 文字を日付に,   -- 標準SQLの書き方(同じ意味)
    TRY_CAST('あ' AS NUMBER)     AS 失敗しても止めない,  -- 変換できなければNULL
    ROUND(1234.567, 1)           AS 四捨五入,
    TO_CHAR(1234567, '999,999,999') AS カンマ区切り;
実行結果
文字を数値に  文字を日付に  失敗しても止めない  四捨五入  カンマ区切り
1234          2026-08-14    NULL                1234.6      1,234,567
TRY_CAST は実務の必需品 取り込んだデータに1行でも変な値が混じっていると、CAST はクエリ全体をエラーで止めます。TRY_CAST なら、変換できない行だけ NULL になって処理は続きます。TRY_TO_DATETRY_TO_NUMBER も同じ考え方です。

大文字・小文字と引用符のルール

Snowflakeでは、引用符で囲まずに書いた名前はすべて大文字として保存されますcreate table orders と書いても、実体は ORDERS です。一方、"orders" と二重引用符で囲むと、その通りの小文字で保存され、以後も毎回 "orders" と書かなければならなくなります。

テーブル名・列名を二重引用符で囲まない 外部ツールから作ったテーブルが "Order Date" のような名前になっていると、以後すべてのSQLで引用符が必要になり、非常に扱いにくくなります。自分で作るときは英大文字と _ だけにしてください。なお、文字列の値はシングルクォート'東京')です。ダブルクォートは名前用、シングルクォートは値用、と覚えます。

エラーメッセージの読み方

よく出るエラー意味直し方
SQL compilation error: Object 'X' does not existそのテーブルが見つからないスペル、USE DATABASE、DB.スキーマ.テーブルの3階層を確認
invalid identifier 'Y'その列名が無い列名のスペル。DESC TABLE で確認
No active warehouse selected計算機が選ばれていないUSE WAREHOUSE LEARN_WH;
Numeric value 'abc' is not recognized数値に変換できない値があるTRY_CAST を使う。元データを確認
Insufficient privileges to operate on ...権限が足りないロールを切り替える(STEP 11)

エラーは失敗ではなく、Snowflakeが場所を教えてくれている状態です。行番号と、引用符で囲まれた名前の2つを見れば、原因のほとんどは特定できます。

練習問題

SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS から、注文状態(O_ORDERSTATUS)が 'F' で、金額(O_TOTALPRICE)が30万を超える注文を、金額の大きい順に5件表示してください。

解答を見る
answer.sql
SELECT O_ORDERKEY, O_ORDERDATE, O_TOTALPRICE, O_ORDERSTATUS
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS
WHERE O_ORDERSTATUS = 'F'
  AND O_TOTALPRICE > 300000
ORDER BY O_TOTALPRICE DESC
LIMIT 5;

※ 条件を2つ以上つなぐときは AND。行を減らす条件は多いほどクエリは速く、安くなります。

STEP 3 基礎

集計 ── GROUP BY と日付の扱い

目安 1〜2週間
このステップの到達点 ── 「部署別の合計」「月ごとの件数」といった、実務でいちばん多く求められる集計を書ける。Excelのピボットテーブルに相当する処理がSQLでできる。

集計関数

aggregate.sql
SELECT
    COUNT(*)                AS 行数,          -- 行の数(NULLも数える)
    COUNT(O_COMMENT)        AS コメント有り,   -- NULLは数えない
    COUNT(DISTINCT O_CUSTKEY) AS 顧客数,      -- 重複を除いた数
    SUM(O_TOTALPRICE)       AS 合計金額,
    AVG(O_TOTALPRICE)       AS 平均金額,
    MIN(O_ORDERDATE)        AS 最初の注文日,
    MAX(O_ORDERDATE)        AS 最後の注文日,
    MEDIAN(O_TOTALPRICE)    AS 中央値
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS;
実行結果(例)
行数      コメント有り  顧客数   合計金額          平均金額     最初の注文日  最後の注文日  中央値
1500000   1500000      99996    229577310891.6    153051.5     1992-01-01    1998-08-02   144357.4
平均だけを見ない 実務では、平均は少数の極端な値に引っぱられます。MEDIAN(中央値)や PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY 金額)(上位10%の境目)も一緒に見ると、データの姿を見誤りません。

GROUP BY ── 「〜ごと」の集計

「顧客ごと」「月ごと」のように単位を決めて集計するのが GROUP BY です。SELECTに書いた列のうち、集計関数で包まれていない列は、すべてGROUP BYに書くのがルールです。

group_by.sql
SELECT
    O_ORDERSTATUS          AS 状態,
    COUNT(*)               AS 件数,
    SUM(O_TOTALPRICE)      AS 合計金額
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS
WHERE O_ORDERDATE >= '1998-01-01'   -- 集計する前に行をしぼる
GROUP BY O_ORDERSTATUS               -- 状態ごとにまとめる
HAVING COUNT(*) > 1000               -- まとめた後の結果をしぼる
ORDER BY 合計金額 DESC;
実行結果(例)
状態  件数     合計金額
O     70000    10800000000.0
F     69000    10600000000.0
P      3900      600000000.0
WHERE と HAVING の違い WHERE集計する前に行をしぼり、HAVING集計した後の結果をしぼります。「1998年以降のデータで(WHERE)、件数が1000件を超えるグループだけ(HAVING)」という関係です。処理の順番は FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT で、これを覚えると多くの疑問が解けます。
Snowflakeの便利機能:GROUP BY ALL 列が増えるとGROUP BYの書き写しが面倒になります。Snowflakeでは GROUP BY ALL と書くだけで、「集計関数で包まれていない列すべて」を自動的に指定してくれます。列の追加漏れによるエラーが消えるので、実務では多用されます。

CASE ── 条件で値を振り分ける

case_when.sql
SELECT
    CASE
        WHEN O_TOTALPRICE >= 300000 THEN '大口'
        WHEN O_TOTALPRICE >= 100000 THEN '中口'
        ELSE '小口'
    END                                              AS 区分,
    COUNT(*)                                         AS 件数,
    -- 条件に合う行だけを数える書き方(実務で頻出)
    COUNT(CASE WHEN O_ORDERSTATUS = 'F' THEN 1 END)  AS 完了件数,
    SUM(IFF(O_ORDERSTATUS = 'F', O_TOTALPRICE, 0))   AS 完了金額
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS
GROUP BY ALL
ORDER BY 件数 DESC;
実行結果(例)
区分  件数     完了件数  完了金額
小口  611000   300000    18500000000.0
中口  700000   345000    75200000000.0
大口  189000    93000    69300000000.0

IFF(条件, 真のとき, 偽のとき) は、Excelの IF 関数とまったく同じ働きをする、CASEの短縮形です。

日付の扱い

実務の集計は、9割が「日付ごと」「月ごと」です。日付関数は最優先で覚えてください。

date_functions.sql
SELECT
    CURRENT_DATE()                              AS 今日,
    DATE_TRUNC('MONTH', CURRENT_DATE())         AS 今月の1日,   -- 月初に切り下げ
    LAST_DAY(CURRENT_DATE())                    AS 月末,
    DATEADD('DAY', -7, CURRENT_DATE())          AS 7日前,
    DATEDIFF('DAY', '2026-01-01', CURRENT_DATE()) AS 経過日数,
    TO_CHAR(CURRENT_DATE(), 'YYYY-MM')          AS 年月,
    YEAR(CURRENT_DATE())                        AS 年,
    DAYNAME(CURRENT_DATE())                     AS 曜日;
実行結果(例)
今日        今月の1日    月末        7日前       経過日数  年月      年     曜日
2026-08-14  2026-08-01  2026-08-31  2026-08-07  225      2026-08  2026   Fri

月別の売上集計は、この DATE_TRUNC を使うのが定番です。

monthly_sales.sql
SELECT
    DATE_TRUNC('MONTH', O_ORDERDATE) AS 年月,
    COUNT(*)                         AS 件数,
    ROUND(SUM(O_TOTALPRICE))         AS 売上
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS
WHERE O_ORDERDATE >= '1998-01-01'
GROUP BY ALL
ORDER BY 年月;
実行結果(例)
年月        件数    売上
1998-01-01  19000   2900000000
1998-02-01  17000   2600000000
1998-03-01  19000   2900000000
...
タイムゾーンで日付がずれる Snowflakeの初期設定のタイムゾーンは America/Los_Angeles です。CURRENT_TIMESTAMP() や、タイムゾーン付きの時刻を日付に変換すると、日本時間とずれます。次のどちらかで対処してください。
timezone.sql
-- 方法1: 自分のユーザー既定を日本時間にする(おすすめ)
ALTER USER CURRENT_USER SET TIMEZONE = 'Asia/Tokyo';

-- 方法2: その場だけ変換する
SELECT CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP()) AS 日本時間;

-- アカウント全体を変えたいとき(ACCOUNTADMINが必要。影響が大きいので合意のうえで)
-- ALTER ACCOUNT SET TIMEZONE = 'Asia/Tokyo';
練習問題

TPCH_SF1.CUSTOMER から、国コード(C_NATIONKEY)ごとに、顧客数・平均残高・残高がマイナスの顧客数を求め、顧客数の多い順に並べてください。

解答を見る
answer.sql
SELECT
    C_NATIONKEY                              AS 国コード,
    COUNT(*)                                 AS 顧客数,
    ROUND(AVG(C_ACCTBAL), 2)                 AS 平均残高,
    COUNT(CASE WHEN C_ACCTBAL < 0 THEN 1 END) AS マイナス顧客数
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER
GROUP BY ALL
ORDER BY 顧客数 DESC;

COUNT(CASE WHEN 条件 THEN 1 END) は「条件に合う行だけを数える」定型句です。ELSEを書かなければ、条件外はNULLになり数えられません。

STEP 4 基礎

結合とCTE ── 複数の表を組み合わせる

目安 1〜2週間
このステップの到達点 ── JOINで表をつなぎ、長いSQLを WITH で読みやすく分割できる。ここがSQLの山場です。越えれば、実務のSQLの大半が読めるようになります。

なぜ表が分かれているのか

データベースでは、注文のたびに顧客名や住所を書き写すことはしません。顧客の情報は顧客テーブルに1回だけ持ち、注文テーブルには顧客番号だけを置きます。こうすれば、住所が変わったときに1か所直せば済みます。この分け方を正規化といい、分かれた表を必要なときにつなぎ直すのが JOIN です。ExcelのVLOOKUPに相当します。

join_basic.sql
SELECT
    o.O_ORDERKEY    AS 注文番号,
    o.O_ORDERDATE   AS 注文日,
    c.C_NAME        AS 顧客名,
    n.N_NAME        AS 国名,
    o.O_TOTALPRICE  AS 金額
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS   AS o
JOIN SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER AS c
     ON o.O_CUSTKEY = c.C_CUSTKEY            -- 注文の顧客番号 = 顧客の顧客番号
JOIN SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.NATION   AS n
     ON c.C_NATIONKEY = n.N_NATIONKEY        -- さらに国マスタもつなぐ
WHERE o.O_ORDERDATE >= '1998-07-01'
ORDER BY o.O_TOTALPRICE DESC
LIMIT 10;
実行結果(例)
注文番号  注文日      顧客名               国名      金額
4722021   1998-07-08  Customer#000082873   JAPAN     555285.16
2078117   1998-07-14  Customer#000094807   GERMANY   544089.09
...

AS o のような短い別名(エイリアス)を付け、o.列名 と書くのが実務の作法です。どの表の列かが一目で分かり、同じ列名が両方にあるときのエラーも防げます。

JOINの種類

種類結果に残る行使う場面
INNER JOIN
(単に JOIN)
両方に相手がいる行だけ基本。ただし片方に無い行は消える
LEFT JOIN左の表は全部残す。右に無ければNULL実務で最も使う。「注文は全部残し、マスタに無ければ空欄」
FULL OUTER JOIN両方の全行2つの表の差分を調べる
CROSS JOIN全組み合わせ(行数が掛け算になる)カレンダー表と店舗表の掛け合わせなど
JOINで行が増えたら、それは事故のサイン つなぐ相手が1行に決まらない(同じキーが複数ある)と、行数が勝手に増えます。JOINの前後で COUNT(*) を比べる習慣をつけてください。増えていたら、右側の表でキーが重複しています。次のSQLで確認できます。
check_duplicate_key.sql
-- つなぐ相手(マスタ)に、キーの重複が無いかを確認する
SELECT C_CUSTKEY, COUNT(*) AS 件数
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER
GROUP BY C_CUSTKEY
HAVING COUNT(*) > 1;    -- 0行なら重複なし=安心してJOINできる

LEFT JOIN と「つながらなかった行」

left_join.sql
-- 注文はすべて残しつつ、マスタに無い顧客番号を洗い出す
SELECT
    o.O_ORDERKEY,
    o.O_CUSTKEY,
    c.C_NAME
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS   AS o
LEFT JOIN SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER AS c
       ON o.O_CUSTKEY = c.C_CUSTKEY
WHERE c.C_CUSTKEY IS NULL     -- つながらなかった行だけ=マスタ漏れ
LIMIT 20;

この「LEFT JOIN したうえで IS NULL で絞る」型は、データの取りこぼしを見つける点検SQLとして現場で頻繁に使います。

WITH(CTE)── SQLを段階に分けて書く

実務のSQLは何十行にもなります。入れ子のサブクエリで書くと読めなくなるため、WITH を使って「途中の表」に名前を付けながら、上から順に組み立てます。これをCTE(共通テーブル式)といいます。

cte.sql
WITH 月別 AS (                                   -- ① まず月ごとに集計
    SELECT
        DATE_TRUNC('MONTH', O_ORDERDATE) AS 年月,
        SUM(O_TOTALPRICE)                AS 売上
    FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS
    WHERE O_ORDERDATE >= '1998-01-01'
    GROUP BY ALL
),
平均 AS (                                        -- ② ①を使って平均を出す
    SELECT AVG(売上) AS 月平均 FROM 月別
)
SELECT                                           -- ③ ①と②を組み合わせる
    m.年月,
    ROUND(m.売上)                        AS 売上,
    ROUND(a.月平均)                      AS 月平均,
    ROUND(m.売上 / a.月平均 * 100, 1)    AS 平均比パーセント
FROM 月別 AS m
CROSS JOIN 平均 AS a
ORDER BY m.年月;
実行結果(例)
年月        売上        月平均      平均比パーセント
1998-01-01  2900000000  2750000000  105.5
1998-02-01  2600000000  2750000000   94.5
...
CTEは「作業用の中間テーブル」だと思ってよい 実際にテーブルは作られず、そのクエリの中でだけ有効です。1つのCTEに1つの仕事をさせ、名前を日本語や分かりやすい英語にすると、他人が読めるSQLになります。上から順に読める形で書くこと自体が、実務で評価される技術です。

UNION ALL ── 表を縦につなぐ

union.sql
SELECT '東日本' AS 地域, O_ORDERKEY, O_TOTALPRICE
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS WHERE O_ORDERKEY < 100
UNION ALL              -- 重複を除かない(速い。ふつうはこちら)
SELECT '西日本' AS 地域, O_ORDERKEY, O_TOTALPRICE
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS WHERE O_ORDERKEY BETWEEN 100 AND 200;
-- UNION(ALL無し)は重複行を除くが、その分だけ遅い
練習問題

国名ごとの「顧客数」と「注文の合計金額」を1つの表にしてください(NATIONCUSTOMERORDERS の3つを使います)。注文が1件もない国も、行として残してください。

解答を見る
answer.sql
WITH 注文 AS (
    SELECT c.C_NATIONKEY, COUNT(DISTINCT c.C_CUSTKEY) AS 顧客数, SUM(o.O_TOTALPRICE) AS 売上
    FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER AS c
    LEFT JOIN SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS AS o
           ON c.C_CUSTKEY = o.O_CUSTKEY
    GROUP BY ALL
)
SELECT
    n.N_NAME              AS 国名,
    t.顧客数,
    ROUND(COALESCE(t.売上, 0)) AS 売上
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.NATION AS n
LEFT JOIN 注文 AS t ON n.N_NATIONKEY = t.C_NATIONKEY
ORDER BY 売上 DESC;

※ 先に集計してからJOINするのが要点です。JOINしてから集計すると、行が増えている分だけ金額が重複して膨らむことがあります。

STEP 5 基礎

自分のテーブルを作り、データを入れ替える

目安 1週間
このステップの到達点 ── テーブルを設計して作り、行を追加・更新・削除できる。MERGEで「あれば更新、なければ追加」ができる。ここから先の全ステップで使う練習用データを自分で用意します。

テーブルを作る

create_table.sql
USE ROLE SYSADMIN;
USE WAREHOUSE LEARN_WH;
USE SCHEMA LEARN_DB.SALES;

CREATE OR REPLACE TABLE CUSTOMERS (
    CUSTOMER_ID   NUMBER(10,0)  NOT NULL,        -- 必ず値が必要
    CUSTOMER_NAME VARCHAR(100)  NOT NULL,
    PREFECTURE    VARCHAR(20),
    SIGNUP_DATE   DATE,
    IS_ACTIVE     BOOLEAN       DEFAULT TRUE,     -- 省略時の値
    CREATED_AT    TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);

CREATE OR REPLACE TABLE ORDERS (
    ORDER_ID    NUMBER(12,0)  NOT NULL,
    CUSTOMER_ID NUMBER(10,0)  NOT NULL,
    ORDER_DATE  DATE          NOT NULL,
    CHANNEL     VARCHAR(20),                      -- 'WEB' / 'STORE' / 'PHONE'
    AMOUNT      NUMBER(12,2)  NOT NULL,           -- 金額は必ずNUMBER
    STATUS      VARCHAR(10)   DEFAULT 'OPEN'
);

SHOW TABLES;          -- 作られたか確認
DESC TABLE ORDERS;    -- 列の定義を確認
PRIMARY KEY は宣言できるが、守ってくれない Snowflakeでは PRIMARY KEYFOREIGN KEY を書けますが、重複や不整合を実際にはじいてくれるわけではありません(強制されるのは NOT NULL だけ)。他のデータベースから来た人が必ず驚く点です。書く意味はあります(設計意図がツールに伝わる)が、データの正しさは自分で点検SQLを書いて守る必要があります。
CREATE OR REPLACECREATE IF NOT EXISTS 前者は既存のテーブルを中身ごと作り直します(学習中は便利、本番では危険)。後者は既にあれば何もしません。本番の運用スクリプトでは CREATE TABLE IF NOT EXISTS か、既存の定義を活かして変更できる CREATE OR ALTER TABLE を使い分けます。

データを入れる(INSERT)

insert.sql
INSERT INTO CUSTOMERS (CUSTOMER_ID, CUSTOMER_NAME, PREFECTURE, SIGNUP_DATE) VALUES
    (1, '山田商店',   '東京都',   '2025-04-01'),
    (2, '佐藤物産',   '大阪府',   '2025-06-15'),
    (3, '鈴木工業',   '愛知県',   '2026-01-20'),
    (4, '田中フーズ', '福岡県',   '2026-03-02'),
    (5, '高橋電機',   '北海道',   '2026-05-11');

INSERT INTO ORDERS (ORDER_ID, CUSTOMER_ID, ORDER_DATE, CHANNEL, AMOUNT, STATUS) VALUES
    (1001, 1, '2026-06-01', 'WEB',   120000, 'PAID'),
    (1002, 1, '2026-06-18', 'STORE',  38000, 'PAID'),
    (1003, 2, '2026-06-20', 'WEB',   256000, 'PAID'),
    (1004, 3, '2026-07-02', 'PHONE',  74000, 'OPEN'),
    (1005, 2, '2026-07-11', 'WEB',    91000, 'PAID'),
    (1006, 4, '2026-07-25', 'WEB',   183000, 'CANCEL'),
    (1007, 5, '2026-08-03', 'STORE', 452000, 'PAID'),
    (1008, 1, '2026-08-09', 'WEB',    65000, 'OPEN');

SELECT COUNT(*) FROM ORDERS;
実行結果
COUNT(*)
8
1行ずつのINSERTは実務では使わない Snowflakeは大量データをまとめて扱うのが得意で、逆に1行ずつの書き込みは苦手です(1件ごとにコストがかかります)。実務では、次のSTEP 6で学ぶ COPY INTO でファイルから一括投入するのが基本です。ここでのINSERTは、あくまで練習用データの用意です。

SELECTの結果からテーブルを作る(CTAS)

ctas.sql
-- 集計結果をそのままテーブルにする。列の型は自動で決まる
CREATE OR REPLACE TABLE MONTHLY_SALES AS
SELECT
    DATE_TRUNC('MONTH', ORDER_DATE) AS 年月,
    COUNT(*)                        AS 件数,
    SUM(AMOUNT)                     AS 売上
FROM ORDERS
WHERE STATUS <> 'CANCEL'
GROUP BY ALL;

SELECT * FROM MONTHLY_SALES ORDER BY 年月;
実行結果
年月        件数  売上
2026-06-01   3    414000.00
2026-07-01   2    165000.00
2026-08-01   2    517000.00

更新・削除・MERGE

update_delete.sql
-- 更新(WHEREを忘れると全行が書き換わる。必ず先にSELECTで確認)
UPDATE ORDERS SET STATUS = 'PAID' WHERE ORDER_ID = 1004;

-- 削除
DELETE FROM ORDERS WHERE STATUS = 'CANCEL';

-- 中身だけ全部消す(テーブルの定義は残る)
-- TRUNCATE TABLE ORDERS;

-- テーブルごと消す
-- DROP TABLE ORDERS;
UPDATE / DELETE を書くときの手順 ① まず SELECT * FROM ... WHERE 同じ条件 を実行して、対象行を目で確認する ② 件数が想定どおりなら、SELECT * の部分だけを UPDATE ... SET に書き換える。この順番を守るだけで、事故はほぼ防げます。なお、間違えて消してもSTEP 9のTime Travelで戻せます。あわてないでください。

MERGE は「同じキーがあれば更新、なければ追加」を1文で行う命令です。日次でデータを同期するときの必須構文で、実務では毎日使います。

merge.sql
-- 今日届いた差分データ(本来はファイルから取り込む)
CREATE OR REPLACE TEMPORARY TABLE ORDERS_DELTA AS
SELECT * FROM ORDERS WHERE FALSE;   -- 同じ形の空テーブルを作る小技

INSERT INTO ORDERS_DELTA VALUES
    (1008, 1, '2026-08-09', 'WEB',  65000, 'PAID'),    -- 既存 → 更新される
    (1009, 3, '2026-08-13', 'WEB', 138000, 'OPEN');    -- 新規 → 追加される

MERGE INTO ORDERS AS t                       -- 対象(target)
USING ORDERS_DELTA AS s                      -- 差分(source)
   ON t.ORDER_ID = s.ORDER_ID                -- 突き合わせるキー
WHEN MATCHED THEN UPDATE SET
    t.STATUS = s.STATUS,
    t.AMOUNT = s.AMOUNT
WHEN NOT MATCHED THEN INSERT
    (ORDER_ID, CUSTOMER_ID, ORDER_DATE, CHANNEL, AMOUNT, STATUS)
    VALUES (s.ORDER_ID, s.CUSTOMER_ID, s.ORDER_DATE, s.CHANNEL, s.AMOUNT, s.STATUS);
実行結果
number of rows inserted  number of rows updated
1                        1

テーブルの3つの種類

種類特徴使い分け
PERMANENT
(既定)
Time Travel(最大90日)とFail-safe(7日)がある本番データ。消えたら困るもの
TRANSIENTFail-safeが無く、Time Travelは最大1日。保管料が安いいつでも作り直せる中間テーブル
TEMPORARY接続(セッション)を閉じると消えるその場かぎりの作業用

※ 何度でも再生成できる中間テーブルを TRANSIENT にしておくと、Fail-safe用の保管料がかからず、ストレージ費用を下げられます。実務のコスト削減で最初に検討される項目のひとつです。

練習問題

商品テーブル PRODUCTS(商品ID・商品名・カテゴリ・単価)を作り、5行入れてください。そのうえで、カテゴリごとの平均単価を求めてください。

解答を見る
answer.sql
CREATE OR REPLACE TABLE PRODUCTS (
    PRODUCT_ID   NUMBER(8,0)  NOT NULL,
    PRODUCT_NAME VARCHAR(100) NOT NULL,
    CATEGORY     VARCHAR(30),
    UNIT_PRICE   NUMBER(10,2) NOT NULL
);

INSERT INTO PRODUCTS VALUES
    (1, 'ノートPC',     '電子機器', 128000),
    (2, 'モニター',     '電子機器',  32000),
    (3, 'デスク',       '家具',      45000),
    (4, 'チェア',       '家具',      28000),
    (5, 'ボールペン',   '文具',         180);

SELECT CATEGORY AS カテゴリ, COUNT(*) AS 品目数, AVG(UNIT_PRICE) AS 平均単価
FROM PRODUCTS
GROUP BY ALL
ORDER BY 平均単価 DESC;
STEP 6 応用

データを取り込む ── ステージとCOPY INTO

目安 2週間
このステップの到達点 ── 手元のCSVをSnowflakeに取り込める。ステージ・ファイルフォーマット・COPY INTO の関係を説明でき、取り込みエラーを自分で調べて直せる。実務でいちばん相談される作業がこれです。

取り込みの3つの道具

Snowflakeへのデータ投入は、必ず次の流れになります。用語が多く見えますが、やっていることは「置き場に置く → 読み方を決める → 表に流し込む」だけです。

道具役割たとえると
ステージ
(STAGE)
ファイルを一時的に置く場所荷物の置き場
ファイルフォーマット
(FILE FORMAT)
区切り文字・文字コード・ヘッダ有無などの読み方の指定荷物の開け方の説明書
COPY INTOステージのファイルをテーブルに流し込む命令荷物を棚に並べる作業

ステージには4種類あります。最初は「名前付き内部ステージ」だけ覚えれば十分です。

種類書き方使う場面
ユーザーステージ@~自分だけが使う一時ファイル
テーブルステージ@%ORDERSそのテーブル専用の取り込み
名前付き内部ステージ@MY_STAGE基本。チームで共有できる
外部ステージ@S3_STAGES3・Azure Blob・GCSを直接参照する(本番の主流)

方法A:画面からCSVをアップロードする(最短)

数万行までなら、Snowsightの画面だけで完結します。左メニューの Data › Databases から対象のスキーマを開き、右上の Create › Table › From File を選び、CSVファイルをドラッグします。列名と型を自動で推測してくれるので、確認して「Load」を押すだけです。まずはこれで、取り込みの成功体験を作ってください。

文字コードは UTF-8 で Excelから「CSV(カンマ区切り)」で保存したファイルは Shift_JIS のことがあり、日本語が化けます。Excelでは「CSV UTF-8(コンマ区切り)」を選んで保存してください。これだけで、日本のデータ取り込みトラブルの半分は消えます。

方法B:SQLで取り込む(実務の型)

本番では手作業ではなくSQLで行います。ファイルフォーマットとステージを1回作れば、あとは COPY INTO を繰り返すだけです。

stage_setup.sql
USE SCHEMA LEARN_DB.SALES;

-- ① 読み方の説明書を作る
CREATE OR REPLACE FILE FORMAT CSV_JP
    TYPE = 'CSV'
    FIELD_DELIMITER = ','
    SKIP_HEADER = 1                       -- 1行目は見出しなので読み飛ばす
    FIELD_OPTIONALLY_ENCLOSED_BY = '"'    -- 値が " で囲まれている場合に対応
    NULL_IF = ('', 'NULL', 'null', 'NA')  -- 空欄はNULLとして扱う
    EMPTY_FIELD_AS_NULL = TRUE
    ENCODING = 'UTF8'
    DATE_FORMAT = 'YYYY-MM-DD'
    TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'
    ERROR_ON_COLUMN_COUNT_MISMATCH = FALSE;  -- 列数のズレで即エラーにしない

-- ② 置き場を作る
CREATE OR REPLACE STAGE MY_STAGE
    FILE_FORMAT = CSV_JP
    COMMENT = '取り込み用の一時置き場';

SHOW STAGES;

作ったステージにファイルを置くには、パソコン側からアップロードします。Snowsightの Data › Databases › スキーマ › Stages から画面でアップロードするか、Snowflake CLI(STEP 12)から PUT コマンドを使います。

ターミナル(Snowflake CLI)
# 手元のCSVをステージへアップロード(自動で圧縮される)
snow stage copy ./orders_202608.csv @MY_STAGE

# 置かれたファイルの確認は、SQL側から LIST でもできる
copy_into.sql
-- ③ ステージの中身を確認
LIST @MY_STAGE;

-- ④ 取り込む前に、中身を目で見る(これができるのがSnowflakeの強み)
SELECT $1, $2, $3, $4, $5, $6      -- $1 は1列目という意味
FROM @MY_STAGE/orders_202608.csv
(FILE_FORMAT => CSV_JP)
LIMIT 5;

-- ⑤ 本番の取り込み前に、検証だけ行う(1行も入らない)
COPY INTO ORDERS
FROM @MY_STAGE/orders_202608.csv
FILE_FORMAT = (FORMAT_NAME = CSV_JP)
VALIDATION_MODE = 'RETURN_ERRORS';

-- ⑥ 取り込む
COPY INTO ORDERS
FROM @MY_STAGE/orders_202608.csv
FILE_FORMAT = (FORMAT_NAME = CSV_JP)
ON_ERROR = 'CONTINUE';       -- エラー行は飛ばして続行
実行結果(例)
file                    status   rows_parsed  rows_loaded  errors_seen
orders_202608.csv.gz    LOADED   12043        12040        3

ON_ERROR の選び方

指定動き使う場面
ABORT_STATEMENT(既定)1行でもエラーがあれば全体を中止会計データなど、1行の欠けも許されないとき
CONTINUEエラー行だけ飛ばして続行ログなど、多少の欠損を許せるとき
SKIP_FILEそのファイルごと飛ばすファイル単位で品質を管理するとき
check_copy_error.sql
-- どの行が、なぜ弾かれたのかを調べる
SELECT *
FROM TABLE(VALIDATE(ORDERS, JOB_ID => '_last'));

-- 過去の取り込み履歴(いつ・何件・エラー数)
SELECT FILE_NAME, ROW_COUNT, ROW_PARSED, ERROR_COUNT, STATUS, LAST_LOAD_TIME
FROM TABLE(INFORMATION_SCHEMA.COPY_HISTORY(
    TABLE_NAME => 'ORDERS',
    START_TIME => DATEADD('DAY', -7, CURRENT_TIMESTAMP())
))
ORDER BY LAST_LOAD_TIME DESC;
同じファイルは二重に取り込まれない COPY INTO は、一度読み込んだファイル名を64日間おぼえていて、同じファイルは自動的にスキップします(ロードメタデータ)。二重計上を防いでくれる親切な仕組みですが、「直したCSVを同じ名前で入れ直したのに反映されない」という混乱の原因にもなります。その場合は FORCE = TRUE を付けます。

列の構造が分からないCSVを取り込む

infer_schema.sql
-- ファイルから列名と型を自動で推測させ、その形のテーブルを作る
CREATE OR REPLACE TABLE ORDERS_RAW
USING TEMPLATE (
    SELECT ARRAY_AGG(OBJECT_CONSTRUCT(*))
    FROM TABLE(INFER_SCHEMA(
        LOCATION => '@MY_STAGE/orders_202608.csv',
        FILE_FORMAT => 'CSV_JP'
    ))
);

-- 列の順番ではなく「見出しの名前」で対応づけて取り込む
COPY INTO ORDERS_RAW
FROM @MY_STAGE/orders_202608.csv
FILE_FORMAT = (FORMAT_NAME = CSV_JP)
MATCH_BY_COLUMN_NAME = 'CASE_INSENSITIVE';

MATCH_BY_COLUMN_NAME を使うと、CSVの列の並び順が変わっても壊れません。外部から毎月もらうファイルの取り込みで重宝します。

外部ステージ(S3など)とSnowpipe

本番では、ファイルを手でアップロードしません。クラウドストレージ(S3など)を外部ステージとして直接参照し、そこにファイルが置かれたら自動で取り込みます。

external_stage.sql
-- ① 接続情報(ACCOUNTADMINが1回だけ作る。鍵をSQLに書かずに済む)
CREATE OR REPLACE STORAGE INTEGRATION S3_INT
    TYPE = EXTERNAL_STAGE
    STORAGE_PROVIDER = 'S3'
    ENABLED = TRUE
    STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::123456789012:role/snowflake-role'
    STORAGE_ALLOWED_LOCATIONS = ('s3://my-bucket/sales/');

-- ② 外部ステージ
CREATE OR REPLACE STAGE S3_SALES_STAGE
    STORAGE_INTEGRATION = S3_INT
    URL = 's3://my-bucket/sales/'
    FILE_FORMAT = CSV_JP;

-- ③ ファイルが置かれたら自動で取り込む仕掛け(Snowpipe)
CREATE OR REPLACE PIPE SALES_PIPE
    AUTO_INGEST = TRUE
AS
COPY INTO ORDERS FROM @S3_SALES_STAGE FILE_FORMAT = (FORMAT_NAME = CSV_JP);

SELECT SYSTEM$PIPE_STATUS('SALES_PIPE');   -- 動いているかの確認
認証情報をSQLに直接書かない CREDENTIALS = (AWS_KEY_ID = '...' AWS_SECRET_KEY = '...') という書き方もできますが、クエリ履歴に残り、Gitに載れば流出します。実務では必ずストレージ統合(STORAGE INTEGRATION)を使ってください。鍵をSnowflakeに渡さずに接続できます。

逆に、書き出す(アンロード)

unload.sql
COPY INTO @MY_STAGE/export/monthly_
FROM (SELECT * FROM MONTHLY_SALES ORDER BY 年月)
FILE_FORMAT = (TYPE = 'CSV' COMPRESSION = 'GZIP' HEADER = TRUE)
SINGLE = FALSE
MAX_FILE_SIZE = 100000000
OVERWRITE = TRUE;

LIST @MY_STAGE/export/;
-- GET でパソコンに落とす(Snowflake CLIから)
実践課題

手元にある実際のCSV(売上でも家計簿でも構いません)を1つ選び、① Snowsightの画面から取り込む ② 取り込んだテーブルの行数と、各列のNULL件数を数える ③ 1つでも取り込みエラーが出たら、その原因を COPY_HISTORY で確認する、までをやってみてください。この一連の流れが、データ基盤の入り口の仕事そのものです。

STEP 7 応用

JSONを扱う ── VARIANT と FLATTEN

目安 1週間
このステップの到達点 ── JSON形式のデータをそのまま保存し、SQLで必要な項目だけを取り出せる。入れ子になった配列を行に展開できる。

なぜこれが重要なのか

WebのアクセスログもAPIの応答も、いまのデータはほとんどが JSON という形式です。ふつうのデータベースでは、JSONを表の形に直してからでないと入れられません。Snowflakeは VARIANT型 にそのまま放り込み、後からSQLで取り出せます。「まず入れて、後で考える」ができるのが、Snowflakeが選ばれる大きな理由のひとつです。

variant_basic.sql
USE SCHEMA LEARN_DB.SALES;

CREATE OR REPLACE TABLE EVENTS (
    EVENT_ID  NUMBER,
    PAYLOAD   VARIANT        -- 何が来ても受け止められる型
);

INSERT INTO EVENTS
SELECT 1, PARSE_JSON('{
  "user": {"id": 101, "name": "山田", "pref": "東京都"},
  "action": "purchase",
  "amount": 12800,
  "items": [
    {"sku": "A-01", "qty": 2, "price": 4900},
    {"sku": "B-77", "qty": 1, "price": 3000}
  ],
  "ts": "2026-08-14T10:22:31"
}');

SELECT * FROM EVENTS;

PARSE_JSON は、文字列をJSONとして解釈してVARIANTに変換する関数です。ファイルから取り込む場合は、TYPE = 'JSON' のファイルフォーマットで COPY INTO すれば同じ形になります。

中の値を取り出す

variant_extract.sql
SELECT
    PAYLOAD:action::STRING          AS 行動,       -- コロンで階層をたどる
    PAYLOAD:user.name::STRING       AS 名前,       -- 入れ子はドットでつなぐ
    PAYLOAD:user.id::NUMBER         AS ユーザーID,
    PAYLOAD:amount::NUMBER          AS 金額,
    PAYLOAD:ts::TIMESTAMP_NTZ       AS 時刻,
    PAYLOAD:items[0].sku::STRING    AS 最初の商品,  -- 配列は0から数える
    ARRAY_SIZE(PAYLOAD:items)       AS 商品数
FROM EVENTS;
実行結果
行動      名前   ユーザーID  金額    時刻                  最初の商品  商品数
purchase  山田   101        12800   2026-08-14 10:22:31   A-01        2
::型 を省略しない PAYLOAD:user.name のままだと、値は "山田" のように二重引用符付きのVARIANTとして返ります。文字列として比較や結合をしたいなら、必ず ::STRING を付けてください。数値も同様に ::NUMBER が必要です。「JSONから取った値だけ検索に引っかからない」の原因はほぼこれです。

配列を行に展開する(FLATTEN)

1件のイベントに商品が2つ入っている、というような入れ子の配列は、LATERAL FLATTEN1商品=1行の形に開きます。ここまでできれば、あとはふつうのSQLで集計できます。

flatten.sql
SELECT
    e.EVENT_ID,
    e.PAYLOAD:user.name::STRING AS 名前,
    f.INDEX                     AS 何番目,
    f.VALUE:sku::STRING         AS 商品コード,
    f.VALUE:qty::NUMBER         AS 数量,
    f.VALUE:price::NUMBER       AS 単価,
    f.VALUE:qty::NUMBER * f.VALUE:price::NUMBER AS 小計
FROM EVENTS AS e,
     LATERAL FLATTEN(INPUT => e.PAYLOAD:items) AS f;
実行結果
EVENT_ID  名前   何番目  商品コード  数量  単価   小計
1         山田   0       A-01        2     4900   9800
1         山田   1       B-77        1     3000   3000
FLATTENの4つの列 展開すると SEQ(元の行の通し番号)、INDEX(配列の何番目か)、KEY(オブジェクトのキー名)、VALUE(中身)、PATH(階層のパス)が使えます。使うのはほぼ VALUEINDEX です。

逆に、JSONを作る

build_json.sql
SELECT
    OBJECT_CONSTRUCT(
        'customer_id', CUSTOMER_ID,
        'name',        CUSTOMER_NAME,
        'orders',      ARRAY_AGG(OBJECT_CONSTRUCT('id', ORDER_ID, 'amount', AMOUNT))
    ) AS 顧客JSON
FROM CUSTOMERS
JOIN ORDERS USING (CUSTOMER_ID)     -- 同名の列で結合する短い書き方
GROUP BY CUSTOMER_ID, CUSTOMER_NAME;

APIに渡す形にまとめたり、外部システムへ受け渡したりするときに使います。ARRAY_AGG は「グループ内の値を配列にまとめる」集計関数です。

練習問題

上の EVENTS テーブルに、別のユーザーのイベントを2件追加し、商品コードごとの合計数量と合計金額を求めてください。

解答を見る
answer.sql
SELECT
    f.VALUE:sku::STRING AS 商品コード,
    SUM(f.VALUE:qty::NUMBER) AS 合計数量,
    SUM(f.VALUE:qty::NUMBER * f.VALUE:price::NUMBER) AS 合計金額
FROM EVENTS AS e,
     LATERAL FLATTEN(INPUT => e.PAYLOAD:items) AS f
GROUP BY ALL
ORDER BY 合計金額 DESC;

※ FLATTENしてしまえば、あとはSTEP 3の集計とまったく同じです。「開いてから集計する」が半構造化データの基本の型です。

STEP 8 応用

ウィンドウ関数 ── 順位・前月比・累計

目安 1〜2週間
このステップの到達点 ── 「顧客ごとの最新の注文」「前月比」「累計売上」「重複行の除去」が書ける。ここができると、分析者として一段上に見られます。

GROUP BY との違い

GROUP BY は行をまとめて減らします。一方 ウィンドウ関数は、行を減らさずに、各行の隣に集計値を並べます。「明細を残したまま、その顧客の合計も横に出したい」というときに使います。書き方はいつもこの形です。

ウィンドウ関数の形
関数() OVER (
    PARTITION BY 区切る列      -- 何ごとに計算するか(省略可=全体)
    ORDER BY     並べる列       -- どの順で見るか
)
window_basic.sql
SELECT
    ORDER_ID, CUSTOMER_ID, ORDER_DATE, AMOUNT,
    -- 顧客ごとの合計を、明細の横に並べる
    SUM(AMOUNT) OVER (PARTITION BY CUSTOMER_ID)                       AS 顧客合計,
    -- 顧客ごとの、注文順の通し番号
    ROW_NUMBER() OVER (PARTITION BY CUSTOMER_ID ORDER BY ORDER_DATE)  AS 何回目,
    -- 全体での金額順位
    RANK() OVER (ORDER BY AMOUNT DESC)                                AS 金額順位,
    -- その顧客の前回の注文金額
    LAG(AMOUNT) OVER (PARTITION BY CUSTOMER_ID ORDER BY ORDER_DATE)   AS 前回金額
FROM ORDERS
ORDER BY CUSTOMER_ID, ORDER_DATE;
実行結果(例)
ORDER_ID  CUSTOMER_ID  ORDER_DATE  AMOUNT   顧客合計  何回目  金額順位  前回金額
1001      1            2026-06-01  120000   223000   1       4        NULL
1002      1            2026-06-18   38000   223000   2       7        120000
1008      1            2026-08-09   65000   223000   3       6         38000
1003      2            2026-06-20  256000   347000   1       2        NULL
...

順位づけの3兄弟

関数同点のときの動き使う場面
ROW_NUMBER()同点でも必ず別番号(1,2,3,4)重複除去・最新1件の抽出
RANK()同点は同順位、次は飛ぶ(1,2,2,4)売上ランキング
DENSE_RANK()同点は同順位、次は飛ばない(1,2,2,3)等級・区分の割り当て

QUALIFY ── Snowflakeの隠れた主役

「顧客ごとの最新の注文だけ」を取りたいとき、ふつうのSQLではサブクエリが必要です。Snowflakeには QUALIFY があり、ウィンドウ関数の結果を直接WHEREのように絞れます。実務で圧倒的によく使う機能です。

qualify.sql
-- 顧客ごとの最新の注文だけを1行ずつ取り出す
SELECT ORDER_ID, CUSTOMER_ID, ORDER_DATE, AMOUNT
FROM ORDERS
QUALIFY ROW_NUMBER() OVER (PARTITION BY CUSTOMER_ID ORDER BY ORDER_DATE DESC) = 1
ORDER BY CUSTOMER_ID;

-- 重複行の除去(同じ注文番号が複数あるとき、いちばん新しい1行を残す)
SELECT *
FROM ORDERS
QUALIFY ROW_NUMBER() OVER (PARTITION BY ORDER_ID ORDER BY ORDER_DATE DESC) = 1;
実行結果(例)
ORDER_ID  CUSTOMER_ID  ORDER_DATE  AMOUNT
1008      1            2026-08-09   65000
1005      2            2026-07-11   91000
1009      3            2026-08-13  138000
...
取り込み直したデータの重複を消す定型句 「同じキーで複数回取り込んでしまった」ときの後始末は、この QUALIFY ROW_NUMBER() ... = 1 が定番です。処理の順番は WHERE → GROUP BY → HAVING → QUALIFY で、QUALIFYはウィンドウ計算の後に効きます。

前月比と累計

trend.sql
WITH 月別 AS (
    SELECT DATE_TRUNC('MONTH', O_ORDERDATE) AS 年月, SUM(O_TOTALPRICE) AS 売上
    FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS
    WHERE O_ORDERDATE >= '1997-01-01'
    GROUP BY ALL
)
SELECT
    年月,
    ROUND(売上)                                                  AS 売上,
    ROUND(LAG(売上) OVER (ORDER BY 年月))                        AS 前月,
    ROUND((売上 / LAG(売上) OVER (ORDER BY 年月) - 1) * 100, 1)  AS 前月比パーセント,
    ROUND(SUM(売上) OVER (ORDER BY 年月))                        AS 累計,
    -- 3か月移動平均(自分を含む直近3行の平均)
    ROUND(AVG(売上) OVER (ORDER BY 年月 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)) AS 移動平均3か月
FROM 月別
ORDER BY 年月;
実行結果(例)
年月        売上        前月        前月比パーセント  累計         移動平均3か月
1997-01-01  2870000000  NULL        NULL              2870000000   2870000000
1997-02-01  2600000000  2870000000  -9.4              5470000000   2735000000
1997-03-01  2910000000  2600000000  11.9              8380000000   2793333333
...
前年同月比を出したいとき LAG(売上, 12) OVER (ORDER BY 年月) のように、第2引数で「何行前か」を指定します。ただし、途中に売上ゼロの月があって行そのものが無いと、12行前が前年同月とは限りません。実務では日付マスタ(カレンダーテーブル)と LEFT JOIN して、欠けた月を埋めてから計算するのが安全です。
練習問題

TPCH_SF1.ORDERSCUSTOMER を使い、国コードごとの売上上位3顧客を求めてください。

解答を見る
answer.sql
WITH 顧客別 AS (
    SELECT
        c.C_NATIONKEY   AS 国コード,
        c.C_NAME        AS 顧客名,
        SUM(o.O_TOTALPRICE) AS 売上
    FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS   AS o
    JOIN SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER AS c
      ON o.O_CUSTKEY = c.C_CUSTKEY
    GROUP BY ALL
)
SELECT
    国コード, 顧客名, ROUND(売上) AS 売上,
    RANK() OVER (PARTITION BY 国コード ORDER BY 売上 DESC) AS 順位
FROM 顧客別
QUALIFY 順位 <= 3
ORDER BY 国コード, 順位;

※ QUALIFYの中では、SELECTで付けた別名(順位)をそのまま使えます。これもSnowflakeの親切な仕様です。

STEP 9 応用

Snowflakeならではの機能 ── Time Travelとクローン

目安 3〜5日
このステップの到達点 ── 誤って消したデータを自力で復旧できる。本番データを1秒でコピーして、安全な検証環境を作れる。他のデータベース経験者がいちばん驚く機能です。

Time Travel ── 過去の状態を見る

Snowflakeは、テーブルを更新しても古い状態をしばらく保持しています。そのため、「1時間前のテーブル」をSQLで直接参照できます。保持期間はEnterprise以上で最大90日、既定は1日です。

time_travel.sql
-- 事故を起こしてみる
DELETE FROM ORDERS;             -- 全部消えた!
SELECT COUNT(*) FROM ORDERS;    -- 0

-- 5分前の状態を見る
SELECT COUNT(*) FROM ORDERS AT(OFFSET => -60*5);

-- 特定の時刻の状態を見る
SELECT * FROM ORDERS AT(TIMESTAMP => '2026-08-14 09:00:00'::TIMESTAMP_NTZ);

-- 「あのクエリを実行する直前」の状態を見る(いちばん確実)
SELECT * FROM ORDERS BEFORE(STATEMENT => '01b2c3d4-0000-abcd-0000-000000000001');

-- 復旧する
CREATE OR REPLACE TABLE ORDERS AS
SELECT * FROM ORDERS AT(OFFSET => -60*5);

SELECT COUNT(*) FROM ORDERS;    -- 戻った
クエリIDの調べ方 Snowsightの左メニュー「Monitoring › Query History」で、実行したSQLの一覧とそのIDが見られます。事故が起きたら、まず「やらかしたSQLのID」を控えること。BEFORE(STATEMENT => ...) で、その直前の状態にピンポイントで戻せます。
undrop.sql
DROP TABLE ORDERS;
UNDROP TABLE ORDERS;        -- 消したテーブルを戻す

-- データベースやスキーマごと戻すこともできる
-- UNDROP SCHEMA SALES;
-- UNDROP DATABASE LEARN_DB;

-- 保持期間の設定(Enterprise以上は最大90日)
ALTER TABLE ORDERS SET DATA_RETENTION_TIME_IN_DAYS = 7;
Time Travelはタダではない 保持期間を延ばすと、その分だけ古いデータの保管料がかかります。毎日全件を入れ替える巨大なテーブルで90日に設定すると、ストレージ費用が跳ね上がります。本番の重要テーブルだけ長め、中間テーブルは0〜1日が実務の落としどころです。

Time Travelの期間を過ぎたデータは、さらにFail-safeとして7日間保持されますが、これは自分では触れません。Snowflakeのサポートに依頼して初めて復旧できる、最後の保険です(時間も費用もかかります)。頼りにするものではありません。

ゼロコピークローン ── 一瞬で複製する

1TBのテーブルを複製するのに、ふつうは1TBのコピーと長い時間が必要です。Snowflakeでは、実データをコピーせず「同じデータを指す別の名前」を作るため、巨大なテーブルでも数秒、追加のストレージ費用もほぼゼロです。変更した部分だけが実体を持ちます。

clone.sql
-- 本番のコピーで検証する(データベースまるごと)
CREATE DATABASE DEV_DB CLONE LEARN_DB;

-- テーブル単位でも
CREATE TABLE ORDERS_BACKUP CLONE ORDERS;

-- 「昨日の状態」でクローンする(Time Travelとの合わせ技)
CREATE TABLE ORDERS_YESTERDAY CLONE ORDERS AT(OFFSET => -60*60*24);

-- 権限も引き継ぎたいとき
-- CREATE TABLE ORDERS_BK CLONE ORDERS COPY GRANTS;
実務での使い道 ① 大きな更新の前に CLONE で退避しておく(数秒で終わる保険)② 本番と同じデータで開発環境を作る ③ 月末時点のスナップショットを残す。「危ない作業の前にクローン」は、Snowflake実務者の基本動作です。

結果キャッシュ ── 2回目がタダになる

cache.sql
-- 同じSQLを2回実行してみる。2回目は一瞬で終わり、ウェアハウスすら起動しない
SELECT COUNT(*) FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.LINEITEM;

-- 検証のためキャッシュを無効にする(性能を測るときだけ)
ALTER SESSION SET USE_CACHED_RESULT = FALSE;

まったく同じSQLで、元データも変わっていなければ、Snowflakeは24時間以内の結果をそのまま返します。ウェアハウスが動かないので、料金もかかりません。ダッシュボードを何人もが開くような場面で、大きく効きます。

データ共有とMarketplace(概要だけ)

Snowflakeでは、データをコピーせずに他社・他部門のアカウントへ共有できます(Secure Data Sharing)。相手はそのデータを自分のアカウントのテーブルのように読めますが、実体は1つのままです。また、Marketplaceでは天気・人流・為替などの外部データを、その場で自分のアカウントに追加して結合できます。「データを送る」から「データを見せる」への転換が、Snowflakeの思想です。

練習問題

CUSTOMERS テーブルをクローンして CUSTOMERS_BK を作る ② 元のテーブルで UPDATE を実行して1行書き換える ③ Time Travelを使って、書き換える前の値を確認する、をやってみてください。

解答を見る
answer.sql
CREATE OR REPLACE TABLE CUSTOMERS_BK CLONE CUSTOMERS;

UPDATE CUSTOMERS SET PREFECTURE = '神奈川県' WHERE CUSTOMER_ID = 1;

-- 変更前と変更後を並べて比べる
SELECT '変更後' AS 状態, PREFECTURE FROM CUSTOMERS WHERE CUSTOMER_ID = 1
UNION ALL
SELECT '変更前', PREFECTURE FROM CUSTOMERS AT(OFFSET => -60) WHERE CUSTOMER_ID = 1;

※ 変更前後を UNION ALL で並べて比べるのは、影響範囲の確認に使える実用的な型です。

STEP 10 実務

自動で回す ── ビュー・ストリーム・タスク・動的テーブル

目安 2週間
このステップの到達点 ── 毎朝の集計を人手なしで回せる。「取り込む → 加工する → 分析用の表を更新する」という、データ基盤の心臓部を自分で組める。

ビュー ── SQLに名前を付けて保存する

よく使う集計SQLは、毎回書かずにビューとして保存します。ビューは実データを持たず、参照されるたびに中のSQLが実行される「名前の付いたSELECT文」です。

views.sql
USE SCHEMA LEARN_DB.SALES;

-- ふつうのビュー(実データを持たない。常に最新)
CREATE OR REPLACE VIEW V_MONTHLY_SALES AS
SELECT
    DATE_TRUNC('MONTH', o.ORDER_DATE) AS 年月,
    c.PREFECTURE                      AS 都道府県,
    COUNT(*)                          AS 件数,
    SUM(o.AMOUNT)                     AS 売上
FROM ORDERS AS o
JOIN CUSTOMERS AS c USING (CUSTOMER_ID)
WHERE o.STATUS <> 'CANCEL'
GROUP BY ALL;

SELECT * FROM V_MONTHLY_SALES ORDER BY 年月, 売上 DESC;
種類実体使いどころ
VIEW持たない(毎回計算)基本。定義を共有したいとき
SECURE VIEW持たない+定義を隠す他社へのデータ共有、行レベルの制限
MATERIALIZED VIEW持つ(自動で更新される)1つのテーブルへの重い集計を繰り返すとき。Enterprise以上。制約が多い
DYNAMIC TABLE持つ(指定した遅延内で自動更新)いまの主流。JOINを含む加工でも使える

タスク ── 決まった時刻にSQLを実行する

task.sql
CREATE OR REPLACE TASK T_REFRESH_MONTHLY
    SCHEDULE = 'USING CRON 0 6 * * * Asia/Tokyo'   -- 毎朝6時(日本時間)
    USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE = 'XSMALL'  -- サーバーレスで動かす
AS
    CREATE OR REPLACE TABLE MONTHLY_SALES AS
    SELECT * FROM V_MONTHLY_SALES;

-- 作った直後のタスクは停止状態。明示的に開始する(忘れがち)
ALTER TASK T_REFRESH_MONTHLY RESUME;

-- 手で1回動かして試す
EXECUTE TASK T_REFRESH_MONTHLY;

-- 実行結果の確認
SELECT NAME, STATE, SCHEDULED_TIME, COMPLETED_TIME, ERROR_MESSAGE
FROM TABLE(INFORMATION_SCHEMA.TASK_HISTORY())
ORDER BY SCHEDULED_TIME DESC
LIMIT 10;
CRONの読み方 分 時 日 月 曜日 の5つです。0 6 * * * は「毎日6時0分」、0 */2 * * * は「2時間おき」、30 9 * * 1 は「毎週月曜9時30分」。タイムゾーンを必ず書いてください。省略するとUTCになり、9時間ずれます。
作ったタスクは RESUME するまで動かない Snowflakeのタスクは、作成直後は必ず SUSPENDED(停止)です。「毎朝動くはずなのに動かない」という相談の原因は、9割がこれです。SHOW TASKS;state 列を確認してください。

ストリーム ── 変わった行だけを拾う

毎回テーブル全体を作り直すのは、データが大きくなると無駄です。ストリームは、あるテーブルに対する「前回見た時点からの変更(追加・更新・削除)」だけを取り出す仕掛けです。

stream.sql
-- ORDERSの変更を監視するストリーム
CREATE OR REPLACE STREAM S_ORDERS ON TABLE ORDERS;

-- 変更を起こす
INSERT INTO ORDERS VALUES (1010, 2, '2026-08-14', 'WEB', 77000, 'OPEN');
UPDATE ORDERS SET STATUS = 'PAID' WHERE ORDER_ID = 1004;

-- ストリームには「差分」だけが見える
SELECT ORDER_ID, AMOUNT, STATUS,
       METADATA$ACTION,      -- INSERT / DELETE
       METADATA$ISUPDATE     -- 更新による削除+挿入か
FROM S_ORDERS;
実行結果(例)
ORDER_ID  AMOUNT  STATUS  METADATA$ACTION  METADATA$ISUPDATE
1010      77000   OPEN    INSERT           FALSE
1004      74000   OPEN    DELETE           TRUE
1004      74000   PAID    INSERT           TRUE
ストリームは読むと空になる 正確には、ストリームをDML(INSERT/MERGEなど)の中で使うと、その位置(オフセット)が進んで差分が消費されます。単なる SELECT では消えません。この性質のため、ストリームは「タスクの中でMERGEに渡す」使い方が基本になります。

ストリーム+タスクで差分反映を自動化する

stream_task.sql
CREATE OR REPLACE TABLE ORDERS_MART LIKE ORDERS;   -- 同じ形の空テーブル

CREATE OR REPLACE TASK T_SYNC_ORDERS
    SCHEDULE = '5 MINUTE'
    WHEN SYSTEM$STREAM_HAS_DATA('S_ORDERS')   -- 差分があるときだけ動く=無駄な課金なし
AS
    MERGE INTO ORDERS_MART AS t
    USING (SELECT * FROM S_ORDERS WHERE METADATA$ACTION = 'INSERT') AS s
       ON t.ORDER_ID = s.ORDER_ID
    WHEN MATCHED THEN UPDATE SET t.STATUS = s.STATUS, t.AMOUNT = s.AMOUNT
    WHEN NOT MATCHED THEN INSERT (ORDER_ID, CUSTOMER_ID, ORDER_DATE, CHANNEL, AMOUNT, STATUS)
         VALUES (s.ORDER_ID, s.CUSTOMER_ID, s.ORDER_DATE, s.CHANNEL, s.AMOUNT, s.STATUS);

ALTER TASK T_SYNC_ORDERS RESUME;

WHEN SYSTEM$STREAM_HAS_DATA() を付けると、差分が無いときはタスクがスキップされ、ウェアハウスが起動しないので課金もされません。5分おきのタスクでも安心して回せます。

動的テーブル ── 「作り方」だけ書けば勝手に更新される

ストリームとタスクを自分で組み立てるのは、正しく作るのが難しい部分です。動的テーブル(Dynamic Table)は、「このSELECTの結果を、最大◯分の遅れで保ってほしい」と宣言するだけで、Snowflakeが差分更新の仕組みを自動で組み立ててくれます。新規に組むなら、まずこちらを検討してください。

dynamic_table.sql
CREATE OR REPLACE DYNAMIC TABLE DT_MONTHLY_SALES
    TARGET_LAG = '10 minutes'      -- 元データから最大10分遅れまで許す
    WAREHOUSE  = LEARN_WH
AS
SELECT
    DATE_TRUNC('MONTH', o.ORDER_DATE) AS 年月,
    c.PREFECTURE                      AS 都道府県,
    COUNT(*)                          AS 件数,
    SUM(o.AMOUNT)                     AS 売上
FROM ORDERS AS o
JOIN CUSTOMERS AS c USING (CUSTOMER_ID)
WHERE o.STATUS <> 'CANCEL'
GROUP BY ALL;

SELECT * FROM DT_MONTHLY_SALES;               -- ふつうのテーブルとして読める
SHOW DYNAMIC TABLES;                           -- 更新状況の確認
使い分けの目安 ── ビュー:軽い整形、常に最新が必要。動的テーブル:定期的に更新される加工パイプライン(迷ったらこれ)。ストリーム+タスク:外部システムへの通知など、宣言的に書けない複雑な処理。タスク単独:日次のメンテナンス作業。
実践課題

「都道府県ごとの累計売上」を保持する動的テーブルを作り、ORDERS に新しい行を追加してから、10分後にその内容が自動で更新されていることを確認してください。あわせて SHOW DYNAMIC TABLES; で、最後に更新された時刻を確認してください。

STEP 11 実務

権限・コスト・性能 ── 運用を任される人の仕事

目安 2〜3週間
このステップの到達点 ── 誰に何を見せるかを設計でき、使いすぎを防ぐ仕組みを設定でき、遅いクエリの原因を自分で調べられる。ここが「SQLが書ける人」と「任せられる人」の境目です。

ロールの設計 ── 2階建てにする

実務では、ロールを2種類に分けて設計します。この形が、Snowflakeの推奨であり、人事異動にも強い作り方です。

  • アクセスロール(例:SALES_READSALES_WRITE)── 「どのデータに何ができるか」を持つ
  • 機能ロール(例:ANALYSTENGINEER)── 職務を表す。アクセスロールを束ねて持つ

人には機能ロールだけを渡します。権限を足したいときはアクセスロールを1つ付け替えるだけで済み、誰が何を見られるかが表で説明できる状態になります。

roles.sql
USE ROLE SECURITYADMIN;

CREATE ROLE IF NOT EXISTS SALES_READ;    -- アクセスロール
CREATE ROLE IF NOT EXISTS ANALYST;       -- 機能ロール

-- 参照に必要な3点セット(どれか1つ欠けても読めない)
GRANT USAGE ON DATABASE LEARN_DB              TO ROLE SALES_READ;
GRANT USAGE ON SCHEMA   LEARN_DB.SALES        TO ROLE SALES_READ;
GRANT SELECT ON ALL TABLES IN SCHEMA LEARN_DB.SALES TO ROLE SALES_READ;

-- これから作られるテーブルにも自動で権限を付ける(重要)
GRANT SELECT ON FUTURE TABLES IN SCHEMA LEARN_DB.SALES TO ROLE SALES_READ;
GRANT SELECT ON FUTURE VIEWS  IN SCHEMA LEARN_DB.SALES TO ROLE SALES_READ;

-- 計算機を使う権限も必要
GRANT USAGE ON WAREHOUSE LEARN_WH TO ROLE SALES_READ;

-- 機能ロールに束ねて、人に渡す
GRANT ROLE SALES_READ TO ROLE ANALYST;
GRANT ROLE ANALYST    TO USER "SATO";

-- 管理者から見えるように、SYSADMINの下にもぶら下げる(推奨)
GRANT ROLE ANALYST TO ROLE SYSADMIN;
「テーブルは見えるのに中身が読めない」の正体 Snowflakeの参照権限は、データベースのUSAGE + スキーマのUSAGE + テーブルのSELECTの3点セットです。どれか1つでも欠けると読めません。また、ON ALL TABLES実行した時点のテーブルにしか効かないため、明日作るテーブルには ON FUTURE TABLES が必要です。この2点で、権限のトラブルのほとんどが説明できます。
check_grants.sql
SHOW GRANTS TO ROLE ANALYST;          -- そのロールが持つ権限
SHOW GRANTS ON TABLE ORDERS;          -- そのテーブルに付いている権限
SHOW GRANTS TO USER "SATO";           -- その人が持つロール

ユーザーと認証

users.sql
USE ROLE USERADMIN;

CREATE USER IF NOT EXISTS SATO
    LOGIN_NAME = 'sato'
    DEFAULT_ROLE = ANALYST
    DEFAULT_WAREHOUSE = LEARN_WH
    MUST_CHANGE_PASSWORD = TRUE;

-- プログラムから接続する用途では、パスワードではなく鍵を使う
-- ALTER USER BATCH_USER SET RSA_PUBLIC_KEY = 'MIIBIj...';
人はMFA、プログラムはキーペア 人が使うアカウントには多要素認証(MFA)を必須にしてください。バッチやツールから接続するアカウントは、パスワードではなくキーペア認証にします。Snowflakeはパスワードのみの接続を段階的に廃止しており、これは推奨ではなく前提になりつつあります。

コスト管理 ── 使いすぎを止める

resource_monitor.sql
USE ROLE ACCOUNTADMIN;

CREATE OR REPLACE RESOURCE MONITOR RM_LEARN
    WITH CREDIT_QUOTA = 50              -- 月に50クレジットまで
         FREQUENCY = MONTHLY
         START_TIMESTAMP = IMMEDIATELY
    TRIGGERS
        ON 75  PERCENT DO NOTIFY                    -- 75%で通知
        ON 90  PERCENT DO NOTIFY
        ON 100 PERCENT DO SUSPEND                   -- 到達で新規クエリを止める
        ON 110 PERCENT DO SUSPEND_IMMEDIATE;        -- 実行中のものも止める

ALTER WAREHOUSE LEARN_WH SET RESOURCE_MONITOR = RM_LEARN;
cost_check.sql
USE ROLE ACCOUNTADMIN;

-- ウェアハウス別の、直近30日のクレジット消費
SELECT
    WAREHOUSE_NAME,
    ROUND(SUM(CREDITS_USED), 2) AS クレジット
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE START_TIME >= DATEADD('DAY', -30, CURRENT_TIMESTAMP())
GROUP BY ALL
ORDER BY クレジット DESC;

-- 重いクエリの犯人探し(誰の、どのSQLが時間を使ったか)
SELECT
    USER_NAME, WAREHOUSE_NAME,
    ROUND(TOTAL_ELAPSED_TIME / 1000, 1) AS 秒,
    ROUND(BYTES_SCANNED / POWER(1024, 3), 2) AS 読んだGB,
    LEFT(QUERY_TEXT, 80) AS SQL冒頭
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE START_TIME >= DATEADD('DAY', -7, CURRENT_TIMESTAMP())
  AND TOTAL_ELAPSED_TIME > 60000        -- 60秒以上かかったもの
ORDER BY TOTAL_ELAPSED_TIME DESC
LIMIT 20;
ACCOUNT_USAGE と INFORMATION_SCHEMA SNOWFLAKE.ACCOUNT_USAGE は最大1年分の履歴が見られますが、反映に最大45分〜3時間の遅れがあります。今この瞬間を見たいときは INFORMATION_SCHEMA の関数(QUERY_HISTORY() など)を使います。過去の分析はACCOUNT_USAGE、直近の確認はINFORMATION_SCHEMAと覚えてください。

コストを下げる5つの定石

やること効き方
AUTO_SUSPENDを60秒にするアイドル時間の課金が消える。最初にやるべき対策
用途別にウェアハウスを分ける誰が使っているか分かり、止めやすくなる。BI用・バッチ用・開発用で分ける
サイズを安易に上げない1段階上げると単価が倍。まずSQLの改善を試す
中間テーブルをTRANSIENTにするFail-safe分の保管料が消える
SELECT * をやめるSnowflakeは列単位で読むため、列を絞るほど読む量が減り、速く安くなる

なぜ速いのか ── マイクロパーティションとプルーニング

Snowflakeはテーブルをマイクロパーティションという小さな塊(圧縮前で50〜500MB程度)に自動で分割し、それぞれについて「この塊の日付は何日から何日まで」といった情報を持っています。そのため WHERE ORDER_DATE = '2026-08-14' と書けば、関係のない塊は最初から読み飛ばされます。これをプルーニングといいます。

速いSQLを書くコツは、この仕組みに沿うことです。

  • WHEREで日付を絞る ── 読み飛ばしが効く。範囲が広いほど遅く、高くなる
  • 列を絞る ── SELECT * をやめる
  • 絞ってから結合する ── CTEで先に行を減らす
  • WHEREの列に関数をかけない ── WHERE TO_CHAR(d,'YYYYMM') = '202608' は読み飛ばしが効きません。WHERE d >= '2026-08-01' AND d < '2026-09-01' と書きます
tuning.sql
-- テーブルがどう分割されているかを見る
SELECT SYSTEM$CLUSTERING_INFORMATION('ORDERS', '(ORDER_DATE)');

-- 数TB級の巨大テーブルで、特定の列での絞り込みが多い場合のみ検討
-- ALTER TABLE ORDERS CLUSTER BY (ORDER_DATE);

-- 1行を狙って引く検索が多いテーブル向け(別料金)
-- ALTER TABLE ORDERS ADD SEARCH OPTIMIZATION;
クラスタリングキーは安易に付けない 自動再クラスタリングは継続的にクレジットを消費します。目安として、数TB以上あり、特定の列での絞り込みが頻繁で、かつプルーニングが効いていないと確認できたテーブルにだけ設定します。まずは Query Profile を見るのが先です。

Query Profile ── 遅い原因を目で見る

Snowsightの Query History から個々のクエリを開くと、処理の流れが図で表示されます。見るべき点は3つです。

  • Partitions scanned / total ── 全部読んでいるなら、WHEREが効いていない
  • Bytes spilled to local/remote storage ── メモリ不足でディスクにあふれている。ここが赤信号。SQLを見直すか、一段だけサイズを上げる
  • Most expensive node ── どの処理に時間を使ったか。JOINが原因のことが多い
実践課題

① 直近7日でいちばん時間のかかったクエリを QUERY_HISTORY から探す ② そのクエリをQuery Profileで開き、読み込んだパーティション数を確認する ③ WHEREの条件を足して、読み込み量が減ることを確かめる。この確認ができれば、性能改善の依頼に対応できます。

STEP 12 実務

外部ツールとつなぐ ── Python・CLI・BI・dbt

目安 1〜2週間
このステップの到達点 ── ブラウザの外からSnowflakeを操作できる。PythonからSQLを実行し、結果を受け取って処理できる。Pythonが書ける人は、ここでいっきに世界が広がります。

Snowflake CLI ── 手元からSQLを流す

ターミナル
# 導入(Pythonが入っていれば1行)
pip install snowflake-cli

# 接続情報を対話形式で登録する
snow connection add

# 1本だけ実行
snow sql -q "SELECT CURRENT_VERSION()"

# ファイルにまとめたSQLを実行(バッチ処理の基本形)
snow sql -f ./daily_batch.sql

# ステージへファイルを上げる
snow stage copy ./orders.csv @MY_STAGE

PythonからSnowflakeを使う

SnowflakeにはPython用の公式ライブラリがあります。「SQLで集計し、Pythonでグラフや資料にする」という組み合わせが、実務でもっともよく使われる形です。

ターミナル
pip install "snowflake-connector-python[pandas]" python-dotenv
.env(Gitに載せない)
SF_ACCOUNT=xxxxxxx-yyyyyyy
SF_USER=ueki
SF_PASSWORD=************
SF_WAREHOUSE=LEARN_WH
SF_DATABASE=LEARN_DB
SF_SCHEMA=SALES
query.py
import os

import snowflake.connector
from dotenv import load_dotenv

load_dotenv()   # .env からアカウント情報を読み込む

conn = snowflake.connector.connect(
    account=os.environ["SF_ACCOUNT"],
    user=os.environ["SF_USER"],
    password=os.environ["SF_PASSWORD"],
    warehouse=os.environ["SF_WAREHOUSE"],
    database=os.environ["SF_DATABASE"],
    schema=os.environ["SF_SCHEMA"],
)

sql = """
    SELECT c.PREFECTURE AS 都道府県, SUM(o.AMOUNT) AS 売上
    FROM ORDERS AS o
    JOIN CUSTOMERS AS c USING (CUSTOMER_ID)
    WHERE o.ORDER_DATE >= %s          -- 値は必ずこの形で渡す(文字列連結しない)
    GROUP BY ALL
    ORDER BY 売上 DESC
"""

try:
    cur = conn.cursor()
    cur.execute(sql, ("2026-06-01",))
    df = cur.fetch_pandas_all()       # 結果をそのままpandasのDataFrameで受け取る
    print(df)
    df.to_excel("売上レポート.xlsx", index=False)
finally:
    conn.close()                      # 接続は必ず閉じる
実行結果(例)
   都道府県      売上
0  北海道    452000.00
1  大阪府    347000.00
2  東京都    223000.00
SQLを文字列連結で組み立てない f"WHERE id = {user_input}" のような書き方は、SQLインジェクションという重大な脆弱性になります。値は必ず上のように %s のプレースホルダで渡してください。パスワードやアカウント名も、コードに直接書かず環境変数(.env)から読み、.gitignore.env を必ず加えます。
write_back.py
import pandas as pd
from snowflake.connector.pandas_tools import write_pandas

df = pd.read_csv("results.csv")

# pandasのDataFrameを、そのままテーブルに書き込む
success, nchunks, nrows, _ = write_pandas(
    conn, df, table_name="RESULTS", auto_create_table=True, overwrite=True
)
print(f"{nrows}行を書き込みました")

Snowpark ── PythonをSnowflakeの中で動かす

Snowparkは、pandasに似た書き方でSnowflake上のデータを操作する仕組みです。処理はすべてSnowflake側で実行されるため、大量データを手元に落とさずに済みます。pandasでメモリが足りなくなる規模のとき、これが答えになります。

snowpark_example.py
from snowflake.snowpark import Session
from snowflake.snowpark.functions import col, sum as sum_

session = Session.builder.configs({
    "account": "xxxxxxx-yyyyyyy", "user": "ueki", "password": "****",
    "warehouse": "LEARN_WH", "database": "LEARN_DB", "schema": "SALES",
}).create()

df = (session.table("ORDERS")
      .filter(col("STATUS") != "CANCEL")
      .group_by("CUSTOMER_ID")
      .agg(sum_("AMOUNT").alias("売上"))
      .sort(col("売上").desc()))

df.show()                       # ここで初めてSQLに変換されて実行される
df.write.mode("overwrite").save_as_table("CUSTOMER_SALES")
session.close()
使い分け ── 数万行を手元で加工するなら connector + pandas。数千万行以上を扱う、または処理をSnowflake内で完結させたいなら Snowpark。SQLで書けることは、まずSQLで書くのがいちばん速く、安く、他人にも読めます。

BIツールとdbt

ツール役割覚える順番
Tableau / Power BI / Looker集計結果をグラフとダッシュボードにする会社で使っているものを1つ
Snowsight ダッシュボードSnowflake内蔵。SQLの結果をそのまま図にするまずこれ。追加費用なし
dbtSQLの加工処理をファイルで管理し、依存関係・テスト・文書化を行うSTEP 13の後。実務では事実上の標準
Airflow / Dagster複数システムをまたぐ処理の順序管理必要になったら

Snowsightでは、ワークシートの結果に対して「Chart」タブを押すだけで棒グラフや折れ線グラフになり、それをダッシュボードに固定できます。BIツールを導入する前に、まずここで十分なことが多いので、最初に試してください。

実践課題

Pythonから接続し、① 月別売上を取得して DataFrame にする ② Excelファイルとして保存する ③ そのスクリプトを毎朝実行できる形(引数で対象月を渡せる形)に整える、までを作ってください。Python入門ページのSTEP 9・10と組み合わせると、そのまま業務で使える道具になります。

STEP 13 実務

チームで運用する ── 環境分離・Git・点検

目安 2〜3週間
このステップの到達点 ── 本番を壊さない開発の手順を作れる。SQLをGitで管理し、データの正しさを自動で点検できる。ここまで来れば、データ基盤の担当者として通用します。

環境を分ける

本番のテーブルを直接いじって直す、という運用は必ず事故を生みます。データベースを用途で分け、クローンを使って本番と同じデータで検証します。

environments.sql
USE ROLE SYSADMIN;

CREATE DATABASE IF NOT EXISTS PROD_DB;   -- 本番(人手で触らない)
CREATE DATABASE IF NOT EXISTS DEV_DB;    -- 開発

-- 本番と同じデータで検証したいとき(数秒・追加費用ほぼゼロ)
CREATE OR REPLACE DATABASE DEV_DB CLONE PROD_DB;

-- 本番への変更は、SQLファイルにして流す(画面で手打ちしない)
スキーマ役割テーブルの種類
RAW取り込んだままの生データ。絶対に加工しないPERMANENT
STAGING型変換・重複除去・名寄せなどの整形TRANSIENT(作り直せる)
MART分析用の完成品。BIツールはここだけ見るPERMANENT / DYNAMIC TABLE

※ 生データを保存したまま残しておくのが鉄則です。加工の途中で間違いに気づいても、RAWから作り直せます。ストレージは安く、やり直しは高くつきます。

SQLをGitで管理する

ワークシートに書いたSQLは、他人からは見えず、履歴も残りません。本番に効くSQLは、必ずファイルにしてGitに置きます。Snowflakeには、GitHubなどのリポジトリを直接参照して実行する機能もあります。

git_integration.sql
USE ROLE ACCOUNTADMIN;

CREATE OR REPLACE API INTEGRATION GIT_API
    API_PROVIDER = GIT_HTTPS_API
    API_ALLOWED_PREFIXES = ('https://github.com/my-org')
    ENABLED = TRUE;

CREATE OR REPLACE GIT REPOSITORY SQL_REPO
    API_INTEGRATION = GIT_API
    ORIGIN = 'https://github.com/my-org/snowflake-sql.git';

ALTER GIT REPOSITORY SQL_REPO FETCH;

-- リポジトリ内のSQLファイルをそのまま実行する
EXECUTE IMMEDIATE FROM @SQL_REPO/branches/main/deploy/create_marts.sql;
冪等に書く 何度実行しても同じ結果になるスクリプトを「冪等」といいます。CREATE OR REPLACECREATE IF NOT EXISTSCREATE OR ALTERMERGE を使えば、途中で失敗して流し直しても壊れません。本番用のSQLは、必ず2回実行してみて確認します。

Snowflake Scripting ── 手続きを書く

procedure.sql
CREATE OR REPLACE PROCEDURE SP_DAILY_LOAD(TARGET_DATE DATE)
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE
    row_count INTEGER;
BEGIN
    DELETE FROM MART_SALES WHERE 売上日 = :TARGET_DATE;   -- やり直せるように先に消す

    INSERT INTO MART_SALES
    SELECT ORDER_DATE, SUM(AMOUNT)
    FROM ORDERS
    WHERE ORDER_DATE = :TARGET_DATE
    GROUP BY ORDER_DATE;

    row_count := SQLROWCOUNT;
    RETURN :TARGET_DATE::STRING || ' を ' || :row_count || ' 件で更新しました';
EXCEPTION
    WHEN OTHER THEN
        RETURN '失敗: ' || SQLERRM;
END;
$$;

CALL SP_DAILY_LOAD('2026-08-14');
実行結果
SP_DAILY_LOAD
2026-08-14 を 1 件で更新しました

「対象日を先に消してから入れ直す」という書き方(洗い替え)は、再実行しても二重計上が起きない、実務で最も安全な型です。

データの正しさを点検する

Snowflakeは主キーの重複をはじいてくれません。だからこそ、点検SQLを自分で用意して、毎日流すのが実務の作法です。

data_quality.sql
-- ① 主キーの重複がないか
SELECT 'ORDER_IDの重複' AS 検査, COUNT(*) AS 件数
FROM (SELECT ORDER_ID FROM ORDERS GROUP BY ORDER_ID HAVING COUNT(*) > 1)
UNION ALL
-- ② 必須項目のNULL
SELECT 'CUSTOMER_IDがNULL', COUNT(*) FROM ORDERS WHERE CUSTOMER_ID IS NULL
UNION ALL
-- ③ マスタに存在しない顧客
SELECT 'マスタ未登録の顧客', COUNT(*)
FROM ORDERS AS o LEFT JOIN CUSTOMERS AS c USING (CUSTOMER_ID)
WHERE c.CUSTOMER_ID IS NULL
UNION ALL
-- ④ 金額のマイナスや異常値
SELECT '金額が0以下', COUNT(*) FROM ORDERS WHERE AMOUNT <= 0
UNION ALL
-- ⑤ データが今日も届いているか(届いていなければ0件になる)
SELECT '本日の取り込み件数', COUNT(*) FROM ORDERS WHERE ORDER_DATE = CURRENT_DATE();
実行結果(例)
検査                  件数
ORDER_IDの重複         0
CUSTOMER_IDがNULL      0
マスタ未登録の顧客      0
金額が0以下            0
本日の取り込み件数      12

この点検SQLをタスク(STEP 10)で毎朝流し、異常があれば通知する仕組みにすれば、「気づいたら3日前からデータが止まっていた」という最悪の事態を防げます。Snowflakeには SYSTEM$SEND_EMAIL や Alert 機能もあり、通知まで内製できます。

チームで守る決めごと

  • 命名規約 ── テーブルは複数形の大文字(ORDERS)、ビューは V_、動的テーブルは DT_、ステージは STG_ など、先に決めて文書化する
  • コメントを残す ── COMMENT ON TABLE ORDERS IS '受注明細。RAWから日次で洗い替え'。これがそのまま社内のデータカタログになる
  • 本番のACCOUNTADMINは限られた人だけ ── 権限は必要最小限から始め、足りなければ足す
  • 大きな変更の前にクローン ── 数秒で取れる保険を惜しまない
  • レビューを通す ── 本番に効くSQLは、他人の目を1回通す。事故のほとんどは WHERE の書き忘れ
卒業課題

次の一連の仕組みを、自分のアカウントで最初から最後まで作ってください。これができれば、実務でSnowflakeを任せてもらえる水準です。

  1. CSVファイルを外部(またはローカル)から RAW スキーマに COPY INTO で取り込む
  2. STAGING で型変換・重複除去を行う(QUALIFYを使う)
  3. MART に、分析用の集計テーブルを動的テーブルで作る
  4. 毎朝6時にデータ品質の点検SQLを流すタスクを作り、RESUMEする
  5. 閲覧専用ロールを作り、MART だけを見せる
  6. ここまでのSQLを1つのファイルにまとめ、Gitに置く(2回実行しても壊れないことを確認する)

実務でよく使うコマンド早見表

名前と役割を知っておくだけで、調べる速度が変わります
やりたいことコマンド学ぶ時期
今の状態を確認するSELECT CURRENT_ROLE(), CURRENT_WAREHOUSE(), CURRENT_DATABASE()STEP 0
一覧を見るSHOW TABLES / SHOW WAREHOUSES / SHOW TASKS / SHOW GRANTSSTEP 1
列の定義を見るDESC TABLE 名前STEP 0
ロールを切り替えるUSE ROLE SYSADMINSTEP 1
ウェアハウスを止めるALTER WAREHOUSE X SUSPENDSTEP 1
結果からテーブルを作るCREATE TABLE X AS SELECT ...(CTAS)STEP 5
あれば更新・なければ追加MERGE INTO ... USING ... ON ...STEP 5
ファイルを取り込むCOPY INTO テーブル FROM @ステージSTEP 6
取り込みエラーを調べるTABLE(VALIDATE(T, JOB_ID => '_last'))STEP 6
取り込み履歴TABLE(INFORMATION_SCHEMA.COPY_HISTORY(...))STEP 6
JSONから値を取るPAYLOAD:user.name::STRINGSTEP 7
配列を行に開くLATERAL FLATTEN(INPUT => 列)STEP 7
最新1件だけ取るQUALIFY ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ... DESC) = 1STEP 8
前月比を出すLAG(値) OVER (ORDER BY 年月)STEP 8
過去の状態を見るSELECT * FROM T AT(OFFSET => -300)STEP 9
消したものを戻すUNDROP TABLE TSTEP 9
一瞬で複製するCREATE TABLE T2 CLONE TSTEP 9
定期実行するCREATE TASK ... SCHEDULE = 'USING CRON ...'ALTER TASK ... RESUMESTEP 10
差分を拾うCREATE STREAM S ON TABLE TSTEP 10
自動更新される集計表CREATE DYNAMIC TABLE ... TARGET_LAG = '10 minutes'STEP 10
権限を与えるGRANT SELECT ON FUTURE TABLES IN SCHEMA S TO ROLE RSTEP 11
使いすぎを止めるCREATE RESOURCE MONITOR ... TRIGGERS ON 100 PERCENT DO SUSPENDSTEP 11
コストを調べるSNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORYSTEP 11
重いクエリを探すSNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORYSTEP 11
ファイルのSQLを実行EXECUTE IMMEDIATE FROM @リポジトリ/path.sqlSTEP 13

※ すべてを覚える必要はありません。「こういうことができる仕組みがある」と知っていることが重要で、書き方はそのつど公式ドキュメントを見れば十分です。Snowflakeの公式ドキュメントは日本語版も整備されており、検索窓にコマンド名を入れるのがいちばん速い調べ方です。

お金の感覚をつかむ

「これを実行するといくらか」を見積もれるようになる
やること目安ひとこと
XSサイズで10分の集計約0.17クレジット($0.5前後)学習中はこの程度。気にしなくてよい
XSサイズを1日つけっぱなし24クレジット($70前後)自動停止を忘れたときの損害
Lサイズを1日つけっぱなし192クレジット($570前後)これが数日続くと事故になる
1TBを1か月保管$20〜25程度圧縮後の量で計算される。安い
結果キャッシュに当たったクエリ0ウェアハウスが起動しないため無料
差分がないときのタスク0WHEN SYSTEM$STREAM_HAS_DATA() を付けた場合

※ 金額は1クレジット=$3として計算した概算です。実際の単価はエディション・クラウド・リージョン・契約によって変わります。正確な数字は Admin › Cost Management で確認してください。

請求で失敗しないための3か条 ① すべてのウェアハウスに AUTO_SUSPEND = 60 を設定する ② リソースモニターで上限と通知を設定する ③ 週に1回 Cost Management を開く。この3つだけで、Snowflakeの「思わぬ高額請求」のほぼすべてを防げます。

3か月の学習プラン例

1日1時間、週5日で進めた場合の目安
時期やることその週の到達目標
1週目STEP 0〜1トライアルを作り、自分のウェアハウスとDBを持つ
2〜3週目STEP 2〜3SELECTと集計で、月別・分類別の表を作れる
4〜5週目STEP 4〜5JOINとCTEが書ける。自分でテーブルを設計できる
6〜7週目STEP 6手元のCSVを取り込んで集計できる
8週目STEP 7〜8JSONを扱い、ランキングと前月比を出せる
9週目STEP 9+復習Time Travelとクローンで、安全に作業できる
10〜11週目STEP 10毎朝自動で更新される集計表を作る
12〜13週目STEP 11権限を設計し、コストと性能を説明できる
14週目〜STEP 12〜13Python・Gitとつなぎ、卒業課題を完成させる
続けるためのコツ自分の仕事のデータを使う。サンプルデータだけで進めると、途中で必ず飽きます ② 完璧に理解してから次へ進もうとしない。8割わかったら先に進み、必要になったときに戻るほうが結局は速い ③ うまくいったSQLは必ずファイルに保存して、Gitに置く。3か月後の自分が必ず助かります。

用語集

調べものの途中で出てきたら、ここに戻ってください
ウェアハウス warehouse
SQLを実際に計算する機械。動いた秒数だけ課金される。データの置き場ではないので注意。
クレジット credit
ウェアハウスの利用量を数える単位。XSサイズなら1時間で1クレジット。
ロール role
権限のまとまり。人ではなくロールに権限が付き、人はロールを着替えて使う。
スキーマ schema
データベースの中の仕切り。パソコンのフォルダに近い。
ステージ stage
取り込む前・書き出した後のファイルを置く場所。内部(Snowflake内)と外部(S3など)がある。
COPY INTO
ステージのファイルをテーブルに流し込む命令。同じファイルは64日間、二重に取り込まれない。
Snowpipe
ファイルが置かれたら自動で取り込む仕組み。継続的なデータ連携に使う。
VARIANT
JSONなど、形の決まっていないデータをそのまま入れられる型。
FLATTEN
入れ子の配列を1件1行に展開する関数。JSON集計の起点。
QUALIFY
ウィンドウ関数の結果をそのまま絞り込めるSnowflake独自の句。最新1件の抽出に使う。
CTE WITH句
クエリの途中経過に名前を付ける仕組み。長いSQLを上から読める形に分割できる。
Time Travel
過去の状態のテーブルを参照・復元できる機能。既定は1日、Enterprise以上で最大90日。
Fail-safe
Time Travel終了後の7日間、Snowflake側だけが復旧できる最後の保険。自分では使えない。
ゼロコピークローン clone
実データを複製せずにテーブルやDBのコピーを作る機能。一瞬で終わり、費用もほぼかからない。
マイクロパーティション
テーブルが自動で分割される小さな塊。この単位で読み飛ばし(プルーニング)が起こる。
プルーニング pruning
WHERE条件に関係のない塊を読まずに済ませること。速さと安さの源。
スピル spilling
計算がメモリに収まらず、ディスクにあふれること。Query Profileで見つけたら要改善。
ストリーム stream
テーブルの変更差分だけを取り出す仕組み。タスクと組み合わせて増分処理に使う。
タスク task
SQLを決まった時刻や間隔で実行する仕組み。作成直後は停止状態なのでRESUMEが必要。
動的テーブル dynamic table
「作り方(SELECT)」と「許せる遅れ」を書くだけで、自動で更新され続けるテーブル。
Snowpark
PythonやJavaの書き方で、処理をSnowflake内で実行する仕組み。大量データを手元に落とさずに済む。
ACCOUNT_USAGE
クエリ履歴・コスト・権限などの監査情報が入ったスキーマ。最大1年分。反映に数十分〜数時間の遅れがある。

よくある質問

学び始めるときに、多くの人が迷うこと
SQLの経験がまったくなくても始められますか

始められます。このページのSTEP 2〜4が、SQLそのものの入門になっています。Snowflakeは環境構築が不要で、ブラウザだけで書いてすぐ結果が見られるため、むしろSQLの学習環境として優れています。Excelの関数を使ったことがある方なら、考え方は地続きです。ただし、STEP 4のJOINは山場です。ここだけは時間をかけてください。

お金がかかるのが心配です。無料で学べますか

トライアル(30日間・$400相当)の範囲で、このページの内容はすべて学べます。使い切ることはまずありません。心配なら、STEP 1で作るウェアハウスをX-Smallのまま、AUTO_SUSPEND = 60にし、STEP 11のリソースモニターを先に設定してください。それでも不安なら、Snowflakeが提供する無料の学習環境(Quickstartsやハンズオン)から始める方法もあります。なお、トライアル期間が終わってもクレジットカードを登録しなければ課金は発生しません(アカウントは凍結されます)。

BigQueryやRedshift、Databricksとの違いは何ですか

いずれもクラウドのデータ基盤で、できることは大きく重なります。Snowflakeの特徴は、特定のクラウドに縛られない(AWS/Azure/GCPのどれでも動く)こと、ストレージと計算の分離が徹底していて費用の見通しが立てやすいこと、そしてSQLだけで完結する範囲が広いことです。BigQueryはGoogle環境との相性、Databricksは機械学習や非構造化データに強みがあります。学ぶ価値という点では、どれか1つを深くやれば他への移行は容易です。SQLとデータ設計の考え方は共通だからです。

SnowPro認定資格は取るべきですか

実務に入る前の目標としては良い教材です。SnowPro Coreが入門で、このページのSTEP 0〜11の範囲がほぼ試験範囲に対応します。ただし、資格があるから任せてもらえるわけではありません。「自分で取り込んで、自動で回して、権限とコストを説明できる」ものを1つ作った経験のほうが、採用でも社内でも評価されます。資格は、学習の抜け漏れを埋める道具として使うのがおすすめです。

どのくらいで実務に使えるようになりますか

「与えられたテーブルから必要な集計を出す」レベルなら、SQL未経験でも毎日1時間で1〜2か月です(STEP 8まで)。「データの取り込みから自動化・権限設計まで任される」レベルとなると、3〜6か月を見ておくのが現実的です。ただしこれは学習時間ではなく、実際に動かした回数で決まります。自分の職場のデータを1つ、最初から最後まで通すのが最短です。

データがまだ手元にありません。何で練習すればよいですか

アカウントに最初から入っている SNOWFLAKE_SAMPLE_DATA が、そのまま数億行規模の練習台になります。それに飽きたら、政府統計のポータル(e-Stat)や自治体のオープンデータからCSVを取ってきて、STEP 6の取り込みを練習するのがおすすめです。自分が結果に興味を持てるデータを選ぶことが、いちばん続くコツです。家計簿やゲームの記録でも構いません。

間違って本番のテーブルを消してしまったら

まず落ち着いて、それ以上の操作をやめてください。Snowflakeには Time Travel があり、既定でも過去1日分は戻せます(STEP 9)。UNDROP TABLE でテーブルごと復活し、AT(OFFSET => -300) で5分前の中身を取り出せます。やらかしたクエリのIDを Query History で控えて、BEFORE(STATEMENT => '...') を使うのが最も確実です。Time Travelの期間を過ぎていた場合は、7日間のFail-safeが最後の望みですが、こちらはSnowflakeのサポートへの依頼が必要です。

Pythonも学ぶべきですか

SQLだけでも実務は十分に務まります。そのうえでPythonができると、「集計はSnowflake、加工と資料化はPython」という組み合わせが使えるようになり、任される範囲が一段広がります(STEP 12)。順番としては、まずSnowflakeのSTEP 8まで進み、SQLで手が届かない場面に出会ってからPythonに進むのが効率的です。Python入門ページも同じ構成で用意しています。

📘 これから学ぶ方・社内で導入を検討している方へ

このページは、データ分析やデータ基盤の担当になった社会人の方、およびデータの仕事に興味のある高校生・大学生の方を想定して構成しています。前半(STEP 0〜5)はSQLの入門でもあるため、プログラミング未経験の方でも取り組めます。後半は職務として運用する場面を想定しているので、必要になったときに戻ってくる資料としてお使いください。

社内で導入を検討されている場合、最初の関門は技術ではなく「誰がコストと権限に責任を持つか」です。Snowflakeは使った分だけ課金される仕組みのため、便利さのあまり誰もが好きなサイズのウェアハウスを起動できる状態にすると、費用が予想を超えます。逆に、STEP 11のリソースモニターと自動停止、そして用途別のウェアハウス分割を最初に決めておけば、費用は驚くほど安定します。技術検証と同時に、この運用ルールを決めてください。

学習を人に任せる場合も、最初の1回だけはアカウント作成とロールの説明に付き添うことをおすすめします。Snowflakeでつまずく箇所は文法よりも「権限が足りない」「ウェアハウスが選ばれていない」といった環境側の問題に集中しており、これは知っていれば10秒で解決し、知らなければ半日を溶かします。

データを扱う仕事には、技術とは別に守るべき約束があります。個人情報や取引先の情報を、必要のない人が見られる状態にしないこと、持ち出さないこと、本番のデータを検証目的で勝手にコピーしないことです。Snowflakeはクローンが一瞬でできるぶん、この線引きが甘くなりがちです。技術的にできることと、してよいことは別である、という点は、繰り返し確認していただければと思います。