🐿 こども経済新聞 Snowflake入門SQL入門Python入門Git・GitHub入門
段階
0 / 14 ステップ

まず、全体の地図

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

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

段階ステップ身につくこと目安この段階を終えると
準備 STEP 0〜1 無料アカウント作成、ノートブックの操作、レイクハウスの仕組みと課金、Unity Catalogの階層 3〜7日 自分のワークスペースで安全にコードを実行できる
基礎 STEP 2〜5 SELECT、集計、結合とCTE、Delta Lakeでのテーブル作成とMERGE 3〜6週間 手元のデータを自力で集計し、テーブルを育てられる
応用 STEP 6〜9 ファイルの取り込み、PySpark、JSONとウィンドウ関数、Time Travelと性能改善 4〜8週間 外部データを取り込み、Databricksらしい機能を使える
実務 STEP 10〜13 メダリオン設計とパイプライン自動化、権限設計、コストと性能、チーム開発と可視化 1〜2か月 データ基盤の運用を任せてもらえる

※ 目安は1日1時間ほど学習した場合です。SQLの経験がある方は STEP 2〜4 を数日で通過できます。まったく初めての方は、STEP 2〜4 にいちばん時間をかけてください。ここが土台です。Pythonが初めての方は、STEP 7 に入る前にPython入門ページを1周しておくと格段に楽になります。

先に結論 ── Databricksでいちばん大事な考え方は「データはあなたのクラウドのファイル置き場にただのファイルとして置かれ、Databricksはそれを読み書きする計算機とカタログを提供している」ことです。 テーブルの正体は Parquet というファイルの束と、その履歴を記した Delta Lake のログです。 置いておくだけならほとんどお金はかかりません。お金がかかるのは、計算機(コンピュート)を動かした時間です。 この1点さえ体に入れば、Databricksの設計も課金も、驚くほど素直に理解できます。

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

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

STEP 0 準備

Databricksとは何か。無料で始めて最初のノートブックを動かす

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

Databricksとは何をするものか

Databricksは、クラウド上のデータ分析プラットフォームです。もともとは大量データを分散処理する Apache Spark というソフトを作った研究者たちが立ち上げた会社で、いまは「データを置く・整える・集計する・機械学習する・AIを作る」までを1つの画面で行える基盤になっています。

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

道具得意なこと限界
Excel / スプレッドシート手元の数千行を、目で見ながら加工する行数が増えると重い。人ごとにファイルが分裂する
MySQL / PostgreSQLアプリの裏側でデータを読み書きする(業務システム用)大量データの集計は苦手。サーバー管理が必要
Snowflake / BigQuery表形式データの集計・共有。SQLだけで完結する画像・音声・機械学習は別の道具が要ることが多い
Databricks表形式もファイルも扱える。SQLとPythonの両方が使え、機械学習・AIまで同じ場所でできるできることが多いぶん、最初は覚えることが多い

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

レイクハウスという考え方

Databricksを理解する鍵は レイクハウス(Lakehouse)という言葉です。これは、次の2つを1つにまとめたものを指します。

  • データレイク ── CSVでも画像でもログでも、とりあえず何でも安く置いておける「湖」。ただし、置いただけでは信頼できる集計に使いにくい。
  • データウェアハウス ── きれいに整えた表を高速に集計できる「倉庫」。ただし、整える手間がかかり、画像などは扱えない。

従来はこの2つを別々に持ち、湖から倉庫へデータをコピーして使っていました。レイクハウスは、安いファイル置き場(湖)の上に、倉庫の信頼性を直接乗せてしまおうという考え方です。それを実現している技術が、次のステップ以降でずっと登場する Delta Lake です。

1. アカウントを作る

Databricksの始め方は2通りあります。学習目的なら、迷わず「Free Edition」を選んでください。

種類費用特徴向いている人
Free Edition
(無料版)
無料・期限なしクレジットカード不要。サーバーレスのノートブックとSQL、Unity Catalogが使える。使用量に上限があり、業務利用は不可これから学ぶ人のほぼ全員
無料トライアル14日間無料
(クラウド費用は自己負担)
AWS / Azure / Google Cloud の自分のアカウントに接続して使う。実務と同じ構成を体験できる会社での導入を検討している人
有料プラン従量課金Premium / Enterprise。権限管理や監査などの機能が付く実際に業務で使う組織

Free Editionは databricks.com/learn/free-edition から、メールアドレスだけで登録できます。登録するとワークスペース(作業場所)のURLが発行され、以後はそこにログインして使います。このURLはブックマークしてください。

Free Editionでどこまで学べるか このページのSTEP 0〜11の大半は、Free Editionのまま学習できます。クラウドのストレージ(S3など)に直接つなぐ操作や、大きなクラスターを使う操作は体験できませんが、考え方とコードはまったく同じです。会社で使うときに戸惑うことはありません。

2. 画面の見方

ログインすると左側にメニューが並びます。最初に覚える場所は5つだけです。

場所役割使う場面
Workspaceノートブックやファイルを置く自分のフォルダいちばん長く居る場所。ここで学習を進める
SQL EditorSQLだけを書いて実行する画面集計だけをしたいとき。BIツールに近い使い心地
Catalogカタログ・スキーマ・テーブルの一覧どんなデータがあるかを探すとき
Compute計算機(クラスター・SQLウェアハウス)の一覧起動・停止、サイズの確認
Jobs & Pipelines自動実行の設定(ワークフロー)STEP 10で使う。毎朝自動で回す仕組み

※ 画面の名称や配置は更新されることがあります。名前が少し違っても、この5つの役割は変わりません。「書く場所(Workspace)」「探す場所(Catalog)」「動かす機械(Compute)」の3つが分かれている、と覚えてください。

3. はじめてのノートブック

左メニューの「+ New」→「Notebook」で新しいノートブックを作ります。ノートブックとは、文章とコードと実行結果を上から順に並べて書ける紙のことです。1つのかたまりをセルと呼び、セルごとに実行します。実行は Shift + Enter です。

画面右上でコンピュート(計算機)を選ぶ欄があります。Free Editionでは「Serverless」が自動で選ばれます。数秒待つと使える状態になります。まず、次のPythonを1つ目のセルに貼り付けて実行してください。

ノートブック(Python)
print("はじめてのDatabricks")

# 今どのバージョンのSparkが動いているか
print(spark.version)
実行結果(例)
はじめてのDatabricks
4.0.0

結果がセルの下に表示されれば成功です。spark という変数は、あなたが何も書かなくても最初から用意されているSparkへの入口です。Databricksのノートブックでは常に使えます。

4. 同じノートブックでSQLも書く

Databricksのノートブックは、セルの先頭に「マジックコマンド」を書くと、そのセルだけ別の言語になります。これがDatabricksの大きな特徴です。SQLの人とPythonの人が同じファイルで作業できます。

マジックコマンドそのセルの言語使う場面
%sqlSQL集計、テーブル作成。いちばん使う
%pythonPython既定の言語がSQLのときに戻す
%md文章(Markdown)見出しやメモを書く。人に渡す資料になる
%shシェルコマンドまれに使う。ファイル操作の確認など
%pipライブラリの追加%pip install openpyxl のように使う

2つ目のセルに、次のSQLを貼り付けて実行します。

ノートブック(SQLセル)
%sql
SELECT
    current_catalog()  AS 今のカタログ,   -- データの置き場所(大分類)
    current_schema()   AS 今のスキーマ,   -- その中の仕切り
    current_user()     AS 自分,           -- ログインしている自分
    current_timestamp() AS 今;
実行結果(例)
今のカタログ  今のスキーマ  自分                    今
workspace     default      ueki@example.com        2026-08-14 09:12:33.412

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

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

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

サンプルデータをのぞく
%sql
-- ニューヨークのタクシー乗車記録(練習用の定番データ)
SELECT * FROM samples.nyctaxi.trips LIMIT 10;

-- 何行あるか
SELECT count(*) AS 行数 FROM samples.nyctaxi.trips;
実行結果(例)
行数
21932

もう1つ、もっと大きなサンプルもあります。こちらは数百万行あります。

大きいほうのサンプル
%sql
-- どんなテーブルが入っているかを見る
SHOW TABLES IN samples.tpch;

SELECT count(*) AS 注文件数 FROM samples.tpch.orders;
実行結果(例)
注文件数
1500000

150万行の件数を数えるのに、おそらく数秒しかかかっていないはずです。この速さが、Databricksを使う理由のひとつです。

最初にやっておくこと ノートブックの右上にある言語表示(既定はPython)と、タイムゾーンの扱いを確認しておきましょう。Databricksの内部の時刻は基本的にUTC(世界標準時)で扱われます。日本時間で日付を集計したい場合の書き方は STEP 3 で説明します。「集計結果の日付が1日ずれる」の原因はほぼこれです。
練習問題

samples.nyctaxi.trips テーブルに、どんな列があるかを調べてください。また、乗車距離(trip_distance)がいちばん長かった記録の距離を出してください。

解答を見る
answer.sql
%sql
-- 列の一覧と型を見る
DESCRIBE TABLE samples.nyctaxi.trips;

-- いちばん長い距離
SELECT max(trip_distance) AS 最長距離 FROM samples.nyctaxi.trips;

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

STEP 1 準備

仕組みと料金 ── コンピュート・DBU・Unity Catalogの3階層

目安 2〜5日
このステップの到達点 ── なぜDatabricksが速いのか、どこにお金がかかるのかを説明できる。自分の練習用カタログとスキーマを作り、コンピュートの自動停止を設定できる。ここを飛ばすと、後で高額請求と権限エラーで必ずつまずきます。

データと計算機が分かれている

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

役割お金
ストレージ層
(クラウドのファイル置き場)
データそのものがParquetファイルとして置かれる場所。S3 / ADLS / GCSなど容量 × 期間(月におよそ$20〜25/TB。安い)
コンピュート層
(クラスター・SQLウェアハウス)
実際に計算する機械。必要なときだけ起動する動いた時間 × 台数 × 種類(ここが料金の大半)
コントロールプレーン
(画面・カタログ・ジョブ管理)
ノートブックの保存、権限の確認、ジョブの管理プラン料金に含まれる

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

なぜ速いのか Databricksは、1台の大きなコンピューターで頑張るのではなく、複数台のコンピューターに仕事を分けて同時に走らせます(分散処理)。150万行を10台で分ければ1台あたり15万行です。この仕組みがApache Sparkで、さらにDatabricksは Photon という高速な実行エンジンを載せて、SQLの集計を数倍速くしています。

コンピュートには3つの種類がある

Databricksで最初につまずくのがここです。用途によって使うべき計算機が違い、値段も倍以上違います

種類何のため単価の目安ひとこと
汎用コンピュート
(All-purpose)
ノートブックを人が触りながら使う高い(DBUあたり約$0.4〜0.55)開発・探索用。放置が事故のもと
ジョブコンピュート
(Jobs)
決まった処理を自動実行する安い(DBUあたり約$0.15)ジョブ実行時だけ起動し、終われば自動で消える
SQLウェアハウス
(SQL Warehouse)
SQLの集計・BIツールからの接続中〜高(サーバーレスで約$0.7)起動が数秒。SQLだけならこれが快適

※ 単価はクラウド・リージョン・プランによって変わります。ここに書いた数字は感覚をつかむための概算です。さらに、サーバーレス以外を使う場合は、これに加えてクラウド事業者へのVM代が別途かかります。正確な単価は公式の価格ページと、STEP 12で扱うシステムテーブルで確認してください。

いちばん多いムダ遣い 「毎晩の定期処理を、汎用コンピュートで動かしている」です。ジョブコンピュートに変えるだけで、同じ処理が3分の1程度の費用になります。STEP 10で必ず切り替えてください。

DBUという単位

Databricksの利用量は DBU(Databricks Unit)という単位で数えます。「1時間あたり何DBU消費する機械か」が種類とサイズで決まっていて、DBU数 × 単価 × 動いた時間が請求額になります。電気料金の「kWh」と同じ考え方です。

料金の考え方
# ざっくりした計算式
料金 = DBU数(1時間あたり) × DBU単価 × 起動していた時間 (+ クラウドのVM代)

# 例:小さめのSQLウェアハウス(サーバーレス, 約4DBU/時)を1時間使うと
4 DBU × $0.70 = 約 $2.8 / 時

# 例:同じものを止め忘れて1日つけっぱなしにすると
4 DBU × $0.70 × 24時間 = 約 $67 / 日
自動停止を必ず確認する SQLウェアハウスは既定で10分、クラスターは既定で数十分の無操作で自動停止します。学習中はこれを最短(SQLウェアハウスなら5〜10分)に設定してください。「昨日つけっぱなしだった」を防ぐ、いちばん効くコスト対策がこれです。Free Editionでは自動的にサーバーレスが使われ、使い終わればすぐ止まるため、この心配はほとんどありません。

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

Databricksのテーブルは、カタログ › スキーマ › テーブル の3階層のどこかにあります。この仕組み全体を Unity Catalog(ユニティカタログ)といい、権限管理・検索・履歴追跡のすべてがここに集まっています。フルネームは sales_dev.bronze.orders のようにピリオドでつなぎます。

階層パソコンで言うと実務での分け方の例
カタログ catalogドライブ環境や部門で分ける(dev / prodsales / hr
スキーマ schemaフォルダ加工の段階で分ける(bronze / silver / gold
テーブル / ボリュームファイルテーブル=表データ、ボリューム=CSVや画像などのファイル

ボリューム(Volume)という耳慣れないものが出てきました。これはカタログの中に作れるファイル置き場です。CSVやPDFや画像を置いておき、/Volumes/カタログ名/スキーマ名/ボリューム名/ファイル名 というパスで読み書きします。STEP 6で実際に使います。

自分の作業場所を作る

いきなり本番のような構成を作る必要はありません。まずは学習用のスキーマとボリュームを作ります。以下をノートブックのSQLセルに貼って、上から順に実行してください。

setup.sql
%sql
-- 練習用のカタログ(作れない環境なら、この行は飛ばして既定の workspace を使う)
CREATE CATALOG IF NOT EXISTS learn;

-- 練習用のスキーマ(フォルダ)
CREATE SCHEMA IF NOT EXISTS learn.sales
  COMMENT '学習用。いつ消してもよい';

-- ファイルを置く場所(ボリューム)
CREATE VOLUME IF NOT EXISTS learn.sales.files
  COMMENT '練習用のCSVを置く';

-- これから使う場所を宣言する
USE CATALOG learn;
USE SCHEMA sales;

SELECT current_catalog(), current_schema();
実行結果
current_catalog()  current_schema()
learn              sales
カタログが作れないと言われたら 権限がない環境(会社のワークスペースなど)では CREATE CATALOG は管理者しか実行できません。その場合は、最初から用意されている workspace カタログの中に自分用のスキーマを作ってください(CREATE SCHEMA workspace.自分の名前;)。以降のコードは、カタログ名を読み替えれば同じように動きます。

誰が何をできるか(権限の考え方)

Unity Catalogでは、権限は上の階層から下へ継承されます。カタログに対して読み取り権限を渡せば、その中のスキーマとテーブルすべてが読めるようになります。細かい設計はSTEP 11で行いますが、いまは次の3語だけ覚えてください。

権限の基本3語
%sql
-- 誰に何を許すか
GRANT SELECT ON TABLE learn.sales.orders TO `analyst@example.com`;

-- 今どんな権限が付いているか
SHOW GRANTS ON TABLE learn.sales.orders;

-- 取り消す
REVOKE SELECT ON TABLE learn.sales.orders FROM `analyst@example.com`;
「所有者」という考え方 作成した人(または指定したグループ)が、そのテーブルのオーナーになります。オーナーは何でもできます。実務では、個人ではなくグループをオーナーにするのが鉄則です。個人をオーナーにすると、その人が退職した瞬間に誰も触れないテーブルが生まれます。
練習問題

① いま自分のワークスペースにどんなカタログがあるかを一覧してください。② samples カタログの中にどんなスキーマがあるかを調べてください。

解答を見る
answer.sql
%sql
SHOW CATALOGS;

SHOW SCHEMAS IN samples;

-- テーブルの詳しい情報(保存場所・所有者・作成日)
DESCRIBE TABLE EXTENDED samples.nyctaxi.trips;

DESCRIBE TABLE EXTENDED は、テーブルの実体がクラウドのどこに置かれているかまで教えてくれます。トラブル調査でよく使います。

STEP 2 基礎

SELECT ── データを取り出す

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

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

SQLは、手順ではなく結果の条件を書く言語です。「乗車記録から、料金が50ドル以上の行を、新しい順に10件」と日本語で言えれば、それがほぼそのままSQLになります。文の順番は、いつもこの形です。

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

まずはサンプルデータで動かします。samples.nyctaxi.trips はニューヨークのタクシーの乗車記録です。

select_basic.sql
%sql
SELECT
    tpep_pickup_datetime  AS 乗車時刻,
    trip_distance         AS 距離,
    fare_amount           AS 料金,
    pickup_zip            AS 乗車地の郵便番号
FROM samples.nyctaxi.trips
WHERE fare_amount >= 50          -- 料金が50以上の行だけ
ORDER BY fare_amount DESC        -- 料金の大きい順(DESC=降順)
LIMIT 10;
実行結果(例)
乗車時刻              距離   料金   乗車地の郵便番号
2016-02-13 21:20:00  35.09  275.0  10012
2016-02-17 15:04:00  30.62  150.0  10022
...

SQLの予約語(SELECT など)は大文字でも小文字でも動きます。このページでは、命令は大文字、列名は小文字で書き分けています。読みやすさのための習慣で、実務でもよく使われる書き方です。

WHERE でよく使う条件

書き方意味
= <> > <=等しい/等しくない/大小fare_amount > 100
AND OR NOT条件のつなぎ距離 > 5 AND 料金 < 20
BETWEEN a AND ba以上b以下trip_distance BETWEEN 1 AND 3
IN (…)いずれかに一致pickup_zip IN (10001, 10002)
LIKE '%あ%'文字の部分一致(%は任意の文字列)name LIKE '株式会社%'
IS NULL / IS NOT NULL空っぽかどうかdropoff_zip IS NULL
NULLは「=」では比べられない 空っぽ(NULL)は「値がない」という状態なので、= NULL と書いても永久に一致しません。必ず IS NULL を使います。「なぜか0件になる」の原因の半分はこれです。

列に計算をさせる

SELECTには、既存の列だけでなく計算式も書けます。名前を付けるには AS を使います。

calc.sql
%sql
SELECT
    trip_distance                          AS 距離,
    fare_amount                            AS 料金,
    round(fare_amount / trip_distance, 1)  AS 単価,          -- 1マイルあたり
    CASE
        WHEN trip_distance < 1  THEN '近距離'
        WHEN trip_distance < 5  THEN '中距離'
        ELSE '長距離'
    END                                    AS 距離区分
FROM samples.nyctaxi.trips
WHERE trip_distance > 0
LIMIT 20;

CASE WHEN は「もし〜なら」を書く仕組みで、Excelの IF 関数にあたります。上から順に判定し、最初に当てはまったところで止まります。実務でいちばん出番の多い構文なので、ここで慣れておいてください。

よく使う関数

やりたいこと関数
四捨五入round(値, 桁)round(3.14159, 2) → 3.14
空欄を別の値にcoalesce(a, b)coalesce(備考, 'なし')
文字をつなぐconcat(a, b) / a || bconcat(姓, ' ', 名)
大文字・小文字upper() / lower()lower(メール)
前後の空白を消すtrim()trim(会社名)
区切って取り出すsplit_part(文字, 区切り, 何番目)split_part(メール, '@', 2)
型を変えるcast(x AS INT) / try_cast()try_cast(金額 AS DOUBLE)
try_ が付いた関数を覚えておく cast('あいう' AS INT) は失敗してエラーになりますが、try_cast は失敗するとNULLを返して処理を続けます。汚れたデータを扱う実務では、こちらのほうが役に立つ場面が多いです。

結果を見る・保存する

実行結果の表の上には、いくつかのボタンが並んでいます。「+」からグラフを追加すると、その場で棒グラフや折れ線グラフになります。ダウンロードボタンでCSVとして手元に落とすこともできます。

ノートブックは資料になる %md のセルに見出しと説明を書き、その下に集計とグラフを並べておけば、そのまま報告資料として人に渡せます。「分析の過程が全部残っている資料」は、Excelでは作りにくいものです。これがノートブックを使う大きな利点です。
練習問題

samples.nyctaxi.trips から、①距離が10マイル以上、②料金が空欄でない、という2つの条件を満たす行を、距離の長い順に5件表示してください。表示する列は、乗車時刻・距離・料金・1マイルあたりの単価(小数第1位まで)の4つです。

解答を見る
answer.sql
%sql
SELECT
    tpep_pickup_datetime                  AS 乗車時刻,
    trip_distance                         AS 距離,
    fare_amount                           AS 料金,
    round(fare_amount / trip_distance, 1) AS 単価
FROM samples.nyctaxi.trips
WHERE trip_distance >= 10
  AND fare_amount IS NOT NULL
ORDER BY trip_distance DESC
LIMIT 5;

ORDER BY は必ず WHERE より後ろ、LIMIT は最後です。この順番は決まっていて、入れ替えると構文エラーになります。

STEP 3 基礎

集計と日付 ── GROUP BY で「まとめる」

目安 1〜2週間
このステップの到達点 ── 月別・分類別の売上表を自力で作れる。日付の扱いとタイムゾーンのずれを理解し、正しい期間で集計できる。

集計は「どの単位でまとめるか」を決めること

集計とは、たくさんの行をある単位でまとめて、1行にすることです。「月ごとの売上」なら単位は月、「店舗ごとの客数」なら単位は店舗です。この単位を書くのが GROUP BY です。

group_by.sql
%sql
SELECT
    pickup_zip           AS 乗車地,
    count(*)             AS 件数,
    round(avg(fare_amount), 1)  AS 平均料金,
    round(sum(fare_amount), 0)  AS 合計料金
FROM samples.nyctaxi.trips
GROUP BY pickup_zip          -- この単位でまとめる
HAVING count(*) >= 100       -- まとめた後の絞り込み
ORDER BY 合計料金 DESC
LIMIT 10;
実行結果(例)
乗車地  件数   平均料金  合計料金
10001   1523   13.4      20408
10002   1147   12.1      13879
...
関数意味注意
count(*)行数NULLの行も数える
count(列)その列が空でない行数count(*)との差が「欠損数」
count(DISTINCT 列)種類の数「何人の顧客が買ったか」に使う
sum() avg()合計・平均NULLは無視される
min() max()最小・最大日付にも使える(初回・最終)
percentile(列, 0.5)中央値平均は外れ値に引っぱられる。両方見る
WHEREとHAVINGの違い WHEREまとめる前の行を絞り、HAVINGまとめた後の結果を絞ります。「件数が100件以上のグループだけ」は、まとめないと分からないので HAVING です。ここを取り違えるとエラーになります。

日付でまとめる

実務でいちばん多い集計は「月別」です。日付から月を取り出すには date_trunc を使います。「切り捨てる」という意味で、日付を月初(や年初)に丸めます。

monthly.sql
%sql
SELECT
    date_trunc('MONTH', tpep_pickup_datetime) AS 年月,
    count(*)                                   AS 件数,
    round(sum(fare_amount), 0)                 AS 売上
FROM samples.nyctaxi.trips
GROUP BY 年月           -- ALL と書いてもよい(後述)
ORDER BY 年月;
実行結果(例)
年月                 件数   売上
2016-01-01 00:00:00  10906  138721
2016-02-01 00:00:00  11026  140455
Databricksの便利な書き方:GROUP BY ALL 集計関数ではない列を全部まとめの単位にする、という指定です。列を書き写す手間が消え、書き間違いも減ります。SELECT ... GROUP BY ALL と書くだけです。他のデータベースには無いことが多いので、他社製品に移すコードでは通常の書き方にしてください。

日付を扱う関数

やりたいこと書き方結果の例
今日・今current_date() / current_timestamp()2026-08-14
年・月・日を取り出すyear(d) month(d) day(d)2026 / 8 / 14
月初に丸めるdate_trunc('MONTH', d)2026-08-01
表示の形を変えるdate_format(d, 'yyyy/MM')2026/08
日数を足すdate_add(d, 7)7日後
差を数えるdatediff(終わり, 始め)日数
文字列を日付にto_date('2026-08-14') / try_to_timestamp()日付型

タイムゾーンの落とし穴

Databricksの内部では、時刻は基本的に UTC(世界標準時)で保持されます。日本時間はUTCより9時間進んでいるため、日本時間の朝8時のデータは、UTCでは前日の23時です。日付でまとめると1日ずれます。

timezone.sql
%sql
-- ① セッションのタイムゾーンを日本にする(そのノートブックの間だけ有効)
SET TIME ZONE 'Asia/Tokyo';

-- ② または、変換関数で明示する(こちらが確実)
SELECT
    tpep_pickup_datetime                              AS utcの時刻,
    from_utc_timestamp(tpep_pickup_datetime, 'Asia/Tokyo') AS 日本時間,
    date_trunc('DAY', from_utc_timestamp(tpep_pickup_datetime, 'Asia/Tokyo')) AS 日本時間の日付
FROM samples.nyctaxi.trips
LIMIT 5;
実務での鉄則 「この列は何のタイムゾーンで入っているのか」を、テーブルを作った人に必ず確認してください。分からないまま集計した数字は、月初と月末が丸ごとずれます。テーブルを作る側になったら、列名を created_at_utc のようにして、後の人が迷わないようにしましょう。

縦横の入れ替え(PIVOT)

「行に月、列に商品分類」といったクロス集計は PIVOT で書けます。Excelのピボットテーブルと同じ発想です。

pivot.sql
%sql
SELECT * FROM (
    SELECT
        date_format(o_orderdate, 'yyyy-MM') AS 年月,
        o_orderpriority                      AS 優先度,
        o_totalprice                         AS 金額
    FROM samples.tpch.orders
    WHERE o_orderdate >= DATE'1998-01-01'
)
PIVOT (
    round(sum(金額)/1000, 0)
    FOR 優先度 IN ('1-URGENT', '2-HIGH', '3-MEDIUM')
)
ORDER BY 年月;

※ PIVOTは、列に出したい値をあらかじめ書き並べる必要があります。値が増える可能性のあるものは、素直に縦持ち(GROUP BY)のまま出して、BIツール側で横に開くほうが壊れにくい設計です。

練習問題

samples.tpch.orders を使って、年ごとの注文件数と売上合計を出してください。列は o_orderdate(注文日)と o_totalprice(金額)です。売上は千単位に丸めて、年の古い順に並べてください。

解答を見る
answer.sql
%sql
SELECT
    year(o_orderdate)              AS 年,
    count(*)                       AS 注文件数,
    round(sum(o_totalprice)/1000, 0) AS 売上_千
FROM samples.tpch.orders
GROUP BY ALL
ORDER BY 年;

GROUP BY ALL の代わりに GROUP BY year(o_orderdate)GROUP BY 1(1番目の列)とも書けます。

STEP 4 基礎

結合とCTE ── 複数のテーブルをつなぐ

目安 2週間
このステップの到達点 ── JOINで複数テーブルを正しくつなげる。CTE(WITH句)で長いSQLを読める形に分割できる。ここが最大の山場です。時間をかけてください。

なぜテーブルは分かれているのか

実務のデータは、必ず複数のテーブルに分かれています。注文テーブルには「顧客番号」しか入っておらず、顧客の名前は顧客テーブルにあります。同じ情報を1か所にだけ持つためです(顧客名が変わったとき、1か所直せば済む)。分析するときは、これをつなぎ戻す必要があります。それがJOINです。

join_basic.sql
%sql
SELECT
    c.c_name        AS 顧客名,
    o.o_orderdate   AS 注文日,
    o.o_totalprice  AS 金額
FROM samples.tpch.orders    AS o          -- 左のテーブル
JOIN samples.tpch.customer  AS c          -- 右のテーブル
  ON o.o_custkey = c.c_custkey            -- つなぐ鍵(キー)
WHERE o.o_orderdate >= DATE'1998-01-01'
ORDER BY o.o_totalprice DESC
LIMIT 10;

AS o のように短い別名を付けておくと、以降 o.列名 と書けます。テーブルが3つ4つになるとこれが効いてきます。

JOINの種類

種類結果に残る行使う場面
INNER JOIN(既定)両方にある行だけ注文と、その顧客情報。確実に対応があるとき
LEFT JOIN左は全部残る。右に無ければNULLいちばん使う。「注文していない顧客も含めた一覧」
RIGHT JOIN右が全部残るLEFTで書き直せるので、ほぼ使わない
FULL OUTER JOIN両方の全部2つの名簿の突き合わせ
ANTI JOIN右に無い左の行だけ「まだ買っていない人」を探す
left_join.sql
%sql
-- 注文が1件も無い顧客も含めて、顧客ごとの注文件数を出す
SELECT
    c.c_name                       AS 顧客名,
    count(o.o_orderkey)            AS 注文件数,     -- ← count(*) にしない
    coalesce(sum(o.o_totalprice), 0) AS 売上
FROM samples.tpch.customer AS c
LEFT JOIN samples.tpch.orders AS o
  ON c.c_custkey = o.o_custkey
GROUP BY ALL
ORDER BY 注文件数 ASC
LIMIT 10;
LEFT JOINで count(*) を使わない 注文が無い顧客も、LEFT JOINでは1行(右側が全部NULL)として残ります。count(*) はそれを1件と数えてしまい、注文0件の人が「1件」になります。数えたい対象の列を指定して count(o.o_orderkey) と書けば、NULLは数えられず正しく0になります。

結合で行が増えてしまう問題

初心者が必ずはまるのが、JOINしたら合計金額が実際より増えたという現象です。原因はほぼ一つで、つないだ相手のテーブルに、同じキーの行が複数あったことです。1件の注文に3件の明細があれば、注文金額が3回数えられます。

重複を確かめる
%sql
-- つなぐ前に「キーが重複していないか」を必ず確認する
SELECT
    count(*)                    AS 行数,
    count(DISTINCT o_orderkey)  AS キーの種類数
FROM samples.tpch.orders;
-- この2つが同じなら、o_orderkey は1行につき1つ(安全に結合できる)
結合前チェックを習慣にする ① 結合するキーが重複していないか ② 結合の前後で行数がどう変わったか ③ 結合できなかった行はどれくらいか。この3つを毎回確認すれば、集計を間違えることはほぼ無くなります。これができる人が「データを任せられる人」です。

CTE(WITH句)── 長いSQLを読める形にする

SQLは、書いているうちに入れ子だらけになって読めなくなります。CTEは、途中経過に名前を付けて、上から順に読める形に直す仕組みです。実務のSQLは、ほぼすべてこの形で書かれています。

cte.sql
%sql
WITH 対象注文 AS (
    -- ① まず期間で絞る
    SELECT o_custkey, o_orderkey, o_totalprice
    FROM samples.tpch.orders
    WHERE o_orderdate >= DATE'1998-01-01'
),
顧客別 AS (
    -- ② 顧客ごとにまとめる
    SELECT
        o_custkey,
        count(*)          AS 件数,
        sum(o_totalprice) AS 売上
    FROM 対象注文
    GROUP BY ALL
)
-- ③ 最後に顧客名を付ける
SELECT
    c.c_name  AS 顧客名,
    k.件数,
    round(k.売上, 0) AS 売上
FROM 顧客別 AS k
JOIN samples.tpch.customer AS c ON k.o_custkey = c.c_custkey
ORDER BY k.売上 DESC
LIMIT 10;

①②③の順に読めば、何をしているかが日本語のように分かります。1つのCTEには1つの仕事だけをさせるのがコツです。名前は日本語でも構いません(バッククォートで囲めば空白も使えます)。

集合の演算

書き方意味
UNION ALL2つの結果をそのまま縦に積む(速い。ふつうはこちら)
UNION縦に積んで、重複を消す(重い)
EXCEPT上にあって下に無い行(差分の確認に便利)
INTERSECT両方にある行
データ移行の検算に使える 移行前と移行後のテーブルを EXCEPT で両方向に比べ、どちらも0件なら中身は完全に一致しています。SELECT * FROM 旧 EXCEPT SELECT * FROM 新; と、その逆の2本を流すだけです。実務で本当によく使う技です。
練習問題

samples.tpchorderslineitem(注文明細)を使って、注文1件あたりの平均明細数を出してください。ヒント:先にCTEで注文ごとの明細数を数えてから、その平均を取ります。

解答を見る
answer.sql
%sql
WITH 明細数 AS (
    SELECT
        l_orderkey,
        count(*) AS 明細数
    FROM samples.tpch.lineitem
    GROUP BY ALL
)
SELECT
    round(avg(明細数), 2) AS 平均明細数,
    max(明細数)           AS 最大明細数,
    count(*)              AS 注文数
FROM 明細数;

※ いきなり avg を書こうとすると詰まります。「まず1件あたりの数を出す → その平均を取る」と2段階に分けるのが、集計を組み立てる基本の考え方です。

STEP 5 基礎

Delta Lake ── 自分のテーブルを作って育てる

目安 1〜2週間
このステップの到達点 ── 自分でテーブルを作り、追加・更新・削除ができる。MERGE で「あれば更新・無ければ追加」を1文で書ける。Delta Lakeが何をしてくれているかを説明できる。

Databricksのテーブルの正体

Databricksでテーブルを作ると、裏側では次の2つが作られます。

  • Parquetファイルの束 ── データ本体。列ごとにまとめて圧縮された、集計に強い形式のファイル。
  • _delta_log というフォルダ ── 「いつ、誰が、どのファイルを足した/消した」という取引履歴が全部書かれたログ。

この2つの組み合わせが Delta Lake(デルタレイク)です。ログがあるおかげで、ただのファイルの集まりが、次のような「データベースらしさ」を手に入れます。

できること意味ふつうのCSV置き場だと
ACIDトランザクション途中で失敗しても中途半端な状態が残らない半分だけ書かれたファイルが残る
同時に読み書きできる書き込み中でも、読む人は古い完成版を見られる読んでいる最中に壊れる
UPDATE / DELETE1行だけ直せるファイルを丸ごと作り直すしかない
Time Travel過去のある時点の状態を読める(STEP 9)戻せない
スキーマの管理列の型が違うデータの混入を防げる気づかず混ざる
覚えておくと一生使える一言 ── 「Delta Lakeとは、ファイル置き場に取引履歴を足したもの」。この一言で、Time TravelもMERGEもVACUUMも、後から全部つながって理解できます。なお、Delta Lakeはオープンソースで、Databricks以外でも使えます。

テーブルを作る

STEP 1で作った learn.sales の中に、練習用のテーブルを作ります。

create_table.sql
%sql
USE CATALOG learn;
USE SCHEMA sales;

CREATE TABLE IF NOT EXISTS orders (
    order_id    BIGINT   COMMENT '注文番号',
    customer    STRING   COMMENT '顧客名',
    order_date  DATE     COMMENT '注文日',
    amount      DECIMAL(12,2) COMMENT '金額(円)',
    status      STRING   COMMENT '状態: new / paid / canceled',
    updated_at  TIMESTAMP DEFAULT current_timestamp()
)
COMMENT '学習用の注文テーブル';

DESCRIBE TABLE orders;

CREATE TABLE と書くだけでDelta Lake形式になります(既定の形式です)。USING DELTA と明示的に書くこともできますが、省略して構いません。

よく使う型用途注意
STRING文字。長さ制限なし迷ったらこれ。IDや郵便番号もこれが安全
INT / BIGINT整数件数が多い連番はBIGINT
DECIMAL(p,s)お金金額に DOUBLE を使わない(誤差が出る)
DOUBLE小数(測定値など)誤差が許される場面だけ
DATE / TIMESTAMP日付・日時タイムゾーンに注意(STEP 3)
BOOLEANtrue / falseフラグ列に
ARRAY / STRUCT / MAP / VARIANT入れ子のデータJSONを扱うときに使う(STEP 8)

データを入れる・変える・消す

dml.sql
%sql
-- 追加
INSERT INTO orders (order_id, customer, order_date, amount, status) VALUES
    (1, '山田商店',   DATE'2026-08-01', 12000, 'paid'),
    (2, '鈴木物産',   DATE'2026-08-02',  8400, 'new'),
    (3, '田中工業',   DATE'2026-08-02', 31500, 'new'),
    (4, '山田商店',   DATE'2026-08-05',  4200, 'canceled');

-- 更新(WHEREを忘れると全行が変わる)
UPDATE orders
SET status = 'paid', updated_at = current_timestamp()
WHERE order_id = 2;

-- 削除
DELETE FROM orders WHERE status = 'canceled';

SELECT * FROM orders ORDER BY order_id;
実行結果
order_id  customer  order_date   amount   status  updated_at
1         山田商店   2026-08-01   12000.00  paid    2026-08-14 ...
2         鈴木物産   2026-08-02    8400.00  paid    2026-08-14 ...
3         田中工業   2026-08-02   31500.00  new     2026-08-14 ...
UPDATE / DELETE の前に必ずやること 同じ WHERE 条件で SELECT count(*) を先に実行し、影響する行数を目で確認してください。Delta LakeにはTime Travel(STEP 9)があるので取り返しはつきますが、気づくのが翌週だと手遅れになります。

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

実務でいちばん多いテーブルの作り方です。集計結果をそのまま保存できます。

ctas.sql
%sql
-- 顧客ごとの売上サマリを作る
CREATE OR REPLACE TABLE customer_summary AS
SELECT
    customer               AS 顧客,
    count(*)               AS 件数,
    sum(amount)            AS 売上,
    max(order_date)        AS 最終注文日
FROM orders
GROUP BY ALL;

SELECT * FROM customer_summary;
CREATE OR REPLACE は安全 中身を入れ替えても、テーブルの履歴(Time Travel)は残ります。したがって、間違えて古いデータで作り直しても前の版に戻せます。DROP TABLE してから作り直すより、こちらを使ってください。

MERGE ── 「あれば更新、無ければ追加」

毎日届く更新データを反映する処理は、実務のほぼすべての現場で必要になります。これを1文で書けるのが MERGE です。Databricksでいちばん重要な命令と言ってよいものです。

merge.sql
%sql
-- 今日届いた更新データ(ふだんは取り込んだテーブルを使う)
CREATE OR REPLACE TEMP VIEW 本日分 AS
SELECT * FROM VALUES
    (2, '鈴木物産', DATE'2026-08-02',  9000, 'paid'),      -- 金額が変わった
    (5, '佐藤電機', DATE'2026-08-14', 22000, 'new')        -- 新規
AS t(order_id, customer, order_date, amount, status);

MERGE INTO orders AS 元
USING 本日分     AS 新
  ON 元.order_id = 新.order_id              -- 何をもって「同じ行」とするか
WHEN MATCHED THEN UPDATE SET
    元.amount     = 新.amount,
    元.status     = 新.status,
    元.updated_at = current_timestamp()
WHEN NOT MATCHED THEN INSERT
    (order_id, customer, order_date, amount, status)
    VALUES (新.order_id, 新.customer, 新.order_date, 新.amount, 新.status);

SELECT * FROM orders ORDER BY order_id;
実行結果
order_id  customer  order_date   amount    status
1         山田商店   2026-08-01   12000.00  paid
2         鈴木物産   2026-08-02    9000.00  paid     ← 更新された
3         田中工業   2026-08-02   31500.00  new
5         佐藤電機   2026-08-14   22000.00  new      ← 追加された
MERGEの事故は「キーの重複」から起きる USING 側に同じキーの行が2つあると、「どちらで更新すればよいか決められない」というエラーになります。取り込んだ生データには重複がよくあるので、MERGEの前に重複を1件に絞るのが定石です。STEP 8のウィンドウ関数(QUALIFY row_number() = 1)が、まさにこれに使われます。

削除まで反映したい場合は WHEN NOT MATCHED BY SOURCE THEN DELETE を足します。「送られてきたデータに無い行は、元でも消す」という同期処理になります。強力なぶん危険なので、必ず条件を絞って使ってください。

テーブルを整える小技

table_ops.sql
%sql
-- 列を足す
ALTER TABLE orders ADD COLUMN memo STRING COMMENT '備考';

-- 列名を変える
ALTER TABLE orders RENAME COLUMN memo TO note;

-- 説明を足す(あとで人が読むときに効く)
COMMENT ON TABLE orders IS '学習用の注文テーブル。毎日MERGEで更新される想定';

-- 中身だけ空にする(テーブルは残る)
-- TRUNCATE TABLE orders;

-- テーブルの詳細(保存場所・サイズ・所有者)
DESCRIBE DETAIL orders;
COMMENTを面倒くさがらない 半年後に自分が見返したとき、あるいは他の人が引き継ぐとき、説明のある列とない列では調査時間が10倍違います。Databricksでは、CatalogのUIやAIによる検索でもこの説明文が使われます。書いた分だけ、後で自分が助かります。
練習問題

learn.salescustomers テーブル(列:customerarea)を作り、3件ほどデータを入れてください。② 注文テーブルと結合して、地域ごとの売上を出してください。③ 同じ顧客のデータをもう一度MERGEしても件数が増えないことを確認してください。

解答を見る
answer.sql
%sql
CREATE OR REPLACE TABLE customers (customer STRING, area STRING);

INSERT INTO customers VALUES
    ('山田商店', '関東'), ('鈴木物産', '関西'), ('田中工業', '関東'), ('佐藤電機', '中部');

-- ② 地域ごとの売上
SELECT
    c.area      AS 地域,
    count(*)    AS 件数,
    sum(o.amount) AS 売上
FROM orders AS o
JOIN customers AS c ON o.customer = c.customer
GROUP BY ALL
ORDER BY 売上 DESC;

-- ③ 同じデータをもう一度MERGEしても増えない(べき等)
MERGE INTO customers AS 元
USING (SELECT '山田商店' AS customer, '関東' AS area) AS 新
  ON 元.customer = 新.customer
WHEN MATCHED THEN UPDATE SET 元.area = 新.area
WHEN NOT MATCHED THEN INSERT *;

SELECT count(*) FROM customers;   -- 4件のまま

※ 「何度実行しても結果が同じ」ことをべき等(idempotent)といいます。処理が途中で失敗しても、そのまま流し直せば復旧できるため、自動化する処理はすべてべき等に作るのが原則です。INSERT INTO ではなく MERGE を使う理由がこれです。

STEP 6 応用

データを取り込む ── CSVからテーブルへ

目安 2週間
このステップの到達点 ── 手元のCSVをDatabricksに取り込み、テーブルとして集計できる。ここができると、仕事で使えるかどうかが変わります。取り込みエラーの調べ方まで身につけます。

取り込みの3つの方法

方法向いている場面覚える順
画面からアップロード手元の小さなCSV・Excelを1回だけ入れるまずこれ
COPY INTO置き場にあるファイルを、増えた分だけ繰り返し取り込む2番目
Auto Loader
cloudFiles
ファイルが次々届く継続的な取り込み3番目(STEP 10で本格化)

1. まずは画面から入れてみる

左メニューの「+ New」→「Add or upload data」→「Create or modify table」からCSVをドラッグします。画面上で列名と型を確認し、保存先のカタログ・スキーマを選ぶだけでテーブルができます。

練習用のCSVが手元にない場合は、次のコードでボリュームの中に作れます。

練習用CSVを作る(Python)
csv = """order_id,customer,order_date,amount,status
101,山田商店,2026-08-01,12000,paid
102,鈴木物産,2026-08-02,8400,new
103,田中工業,2026-08-02,31500,new
104,,2026-08-03,5000,new
105,佐藤電機,2026-08-04,abc,new
"""

path = "/Volumes/learn/sales/files/orders_20260804.csv"
dbutils.fs.put(path, csv, overwrite=True)

# 置けたか確認
display(dbutils.fs.ls("/Volumes/learn/sales/files/"))

dbutils は、Databricksのノートブックで最初から使える便利道具の集まりです。dbutils.fs がファイル操作、dbutils.widgets が入力欄の作成(STEP 10で使います)、dbutils.secrets がパスワードの安全な取り扱いです。

わざと104行目に顧客名の空欄105行目に数値でない金額(abc)を入れてあります。実務のCSVは必ず汚れているので、その扱いも一緒に学びます。

2. ファイルの中身をSQLでのぞく

テーブルにする前に、ファイルのまま中身を確認できます。これがDatabricksの気持ちよいところです。

read_files.sql
%sql
-- ファイルを直接SELECTする
SELECT * FROM read_files(
    '/Volumes/learn/sales/files/',
    format  => 'csv',
    header  => true,
    inferSchema => true
);

-- 型を推測させず、全部文字として読む(調査のときはこちらが安全)
SELECT * FROM read_files(
    '/Volumes/learn/sales/files/',
    format => 'csv',
    header => true,
    schemaHints => 'order_id STRING, amount STRING'
);
取り込みの鉄則:まず生のまま入れる 型を厳しくして取り込むと、1行の汚れで全部が失敗します。実務では「まず文字列のまま丸ごと取り込む(bronze)→ 後で整える(silver)」という順が定石です。この考え方をメダリオンアーキテクチャといい、STEP 10で詳しく扱います。

3. COPY INTO で繰り返し取り込む

COPY INTO は、一度取り込んだファイルを二度取り込まないという性質を持っています。毎日同じフォルダを指定して実行するだけで、増えた分だけがテーブルに入ります。

copy_into.sql
%sql
-- 受け皿(まずは全部STRINGで受ける)
CREATE TABLE IF NOT EXISTS orders_bronze (
    order_id   STRING,
    customer   STRING,
    order_date STRING,
    amount     STRING,
    status     STRING,
    _file_name STRING,
    _loaded_at TIMESTAMP
);

COPY INTO orders_bronze
FROM (
    SELECT
        *,
        _metadata.file_name AS _file_name,   -- どのファイル由来か
        current_timestamp() AS _loaded_at    -- いつ入れたか
    FROM '/Volumes/learn/sales/files/'
)
FILEFORMAT = CSV
FORMAT_OPTIONS ('header' = 'true', 'inferSchema' = 'false')
COPY_OPTIONS ('mergeSchema' = 'true');

SELECT * FROM orders_bronze;
実行結果(例)
num_affected_rows  num_inserted_rows
5                  5

-- もう一度同じCOPY INTOを実行すると
num_affected_rows  num_inserted_rows
0                  0        ← 同じファイルは二度取り込まれない
_metadata 列を必ず入れる _metadata.file_name(ファイル名)や _metadata.file_modification_time(更新時刻)を一緒に保存しておくと、「この行はどのファイルから来たのか」が後から追えます。取り込みの事故調査で、これがあるかないかで解決時間が変わります。

4. 汚れたデータを整える(bronze → silver)

取り込んだ生データを、使える形に直します。ここで try_cast が活きます。

to_silver.sql
%sql
CREATE OR REPLACE TABLE orders_silver AS
SELECT
    cast(order_id AS BIGINT)                       AS order_id,
    nullif(trim(customer), '')                     AS customer,      -- 空文字はNULLに
    try_to_date(order_date, 'yyyy-MM-dd')          AS order_date,
    try_cast(amount AS DECIMAL(12,2))              AS amount,        -- 数値でなければNULL
    lower(trim(status))                            AS status,
    _file_name,
    _loaded_at
FROM orders_bronze
WHERE order_id IS NOT NULL;

-- 品質チェック:変換に失敗した行を数える
SELECT
    count(*)                                          AS 全行,
    count(*) FILTER (WHERE customer IS NULL)          AS 顧客名なし,
    count(*) FILTER (WHERE amount   IS NULL)          AS 金額が変,
    count(*) FILTER (WHERE order_date IS NULL)        AS 日付が変
FROM orders_silver;
実行結果(例)
全行  顧客名なし  金額が変  日付が変
5     1           1         0

count(*) FILTER (WHERE 条件) は「条件に当てはまる行だけ数える」という書き方で、データ品質のチェックに非常に便利です。1本のSQLで、問題のある行がいくつあるかを並べて確認できます。

不良データを黙って捨てない 変換に失敗した行を WHERE で除外して終わりにすると、売上が静かに減ります。実務では、除外した行を別テーブル(隔離テーブル)に保存し、件数を毎日確認する仕組みを作ります。「今日は3件はじきました」と言えることが信頼につながります。

5. Excelを取り込みたいとき

excel.py
%pip install openpyxl
# ライブラリを入れた後はPythonを再起動する
dbutils.library.restartPython()
excel_read.py
import pandas as pd

# Excelはpandasで読み、Sparkのデータフレームに変換してテーブルにする
pdf = pd.read_excel("/Volumes/learn/sales/files/売上.xlsx", sheet_name="2026年8月")
df  = spark.createDataFrame(pdf)

df.write.mode("overwrite").saveAsTable("learn.sales.excel_import")

display(spark.table("learn.sales.excel_import"))

※ Excelは「セル結合」「1行目がタイトル」「途中に小計行」など、機械には読みにくい形になりがちです。取り込みやすい形(1行目が列名、1行1件、結合なし)でもらう交渉をすることが、技術より大事な場面もあります。

練習問題

① 新しいCSVをもう1つボリュームに置き、同じ COPY INTO を実行して、増えた分だけ取り込まれることを確認してください。② orders_silver で、金額の変換に失敗した行の元データ(ファイル名つき)を表示してください。

解答を見る
answer.sql
%sql
-- ② 失敗した行を、元の文字列と一緒に確認する
SELECT
    b.order_id,
    b.amount    AS 元の値,
    b._file_name,
    b._loaded_at
FROM orders_bronze AS b
LEFT JOIN orders_silver AS s ON cast(b.order_id AS BIGINT) = s.order_id
WHERE s.amount IS NULL
  AND b.amount IS NOT NULL;

※ 生データ(bronze)を残しておけば、こうして「元は何だったのか」をいつでも調べられます。生データを消さないのが、取り込み設計の第一原則です。

STEP 7 応用

PySpark ── PythonでDatabricksを動かす

目安 2〜3週間
このステップの到達点 ── SQLとPythonを行き来しながら書ける。DataFrameの基本操作(読む・選ぶ・絞る・まとめる・書く)ができ、どちらを使うべきかを自分で判断できる

なぜPythonも覚えるのか

Databricksは、SQLだけでもかなりの範囲の実務がこなせます。それでもPythonを覚える価値があるのは、次の場面でSQLでは手が届かなくなるからです。

やりたいことSQLPython(PySpark)
集計・結合・整形◎ こちらが読みやすい○ 書ける
同じ処理を50テーブルに繰り返す× 書き写すしかない◎ for文で回せる
条件によって処理を変える◎ if文が使える
APIからデータを取ってくる×
機械学習・AI△(一部SQLで可能)
テストを書く
おすすめの使い分け ── 「集計のロジックはSQL、繰り返しと制御はPython」。実務の現場でも、この形がいちばん多く、いちばん読みやすくなります。Pythonの中にSQLを文字列として書いて実行する、というやり方が普通に使われます。

DataFrameという考え方

PySparkでは、テーブルを DataFrame(データフレーム)という箱として扱います。Excelの1シート、SQLの1テーブルと同じものだと思ってください。DataFrameに対して「列を選ぶ」「行を絞る」といった操作をつなげていきます。

dataframe_basic.py
from pyspark.sql import functions as F

# テーブルを読む
df = spark.table("samples.nyctaxi.trips")

# 選ぶ・絞る・並べる
result = (
    df
    .select("tpep_pickup_datetime", "trip_distance", "fare_amount")
    .filter(F.col("fare_amount") >= 50)
    .orderBy(F.col("fare_amount").desc())
    .limit(10)
)

display(result)   # 表として画面に出す

同じ処理をSQLで書くと、STEP 2で書いたものになります。見比べてください。やっていることは完全に同じです。

SQLPySpark
SELECT a, b.select("a", "b")
WHERE 条件.filter(条件) または .where(条件)
GROUP BY x.groupBy("x")
ORDER BY x DESC.orderBy(F.col("x").desc())
LIMIT 10.limit(10)
AS 別名.alias("別名")
計算列を足す.withColumn("新列", 式)
JOIN … ON ….join(相手, "キー", "left")

集計と列の追加

aggregate.py
from pyspark.sql import functions as F

df = spark.table("samples.nyctaxi.trips")

summary = (
    df
    .withColumn("年月", F.date_trunc("MONTH", "tpep_pickup_datetime"))
    .withColumn("距離区分",
        F.when(F.col("trip_distance") < 1, "近距離")
         .when(F.col("trip_distance") < 5, "中距離")
         .otherwise("長距離"))
    .groupBy("年月", "距離区分")
    .agg(
        F.count("*").alias("件数"),
        F.round(F.avg("fare_amount"), 1).alias("平均料金"),
        F.round(F.sum("fare_amount"), 0).alias("売上"),
    )
    .orderBy("年月", "距離区分")
)

display(summary)
括弧で囲んで改行する書き方 上のように ( で全体を囲むと、. ごとに改行できて読みやすくなります。PySparkのコードはほぼ全てこの形で書かれます。1行が長くなったら改行する、と覚えてください。

SQLとPythonを行き来する

この2つは自由に往復できます。実務では、この行き来がとても多くなります。

sql_python.py
# ① PythonからSQLを実行して、結果をDataFrameで受け取る
df = spark.sql("""
    SELECT pickup_zip, count(*) AS 件数
    FROM samples.nyctaxi.trips
    GROUP BY ALL
""")
display(df)

# ② SQLに値を渡す(文字列を直接つなげない。安全な渡し方)
zip_code = 10001
df2 = spark.sql(
    "SELECT * FROM samples.nyctaxi.trips WHERE pickup_zip = :z LIMIT 5",
    args={"z": zip_code},
)
display(df2)

# ③ DataFrameに名前を付けて、SQLセルから見えるようにする
df.createOrReplaceTempView("zip_summary")
SQLセルから使う
%sql
-- ③で作った一時ビューを、SQLセルからそのまま使える
SELECT * FROM zip_summary ORDER BY 件数 DESC LIMIT 5;
文字列をつなげてSQLを作らない f"... WHERE id = {入力}" のような書き方は、入力に妙な文字が混ざると意図しないSQLが実行されます(SQLインジェクション)。上の②のように args で渡すか、IDENTIFIER() を使ってください。社内データでも例外にしないこと。

遅延実行 ── Sparkの独特なところ

PySparkのコードを実行しても、すぐには計算が始まりませんdisplay()count()、書き込みなど「結果が必要になった瞬間」に、それまでの処理をまとめて最適化してから一気に実行します。これを遅延実行といいます。

lazy.py
df = spark.table("samples.tpch.orders")          # まだ何も動いていない
big = df.filter(F.col("o_totalprice") > 100000)  # まだ動かない
sel = big.select("o_orderkey", "o_totalprice")   # まだ動かない

sel.count()        # ← ここで初めて計算が走る
display(sel)       # ← ここでも走る

# 同じDataFrameを何度も使うなら、結果を保持させると速い
sel.cache()
だから「途中で確認」が安い 100億行のテーブルでも、.limit(10) を付けて表示するだけなら、Sparkは必要な分しか読みません。まず小さく確かめてから、全体に流すという進め方が自然にできます。

書き出す

write.py
# テーブルとして保存(上書き)
summary.write.mode("overwrite").saveAsTable("learn.sales.trip_summary")

# 追記
summary.write.mode("append").saveAsTable("learn.sales.trip_summary")

# 列が増えても受け入れる
(summary.write
    .mode("overwrite")
    .option("overwriteSchema", "true")
    .saveAsTable("learn.sales.trip_summary"))

# CSVとして書き出す(人に渡すとき)
(summary.coalesce(1).write
    .mode("overwrite")
    .option("header", "true")
    .csv("/Volumes/learn/sales/files/export/"))

※ Sparkは分散処理なので、そのまま書き出すとファイルが何十個にも分かれます。1ファイルにまとめたいときは coalesce(1) を使いますが、これは1台に集める処理なので、巨大なデータでやると遅くなります。人に渡す用の小さな結果だけに使ってください。

pandasとの違い

Pythonを学んだことがある人は pandas を知っているかもしれません。混同しやすいので整理します。

pandasPySpark
動く場所1台のメモリの中クラスター全体に分散
扱えるデータ量メモリに載る分だけ(数百万行)何十億行でも
実行のタイミング書いた瞬間結果が必要になった瞬間(遅延)
使いどころ集計後の小さな結果、グラフ、Excel出力大きな元データの処理
pandas_bridge.py
# Sparkで集計してから、小さくなった結果だけpandasに渡す(正しい使い方)
small = spark.sql("SELECT pickup_zip, count(*) AS n FROM samples.nyctaxi.trips GROUP BY ALL")
pdf = small.toPandas()

pdf.plot(kind="bar", x="pickup_zip", y="n", figsize=(10, 4))
toPandas() は「1台に全部集める」命令 1億行のDataFrameにこれを実行すると、メモリが足りずに落ちます。必ず集計・絞り込みをした後の小さな結果に対してだけ使ってください。落ちたときの原因のかなりの割合がこれです。
練習問題

PySparkで、samples.tpch.orders から年ごとの注文件数と売上合計を出し、learn.sales.orders_by_year というテーブルに保存してください(STEP 3の練習問題と同じ集計をPythonで書く、という課題です)。

解答を見る
answer.py
from pyspark.sql import functions as F

result = (
    spark.table("samples.tpch.orders")
    .withColumn("年", F.year("o_orderdate"))
    .groupBy("年")
    .agg(
        F.count("*").alias("注文件数"),
        F.round(F.sum("o_totalprice") / 1000, 0).alias("売上_千"),
    )
    .orderBy("年")
)

display(result)
result.write.mode("overwrite").saveAsTable("learn.sales.orders_by_year")

※ SQLで書いたものとPySparkで書いたもの、どちらが読みやすいかを比べてみてください。この集計だけならSQLのほうが素直です。「Pythonのほうが偉い」ということは一切ありません。

STEP 8 応用

JSONとウィンドウ関数 ── 実務のSQLはここから

目安 2週間
このステップの到達点 ── JSONのような入れ子データから必要な値を取り出せる。ランキング・前月比・最新1件の抽出が書ける。ここまで来ると、任される仕事の幅が一段広がります。

JSONを扱う

アプリのログ、API、IoT機器から来るデータは、たいてい JSON という形をしています。表のようにきれいな四角ではなく、中に入れ子や配列が入っている形式です。Databricksはこれをそのまま列に入れて、SQLで掘れます。

json_setup.sql
%sql
USE CATALOG learn; USE SCHEMA sales;

CREATE OR REPLACE TABLE events (
    event_id BIGINT,
    payload  VARIANT      -- 形が決まっていないデータを入れる型
);

INSERT INTO events VALUES
 (1, parse_json('{"user":{"id":101,"name":"山田"},"action":"purchase","items":[{"sku":"A-1","qty":2},{"sku":"B-7","qty":1}],"amount":12000}')),
 (2, parse_json('{"user":{"id":102,"name":"鈴木"},"action":"view","items":[],"amount":0}')),
 (3, parse_json('{"user":{"id":101,"name":"山田"},"action":"purchase","items":[{"sku":"C-3","qty":5}],"amount":8400}'));

SELECT * FROM events;

VARIANT型は、JSONをそのままの形で保存できる型です。後から「この列は何だったんだ」と困らないよう、まるごと保存しておいて、必要な値だけ取り出す、という使い方をします。

値を取り出す

json_extract.sql
%sql
SELECT
    event_id,
    payload:user.id::BIGINT      AS ユーザーid,   -- : で潜り、:: で型を決める
    payload:user.name::STRING    AS 名前,
    payload:action::STRING       AS 行動,
    payload:amount::DECIMAL(12,2) AS 金額,
    payload:items                 AS 明細  -- 配列のまま
FROM events;
実行結果
event_id  ユーザーid  名前   行動       金額      明細
1         101         山田   purchase   12000.00  [{"sku":"A-1","qty":2},{"sku":"B-7","qty":1}]
2         102         鈴木   view       0.00      []
3         101         山田   purchase   8400.00   [{"sku":"C-3","qty":5}]
記号の意味を覚えるだけ : は「中に潜る」、:: は「型を決める」。payload:user.name::STRING は「payloadの中のuserの中のnameを、文字として取り出す」と読みます。型を決めないと、後の計算や結合で必ず引っかかります。

配列を行に開く(explode)

「1件の注文の中に、明細が3つ入っている」というデータを、明細ごとの1行に開きます。これができると、JSONのデータが普通の表と同じように集計できます。

explode.sql
%sql
SELECT
    e.event_id,
    e.payload:user.name::STRING AS 名前,
    item:sku::STRING            AS 商品,
    item:qty::INT               AS 数量
FROM events AS e,
     LATERAL variant_explode(e.payload:items) AS t(pos, item)
WHERE e.payload:action::STRING = 'purchase';
実行結果
event_id  名前   商品   数量
1         山田   A-1    2
1         山田   B-7    1
3         山田   C-3    5

※ 通常の ARRAY 型の列に対しては explode() を使います(SELECT explode(配列列) FROM …)。VARIANT型の中の配列を開くときが variant_explode です。どちらも「配列を縦に開く」という同じ役目です。

ウィンドウ関数 ── まとめずに、隣の行を見る

GROUP BY は行をまとめてしまうため、明細が消えます。明細を残したまま、順位や前月比を計算したいときに使うのがウィンドウ関数です。実務のSQLで、これが書けるかどうかは大きな差になります。

window.sql
%sql
WITH 月別 AS (
    SELECT
        date_format(o_orderdate, 'yyyy-MM') AS 年月,
        sum(o_totalprice)                    AS 売上
    FROM samples.tpch.orders
    WHERE o_orderdate >= DATE'1998-01-01'
    GROUP BY ALL
)
SELECT
    年月,
    round(売上) AS 売上,
    round(lag(売上) OVER (ORDER BY 年月)) AS 前月,
    round(売上 - lag(売上) OVER (ORDER BY 年月)) AS 増減,
    round(sum(売上) OVER (ORDER BY 年月)) AS 累計,
    round(avg(売上) OVER (ORDER BY 年月 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)) AS 三か月移動平均
FROM 月別
ORDER BY 年月;
関数何が出るか使いどころ
row_number()1,2,3…(同点でも別番号)最新1件の抽出、重複排除
rank() / dense_rank()順位(同点は同順位)売上ランキング
lag() / lead()前の行 / 次の行の値前月比、次回来店までの日数
sum() OVER (…)累計累計売上、在庫の推移
avg() OVER (… ROWS …)移動平均でこぼこをならして傾向を見る
PARTITION BY は「グループごとにやり直す」 OVER (PARTITION BY 顧客 ORDER BY 日付) と書くと、顧客が変わるたびに番号が1に戻ります。「顧客ごとの初回購入日」「店舗ごとの売上順位」のように、実務で必要になるのはほぼこの形です。

QUALIFY ── 最新1件だけを取る

「顧客ごとの最新の注文だけ」を取り出す処理は、データ整備で最も頻繁に登場します。Databricksでは QUALIFY で1文で書けます。

qualify.sql
%sql
-- 顧客ごとの最新注文だけを残す
SELECT
    o_custkey  AS 顧客,
    o_orderkey AS 注文番号,
    o_orderdate AS 注文日,
    o_totalprice AS 金額
FROM samples.tpch.orders
QUALIFY row_number() OVER (PARTITION BY o_custkey ORDER BY o_orderdate DESC, o_orderkey DESC) = 1
ORDER BY 顧客
LIMIT 10;
これが「重複排除」の正体 STEP 5のMERGEで「取り込んだデータに同じキーが複数あると失敗する」と書きました。その解決策がこれです。QUALIFY row_number() OVER (PARTITION BY キー ORDER BY 更新時刻 DESC) = 1 で最新の1件に絞ってからMERGEすれば、安全に反映できます。この1行は、そのまま実務で使えます。覚えてください。
ORDER BYに「同点の決着」を入れる 更新時刻が同じ行が2つあると、どちらが残るかが実行のたびに変わり、結果が毎回違うという最悪の不具合になります。上の例のように、日付の後ろに o_orderkey DESC のような一意の列を足して、必ず順序が決まるようにしてください。
練習問題

samples.tpch.orders から、顧客ごとの購入金額の合計を出し、その上位10位にランキング番号を付けて表示してください。さらに、各顧客の「最初の注文日」と「最後の注文日」も一緒に出してください。

解答を見る
answer.sql
%sql
WITH 顧客別 AS (
    SELECT
        o_custkey        AS 顧客,
        sum(o_totalprice) AS 売上,
        min(o_orderdate)  AS 初回,
        max(o_orderdate)  AS 最終
    FROM samples.tpch.orders
    GROUP BY ALL
)
SELECT
    rank() OVER (ORDER BY 売上 DESC) AS 順位,
    顧客,
    round(売上) AS 売上,
    初回,
    最終,
    datediff(最終, 初回) AS 取引期間_日
FROM 顧客別
QUALIFY 順位 <= 10
ORDER BY 順位;

QUALIFY は、ウィンドウ関数の結果をそのまま条件にできる便利な句です。これが無い場合は、いったんCTEに入れてから WHERE 順位 <= 10 と書くことになります。

STEP 9 応用

Time Travel と性能改善 ── 戻す・速くする・掃除する

目安 1週間
このステップの到達点 ── 誤って更新したテーブルを過去の状態に戻せる。テーブルを速くする方法と、その仕組みを説明できる。「やらかしても戻せる」と知っていることが、思い切った作業を可能にします。

テーブルの履歴を見る

Delta Lakeは、テーブルへの操作をすべて記録しています。まず、STEP 5で作った orders の履歴を見てみましょう。

history.sql
%sql
DESCRIBE HISTORY learn.sales.orders;
実行結果(例)
version  timestamp            operation  operationParameters              userName
4        2026-08-14 10:22:11  MERGE      {predicate: ...}                 ueki@...
3        2026-08-14 10:15:02  DELETE     {predicate: [status = canceled]} ueki@...
2        2026-08-14 10:14:41  UPDATE     {predicate: [order_id = 2]}      ueki@...
1        2026-08-14 10:14:30  WRITE      {mode: Append}                   ueki@...
0        2026-08-14 10:12:08  CREATE TABLE                               ueki@...

「いつ、誰が、何をしたか」がすべて残っています。この一覧は、事故が起きたときの調査でまず開く場所です。

過去の状態を読む・戻す

time_travel.sql
%sql
-- ① バージョン番号を指定して読む
SELECT * FROM learn.sales.orders VERSION AS OF 1;

-- ② 時刻を指定して読む
SELECT * FROM learn.sales.orders TIMESTAMP AS OF '2026-08-14 10:15:00';

-- ③ 今と過去の差分を見る(何が変わったのか)
SELECT * FROM learn.sales.orders VERSION AS OF 1
EXCEPT
SELECT * FROM learn.sales.orders;

-- ④ 本当に戻す
RESTORE TABLE learn.sales.orders TO VERSION AS OF 1;
戻す前に、必ず③で確認する いきなり RESTORE すると、戻したせいで別のデータが消えることがあります。差分を見る → 戻すの順を習慣にしてください。なお RESTORE 自体も履歴に1行として残るので、戻しすぎたらもう一度戻せます。
いつまで戻せるのか 既定では、履歴は30日分保持されます。ただし、後で説明する VACUUM を実行すると、それより前に古いファイルが物理的に消え、その時点より前には戻れなくなります。戻せる期間は無限ではないことを覚えておいてください。

変更履歴を取り出す(CDF)

「どの行が、いつ、どう変わったか」を行単位で取り出す機能もあります。差分だけを次の処理に流したいときに使います。

cdf.sql
%sql
-- 有効にする(作成時に指定してもよい)
ALTER TABLE learn.sales.orders SET TBLPROPERTIES (delta.enableChangeDataFeed = true);

-- 変更差分を読む
SELECT * FROM table_changes('learn.sales.orders', 2);
-- _change_type 列に insert / update_preimage / update_postimage / delete が入る

コピーを作る(CLONE)

clone.sql
%sql
-- 浅いコピー:実データを複製せず、一瞬で終わる。検証用に最適
CREATE OR REPLACE TABLE learn.sales.orders_test
  SHALLOW CLONE learn.sales.orders;

-- 深いコピー:実データも複製する。バックアップや別環境への移送に
CREATE OR REPLACE TABLE learn.sales.orders_backup
  DEEP CLONE learn.sales.orders;
危険な作業の前は、まずCLONE 大きなUPDATEやMERGEを本番テーブルで試す前に、SHALLOW CLONE で複製を作り、そちらで練習してください。ほぼ一瞬・ほぼ無料です。「本番で初めて試す」を避けられる、いちばん簡単な方法です。

テーブルを速くする

Delta Lakeは、書き込みのたびに小さなファイルが増えていきます。ファイルが増えすぎると読み込みが遅くなるため、まとめる作業が必要になります。

optimize.sql
%sql
-- ① 小さいファイルをまとめる
OPTIMIZE learn.sales.orders;

-- ② よく検索に使う列で、データを整列させておく(推奨のやり方)
ALTER TABLE learn.sales.orders CLUSTER BY (order_date, customer);
OPTIMIZE learn.sales.orders;

-- ③ 統計とファイル数の確認
DESCRIBE DETAIL learn.sales.orders;
手段何をするかいつ使うか
OPTIMIZE小さなファイルを適切な大きさにまとめる追記が続いたテーブル
リキッドクラスタリング
CLUSTER BY
よく絞り込む列でデータを並べておくいまはこれが基本。後から列を変えられる
ZORDER BY同上(古い方式)既存のテーブルで使われていれば維持
パーティション
PARTITIONED BY
日付などでフォルダを物理的に分ける1TB超の巨大テーブルだけ。小さいテーブルでやると逆に遅くなる
予測的最適化Databricksが自動でOPTIMIZEとVACUUMを実行有効にできるなら有効に。手間が消える
小さなテーブルをパーティションで分けない 「日付ごとに分ければ速そう」と考えて PARTITIONED BY (日付) にすると、1日あたり数十行しかない場合、極小ファイルが大量にできて劇的に遅くなります。よくある失敗です。迷ったらパーティションは使わず、CLUSTER BY にしてください。

掃除する(VACUUM)

vacuum.sql
%sql
-- 何が消えるかを先に確認する(実際には消さない)
VACUUM learn.sales.orders RETAIN 168 HOURS DRY RUN;

-- 実行(既定は7日=168時間より古い不要ファイルを削除)
VACUUM learn.sales.orders;
VACUUMはTime Travelを削る 消えるのは「もう使われていない古いファイル」ですが、それは過去のバージョンの実体そのものです。VACUUM後は、その時点より前に戻れなくなります。保持期間を短くする指定(RETAIN 0 HOURS)は、実行中の処理を壊す危険もあるため、理由が説明できないうちは既定のままにしてください。
練習問題

learn.sales.orders の全行の金額を、わざと UPDATE orders SET amount = 0; で壊してください。② 履歴を確認し、③ 壊す直前のバージョンとの差分を確認してから、④ 元に戻してください。

解答を見る
answer.sql
%sql
-- ① 事故を起こす(WHEREを忘れた想定)
UPDATE learn.sales.orders SET amount = 0;

-- ② 履歴を見る。いちばん上が事故のバージョン
DESCRIBE HISTORY learn.sales.orders;

-- ③ 直前(事故のversion - 1)と比べる
SELECT * FROM learn.sales.orders VERSION AS OF 5    -- ← 自分の番号に読み替える
EXCEPT
SELECT * FROM learn.sales.orders;

-- ④ 戻す
RESTORE TABLE learn.sales.orders TO VERSION AS OF 5;

SELECT * FROM learn.sales.orders ORDER BY order_id;

※ この練習は、本物の事故が起きる前に一度やっておくことに意味があります。手順を知らないまま本番で慌てると、上書きを重ねて状況を悪化させがちです。

STEP 10 実務

メダリオン設計と自動化 ── 毎朝ひとりでに更新される仕組み

目安 2〜3週間
このステップの到達点 ── データを bronze / silver / gold の3層に整理して設計できる。ジョブを組んで、毎朝自動で集計表が更新される仕組みを作れる。ここができると「基盤を任せられる人」になります。

メダリオンアーキテクチャ

Databricksの現場でほぼ必ず出てくる設計の型です。データを加工の段階で3つの層に分けて置くという、それだけの考え方です。

中身誰が使うか作り方
🥉 bronze
(生)
取り込んだそのまま。型は全部STRINGでもよい。絶対に消さないデータ担当だけCOPY INTO / Auto Loader
🥈 silver
(整えた)
型を直し、重複を消し、キーを整えた「正しい明細」分析者MERGE + QUALIFY
🥇 gold
(使う形)
目的別に集計済み。月次売上、KPI表など全社・BIツールGROUP BY で集計
なぜ分けるのか ① 取り込みで失敗しても生データが残っているので、何度でもやり直せる ② 「どこで数字が変わったか」を層ごとに追える ③ 分析者は整ったsilver以降だけを見ればよく、生データの汚れに振り回されない。これは規模の大小に関係なく効きます。個人の練習でも、最初からこの3層で作ってください。

silver層を作る(重複排除つき)

STEP 6のbronzeから、STEP 8のQUALIFYとSTEP 5のMERGEを組み合わせて、silverを更新します。これが実務のデータ処理のいちばん典型的な形です。

bronze_to_silver.sql
%sql
CREATE TABLE IF NOT EXISTS learn.sales.orders_silver (
    order_id   BIGINT,
    customer   STRING,
    order_date DATE,
    amount     DECIMAL(12,2),
    status     STRING,
    _file_name STRING,
    _loaded_at TIMESTAMP
);

MERGE INTO learn.sales.orders_silver AS s
USING (
    SELECT
        cast(order_id AS BIGINT)              AS order_id,
        nullif(trim(customer), '')            AS customer,
        try_to_date(order_date, 'yyyy-MM-dd') AS order_date,
        try_cast(amount AS DECIMAL(12,2))     AS amount,
        lower(trim(status))                   AS status,
        _file_name,
        _loaded_at
    FROM learn.sales.orders_bronze
    WHERE order_id IS NOT NULL
    -- 同じ注文番号が複数あれば、いちばん新しく取り込んだ1件だけ残す
    QUALIFY row_number() OVER (
        PARTITION BY cast(order_id AS BIGINT)
        ORDER BY _loaded_at DESC, _file_name DESC
    ) = 1
) AS b
  ON s.order_id = b.order_id
WHEN MATCHED AND b._loaded_at > s._loaded_at THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;

SELECT count(*) AS silver件数 FROM learn.sales.orders_silver;

UPDATE SET *INSERT * は、「列名が同じものを全部」という省略記法です。列が多いテーブルで重宝します。WHEN MATCHED AND b._loaded_at > s._loaded_at という条件を入れているのは、古いデータで新しいデータを上書きしてしまう事故を防ぐためです。

gold層を作る

silver_to_gold.sql
%sql
CREATE OR REPLACE TABLE learn.sales.monthly_sales_gold AS
SELECT
    date_trunc('MONTH', order_date) AS 年月,
    count(*)                        AS 件数,
    count(DISTINCT customer)        AS 顧客数,
    sum(amount)                     AS 売上,
    round(avg(amount), 0)           AS 平均単価
FROM learn.sales.orders_silver
WHERE status <> 'canceled'
  AND order_date IS NOT NULL
GROUP BY ALL
ORDER BY 年月;

SELECT * FROM learn.sales.monthly_sales_gold;
goldは「作り直す」でよい 集計表は元データから何度でも作り直せるので、CREATE OR REPLACE TABLE で丸ごと作り直すのがいちばん簡単で、間違いも起きません。データが大きくなって時間がかかるようになってから、差分更新を考えれば十分です。最初から難しく作らないこと。

データ品質のチェックを入れる

自動で回る仕組みには、「おかしくなったら気づける仕掛け」が必須です。まずは簡単なチェックから始めます。

quality_check.sql
%sql
-- ① 制約を付けて、違反データが入らないようにする
ALTER TABLE learn.sales.orders_silver
  ADD CONSTRAINT amount_not_negative CHECK (amount IS NULL OR amount >= 0);

-- ② 毎日確認する数字を1本のSQLで出す
SELECT
    current_date()                                          AS 検査日,
    count(*)                                                AS 件数,
    count(*) FILTER (WHERE customer IS NULL)                AS 顧客名なし,
    count(*) FILTER (WHERE amount IS NULL)                  AS 金額なし,
    count(*) - count(DISTINCT order_id)                     AS 重複件数,
    max(_loaded_at)                                         AS 最終取り込み
FROM learn.sales.orders_silver;

※ ②の結果を毎日テーブルに追記していけば、それ自体が「データ品質の記録」になります。件数が前日から半分に減ったといった異常に気づけるようになり、これができる人は現場でとても重宝されます。

ジョブ(Workflows)で自動実行する

ここまでのノートブックを、毎朝決まった時刻に自動で動かします。左メニューの「Jobs & Pipelines」→「Create job」から設定します。

設定項目何を選ぶか理由
タスクノートブックを指定。複数を順番につなげるbronze → silver → gold の順に依存関係を設定する
コンピュートジョブコンピュート(またはサーバーレス)汎用コンピュートより大幅に安い(STEP 1)
スケジュールcron形式。例:毎朝6時 0 0 6 * * ?タイムゾーンの指定を忘れずに
通知失敗時にメール/Slackへこれが無いと、止まっていることに誰も気づかない
再試行2回程度一時的な失敗は自動で復旧させる
同時実行1前回が終わる前に次が走ると壊れる
通知の設定を最優先で行う 自動化でいちばん怖いのは、失敗することではなく「失敗したまま3週間気づかず、古い数字で経営判断されること」です。ジョブを作ったら、まず通知先を設定し、わざと失敗させて通知が届くことを確認してください。

ノートブックに引数を渡す

ジョブから「何日分を処理するか」といった値を渡せるようにしておくと、再実行や過去分の処理が楽になります。

widgets.py
# 入力欄を作る(ノートブック上部に表示され、ジョブからも値を渡せる)
dbutils.widgets.text("target_date", "2026-08-14", "処理対象日")

target_date = dbutils.widgets.get("target_date")
print(f"{target_date} 分を処理します")

df = spark.sql(
    "SELECT * FROM learn.sales.orders_silver WHERE order_date = :d",
    args={"d": target_date},
)
display(df)

宣言的パイプライン(Lakeflow / DLT)

もう一段進んだ自動化の仕組みがあります。Lakeflow宣言的パイプライン(以前の名称は Delta Live Tables / DLT)といい、「どういうテーブルを作りたいか」だけを書けば、実行の順番・差分処理・再試行・品質チェックをDatabricksが引き受けてくれます。

pipeline.sql(パイプライン用のノートブック)
-- ① 生データを取り込む(ファイルが増えた分だけ自動で処理される)
CREATE OR REFRESH STREAMING TABLE orders_bronze
AS SELECT *, _metadata.file_name AS _file_name, current_timestamp() AS _loaded_at
   FROM STREAM read_files(
        '/Volumes/learn/sales/files/',
        format => 'csv',
        header => true
   );

-- ② 整える。品質ルールを付けられる
CREATE OR REFRESH STREAMING TABLE orders_silver (
    CONSTRAINT 注文番号あり EXPECT (order_id IS NOT NULL) ON VIOLATION DROP ROW,
    CONSTRAINT 金額が正    EXPECT (amount >= 0)
)
AS SELECT
    cast(order_id AS BIGINT)          AS order_id,
    nullif(trim(customer), '')        AS customer,
    try_to_date(order_date)           AS order_date,
    try_cast(amount AS DECIMAL(12,2)) AS amount,
    lower(trim(status))               AS status,
    _file_name, _loaded_at
   FROM STREAM(orders_bronze);

-- ③ 集計する
CREATE OR REFRESH MATERIALIZED VIEW monthly_sales_gold
AS SELECT date_trunc('MONTH', order_date) AS 年月,
          count(*) AS 件数, sum(amount) AS 売上
   FROM orders_silver
   WHERE status <> 'canceled'
   GROUP BY ALL;
書き方意味
STREAMING TABLE増えた分だけを処理して追記する(差分処理を自分で書かなくてよい)
MATERIALIZED VIEW集計結果を保存しておき、元が変わったら自動で作り直す
EXPECT … ON VIOLATION品質ルール。DROP ROW(捨てる)/FAIL UPDATE(止める)/指定なし(記録だけ)
いつ使うか 依存関係が2〜3本のうちは、ふつうのジョブで十分です。テーブルが10本を超え、順番の管理や差分処理が面倒になってきたら、宣言的パイプラインの出番です。品質ルールの結果が画面にグラフで出るので、「今日は何件はじいたか」が一目で分かるのも大きな利点です。名称が変わった経緯(DLT → Lakeflow)があるため、検索するときは両方の名前で調べてください。
練習問題

① bronze → silver → gold の3つのノートブックを作り、ジョブとして順番につないでください。② 失敗通知を自分のメールに設定してください。③ わざとsilverのSQLを壊して実行し、goldが実行されないこと・通知が届くことを確認してください。

解答を見る

画面での操作が中心なので、確認すべき点だけ挙げます。
・ジョブのタスク一覧で、silverのタスクに「依存先=bronze」、goldに「依存先=silver」を設定します。画面に流れ図(DAG)が表示されれば正解です。
・③でsilverが失敗すると、goldは Upstream failed という状態になり実行されません。これが依存関係を設定する目的です。順番なしに並べると、goldは古いsilverを使って「成功」してしまい、誰も異常に気づけません。
・修復は、失敗したタスクだけを対象にできる「Repair run」を使います。全部を流し直す必要はありません。

STEP 11 実務

権限とガバナンス ── 見せてよい人にだけ見せる

目安 1〜2週間
このステップの到達点 ── Unity Catalogで権限を設計し、必要な人に必要な範囲だけを渡せる。個人情報の列を隠す方法、誰がいつ見たかを調べる方法が分かる。

権限設計の基本の考え方

Unity Catalogの権限は、上の階層から下へ継承されます。したがって設計とは、「どの階層で、どのグループに、何を許すか」を決めることに尽きます。

原則具体的には守らないと
個人ではなくグループに渡すdata_analyst のようなグループを作り、そこに権限を付ける入退社のたびに全テーブルを直すことになる
最小権限まず何も渡さない。必要と言われた範囲だけ足す事故と情報漏れの温床になる
gold層だけを公開する分析者にはgoldスキーマだけを見せる生データの汚れで誤った数字が独り歩きする
所有者はグループテーブルのオーナーをチームのグループにする担当者の退職で誰も触れなくなる
環境を分けるカタログを devprod に分ける検証のつもりが本番を壊す

権限を渡す

grants.sql
%sql
-- 3段階すべてに「使ってよい」が必要(ここが最初の関門)
GRANT USE CATALOG ON CATALOG learn            TO `data_analyst`;
GRANT USE SCHEMA  ON SCHEMA  learn.sales      TO `data_analyst`;
GRANT SELECT      ON SCHEMA  learn.sales      TO `data_analyst`;   -- 中の全テーブル

-- 特定のテーブルだけ渡す場合
GRANT SELECT ON TABLE learn.sales.monthly_sales_gold TO `sales_team`;

-- 書き込みも許す(データ担当だけ)
GRANT MODIFY, SELECT ON SCHEMA learn.sales TO `data_engineer`;

-- 確認する
SHOW GRANTS ON SCHEMA learn.sales;
SHOW GRANTS `data_analyst` ON CATALOG learn;

-- 取り消す
REVOKE SELECT ON TABLE learn.sales.orders_bronze FROM `data_analyst`;
「権限がありません」の9割はこれ テーブルに SELECT を付けただけでは読めません。その上のカタログとスキーマに USE が要ります。3階層すべてを通す、と覚えてください。ビューを使う場合は、ビュー自体に権限があれば、元テーブルの権限は不要です(これがビューを使う理由のひとつです)。
権限できること
USE CATALOG / USE SCHEMAその中に入る(これ単体では何も見えない)
SELECT読む
MODIFYINSERT / UPDATE / DELETE / MERGE
CREATE TABLE / CREATE SCHEMA作る
ALL PRIVILEGES全部(安易に使わない
READ VOLUME / WRITE VOLUMEボリューム内のファイルの読み書き

見せたくない列・行を隠す

「売上は見せてよいが、顧客のメールアドレスは伏せたい」「関西支社には関西のデータだけ見せたい」といった要求は、実務で必ず来ます。

masking.sql
%sql
-- ① 列マスク:特定のグループ以外にはぼかして見せる
CREATE OR REPLACE FUNCTION learn.sales.mask_email(email STRING)
RETURN CASE
    WHEN is_account_group_member('pii_reader') THEN email
    ELSE '***@***'
END;

ALTER TABLE learn.sales.customers
  ALTER COLUMN email SET MASK learn.sales.mask_email;

-- ② 行フィルタ:見てよい行だけに絞る
CREATE OR REPLACE FUNCTION learn.sales.area_filter(area STRING)
RETURN is_account_group_member('all_area_reader') OR area = current_user_area();

ALTER TABLE learn.sales.customers
  SET ROW FILTER learn.sales.area_filter ON (area);

※ ②の current_user_area() は説明のための架空の関数です。実際には「ユーザーと担当地域の対応表」を作り、それを参照する関数を書きます。権限のルールをSQLの関数として書き、テーブルに貼り付ける、という形だけ押さえてください。

簡易な方法:ビューで隠す マスク機能が使えない環境では、必要な列だけのビューを作り、ビューにだけ権限を渡す方法があります。CREATE VIEW customers_safe AS SELECT id, name, area FROM customers; のようにして、元テーブルの権限は誰にも渡しません。素朴ですが確実です。

タグとリネージ ── どこから来て、どこへ行くのか

tag.sql
%sql
-- 個人情報を含む列に印を付ける(後で一括で探せる)
ALTER TABLE learn.sales.customers
  ALTER COLUMN email SET TAGS ('pii' = 'true');

ALTER SCHEMA learn.sales SET TAGS ('owner_team' = 'データ基盤チーム');

-- 印の付いた列を全社から探す
SELECT * FROM system.information_schema.column_tags
WHERE tag_name = 'pii';

Catalog画面でテーブルを開くと、Lineage(リネージ)タブがあります。ここには「このテーブルはどのテーブルから作られ、どのダッシュボードで使われているか」が自動で図示されます。「この列を消したら何が壊れるか」を調べる、いちばん確実な方法です。変更前には必ず確認してください。

誰がいつ見たかを調べる

audit.sql
%sql
-- 監査ログ(有効化されている環境で参照できる)
SELECT
    event_time,
    user_identity.email AS 実行者,
    action_name,
    request_params
FROM system.access.audit
WHERE event_date >= current_date() - INTERVAL 7 DAYS
  AND action_name IN ('getTable', 'generateTemporaryTableCredential')
ORDER BY event_time DESC
LIMIT 50;
システムテーブルという宝の山 system カタログには、監査ログ・課金・クエリ履歴・テーブル一覧などがテーブルとして入っています。つまり、いつものSQLで調べられます。STEP 12でコスト分析に使うのもこれです。
練習問題

あなたのチームに、次の3種類の人がいます。それぞれに必要な権限を GRANT 文で書いてください。①データ基盤チーム(全部)②分析担当(goldだけ読める)③営業部(月次売上テーブルだけ読める)。

解答を見る
answer.sql
%sql
-- 前提:スキーマを役割で分けておく(learn.bronze / learn.silver / learn.gold)

-- ① データ基盤チーム
GRANT ALL PRIVILEGES ON CATALOG learn TO `data_engineer`;

-- ② 分析担当:goldだけ
GRANT USE CATALOG ON CATALOG learn      TO `data_analyst`;
GRANT USE SCHEMA  ON SCHEMA learn.gold  TO `data_analyst`;
GRANT SELECT      ON SCHEMA learn.gold  TO `data_analyst`;

-- ③ 営業部:1テーブルだけ
GRANT USE CATALOG ON CATALOG learn                     TO `sales_team`;
GRANT USE SCHEMA  ON SCHEMA learn.gold                 TO `sales_team`;
GRANT SELECT ON TABLE learn.gold.monthly_sales         TO `sales_team`;

-- 確認
SHOW GRANTS `sales_team` ON CATALOG learn;

※ 実務では、これらをコードとしてファイルに残し、Gitで管理します(STEP 13)。画面でぽちぽち設定すると、半年後に「なぜこの人が見られるのか」が誰にも分からなくなります。

STEP 12 実務

コストと性能 ── お金と時間を説明できるようになる

目安 1〜2週間
このステップの到達点 ── いま何にいくらかかっているかをSQLで調べられる。遅いクエリの原因を見つけて直せる。「なぜ今月は高いのか」に答えられる人は、どの組織でも重宝されます。

お金がかかる場所は3つだけ

場所減らし方効果
コンピュートの起動時間自動停止を短くする。使わないものを止めるいちばん大きい
コンピュートの種類定期処理をジョブコンピュートに移す同じ処理で3分の1程度
読むデータの量絞り込み、クラスタリング、不要な SELECT * をやめる速度にも直結
費用の事故は「止め忘れ」で起きる クエリの書き方が悪くて高くなる額より、誰かがクラスターを起動したまま帰った額のほうがずっと大きくなります。まず自動停止、次にクエリ、の順で手を打ってください。

いくら使ったかをSQLで調べる

課金の実績は system.billing.usage というテーブルに入っています。請求書を待たずに、自分で調べられます。

cost_by_day.sql
%sql
-- 直近30日の、種類別・日別のDBU消費
SELECT
    usage_date                                  AS 日付,
    sku_name                                    AS 種類,
    round(sum(usage_quantity), 2)               AS DBU
FROM system.billing.usage
WHERE usage_date >= current_date() - INTERVAL 30 DAYS
GROUP BY ALL
ORDER BY 日付 DESC, DBU DESC;
cost_by_job.sql
%sql
-- 何にいくらかかっているか(概算の金額つき)
SELECT
    u.usage_metadata.job_id      AS ジョブid,
    u.sku_name                   AS 種類,
    round(sum(u.usage_quantity), 1)                       AS DBU,
    round(sum(u.usage_quantity * p.pricing.effective_list.default), 2) AS 概算ドル
FROM system.billing.usage AS u
JOIN system.billing.list_prices AS p
  ON u.sku_name = p.sku_name
 AND u.usage_end_time >= p.price_start_time
 AND (p.price_end_time IS NULL OR u.usage_end_time < p.price_end_time)
WHERE u.usage_date >= current_date() - INTERVAL 30 DAYS
GROUP BY ALL
ORDER BY 概算ドル DESC
LIMIT 20;
この2本を保存しておく 月初にこれを流して、前月と比べる習慣をつけてください。「先月より3割増えました。原因はこのジョブです」と言えるだけで、あなたの評価は変わります。結果をダッシュボード(STEP 13)にしておけば、毎月流す手間も消えます。

使いすぎを防ぐ仕掛け

仕掛け何をするか誰が設定するか
予算(Budgets)上限を決め、超えそうなら通知管理者
コンピュートポリシー「XLサイズ以上は作れない」「自動停止は必須」などのルールを強制管理者
自動停止無操作で停止。全員が確認すべき作った人
タグコンピュートに部署名などを付け、費用を按分できるようにする作った人
タグは最初に決める コンピュートやジョブに teamprojectenv といったタグを付けておくと、system.billing.usagecustom_tags 列で部署別に集計できます。後から付けても過去分は集計できません。最初に決めるべきことのひとつです。

遅いクエリを見つける

slow_query.sql
%sql
SELECT
    executed_by                                    AS 実行者,
    round(total_duration_ms / 1000, 1)             AS 秒,
    round(read_bytes / 1024 / 1024 / 1024, 2)      AS 読んだGB,
    read_rows                                      AS 読んだ行数,
    left(statement_text, 120)                      AS クエリ
FROM system.query.history
WHERE start_time >= current_date() - INTERVAL 7 DAYS
  AND total_duration_ms > 60000        -- 1分以上かかったもの
ORDER BY total_duration_ms DESC
LIMIT 20;

画面から見るときは、SQLエディタやノートブックの実行結果にある Query Profile(クエリプロファイル)を開きます。処理の各段階でどれだけ時間がかかったかが図で表示されます。

プロファイルで見るところ症状対処
Bytes read(読んだ量)結果は10行なのに全件読んでいる絞り込みを効かせる。CLUSTER BY(STEP 9)
Files pruned(読み飛ばした数)0に近い絞り込みの列でクラスタリングする
Spill(あふれ)メモリに収まらずディスクへ処理量を減らす。結合の順を見直す。サイズを上げる
Shuffle(データの移動)結合で大量のデータが行き来している小さいテーブル側を broadcast にする
Skew(偏り)1つのタスクだけ極端に遅い結合キーの偏りを確認(NULLや特定値への集中)

速くするための実践的な7つ

  1. SELECT * をやめる ── 列指向のParquetは、読む列が少ないほど速くなります。効果が大きい割に一番簡単です。
  2. 絞り込みは早く、小さく ── 結合してから絞るのではなく、絞ってから結合します(CTEで先に絞る)。
  3. よく絞る列でクラスタリング ── CLUSTER BY (日付, 顧客)(STEP 9)。
  4. 小さいテーブルはbroadcast ── SELECT /*+ BROADCAST(m) */ … JOIN マスタ m … と書くと、小さい表を各機械に配って移動を減らせます。
  5. OPTIMIZEを定期的に ── 追記が続くテーブルは、ファイルが細かくなり遅くなります。
  6. 結果の使い回し ── 同じ集計を何度も流すなら、gold層のテーブルにしてしまう。何度もSQLを流すより、1回書いて読むほうが安いです。
  7. コンピュートを大きくするのは最後 ── 先に上の6つを試します。サイズを倍にすると費用も倍です。
「速くする」前に「本当に要るのか」を問う 実務では、誰も見ていない日次バッチが動き続けていることがよくあります。ジョブの一覧を見て、出力先のテーブルが最後にいつ参照されたか(system.access.audit やリネージで分かります)を確認してください。止めるのが、最も確実な高速化とコスト削減です。
練習問題

① 直近7日で、いちばんDBUを使っている sku_name は何かを調べてください。② 自分が実行したクエリのうち、いちばん時間がかかったものを見つけ、Query Profileを開いて「読んだデータ量」を確認してください。

解答を見る
answer.sql
%sql
-- ①
SELECT sku_name, round(sum(usage_quantity), 2) AS DBU
FROM system.billing.usage
WHERE usage_date >= current_date() - INTERVAL 7 DAYS
GROUP BY ALL
ORDER BY DBU DESC;

-- ② 自分のクエリだけを対象にする
SELECT statement_id, round(total_duration_ms/1000,1) AS 秒,
       round(read_bytes/1024/1024,1) AS 読んだMB, left(statement_text, 100) AS クエリ
FROM system.query.history
WHERE executed_by = current_user()
ORDER BY total_duration_ms DESC
LIMIT 5;

※ Free Editionや一部のプランでは、システムテーブルの一部が使えない場合があります。その場合は、画面の「Query History」と「Usage」の各ページから同じ情報を見られます。

STEP 13 実務

チーム開発と可視化 ── 人に渡せる形にする

目安 2〜3週間
このステップの到達点 ── ノートブックをGitで管理し、ダッシュボードとして人に見せられる。卒業課題を1つ完成させ、「作ったものを説明できる」状態になる。

Gitでノートブックを管理する

ノートブックを画面上だけで育てていくと、「昨日は動いていたのに」「誰が消したのか分からない」という状態に必ずなります。Databricksは Gitフォルダという機能で、GitHubなどのリポジトリと直接つながります。

やること手順
接続するユーザー設定 → Linked accounts でGitHubなどのトークンを登録
取り込むWorkspace → Create → Git folder でリポジトリURLを指定
日々の作業ブランチを作る → 編集 → Commit & Push → プルリクエストでレビュー
本番に反映mainブランチのGitフォルダを、ジョブから参照させる

Gitそのものの使い方はGit・GitHub入門ページにまとめています。「ブランチを切って、コミットして、プルリクエストを出す」までできれば、当面は十分です。

レビューが品質を作る データ処理の間違いは、実行してもエラーにならずに間違った数字が出ることが多く、自分では気づけません。プルリクエストで他人に見てもらう仕組みがあると、結合の重複や条件の抜けが見つかります。1人のチームでも、翌日の自分にレビューさせるつもりで書いてください。

Databricks Asset Bundles(DAB)

ジョブやパイプラインの設定を、画面ではなくファイルで管理する仕組みです。開発環境と本番環境に同じ構成を配れるようになります。

databricks.yml
bundle:
  name: sales_pipeline

resources:
  jobs:
    daily_sales:
      name: 日次売上更新
      schedule:
        quartz_cron_expression: "0 0 6 * * ?"
        timezone_id: "Asia/Tokyo"
      email_notifications:
        on_failure: ["data-team@example.com"]
      tasks:
        - task_key: bronze
          notebook_task:
            notebook_path: ./notebooks/01_bronze.py
        - task_key: silver
          depends_on: [{ task_key: bronze }]
          notebook_task:
            notebook_path: ./notebooks/02_silver.sql
        - task_key: gold
          depends_on: [{ task_key: silver }]
          notebook_task:
            notebook_path: ./notebooks/03_gold.sql

targets:
  dev:
    default: true
    variables: { catalog: learn_dev }
  prod:
    variables: { catalog: learn_prod }
ターミナルで実行
# CLIをインストールして認証(初回のみ)
databricks auth login --host https://xxxx.cloud.databricks.com

# 検証環境へ配る
databricks bundle deploy -t dev

# 本番へ配る
databricks bundle deploy -t prod

# 手動で1回動かす
databricks bundle run daily_sales -t dev
すぐに使わなくてよい 1人で数本のジョブを回している段階では、画面で作るほうが速いです。「同じ構成をdevとprodの両方に作りたい」「設定変更の履歴を残したい」と感じたときが導入の合図です。そう感じるまでは、名前だけ覚えておけば十分です。

ダッシュボードにする

作った集計表(gold層)を、グラフの並んだ画面にします。左メニューの「Dashboards」→「Create dashboard」から、データにSQLを書き、ウィジェットを配置していきます。

手段向いている場面
AI/BIダッシュボードDatabricks内で完結させたい。作るのが速い
Genie(自然言語での質問)現場の人が自分で「先月の売上は?」と聞ける状態にしたい
Power BI / Tableau / Lookerすでに社内で使っているBIツールがある。SQLウェアハウス経由で接続する
スプレッドシート連携関係者がExcelしか使わない。定期的にCSVを書き出す
BIツールは必ずgold層につなぐ 生データ(bronze)に直接つなぐと、①ツールが毎回巨大なデータを読んで費用がかさむ ②人によって集計の条件が違い、数字が食い違う、という2つの問題が起きます。「見せる数字は1か所で作る」のが、データ基盤を担当する人の仕事です。

外部のPythonから接続する

connect.py(自分のパソコンから)
from databricks import sql
import os

with sql.connect(
    server_hostname = os.environ["DATABRICKS_HOST"],
    http_path       = os.environ["DATABRICKS_HTTP_PATH"],   # SQLウェアハウスの接続先
    access_token    = os.environ["DATABRICKS_TOKEN"],       # コードに直接書かない
) as conn:
    with conn.cursor() as cur:
        cur.execute("SELECT * FROM learn.sales.monthly_sales_gold ORDER BY 年月")
        for row in cur.fetchall():
            print(row)
トークンをコードに書かない アクセストークンはパスワードと同じです。コードに直接書くと、Gitに載って外部に流出します。環境変数か、Databricksのシークレット機能(dbutils.secrets.get())を使ってください。これは絶対の約束です。

この先の道

方向次に学ぶもの向いている人
データエンジニアAuto Loader、宣言的パイプライン、DAB、CI/CD、dbt仕組みを作るのが好き
データアナリストSQLの深掘り、ダッシュボード設計、統計数字から意味を読むのが好き
機械学習・AIMLflow、特徴量エンジニアリング、Vector Search、モデルサービング予測や生成AIに興味がある
基盤運用Unity Catalogの設計、コスト最適化、ネットワークと権限組織全体を整えるのが好き

資格を目標にするなら、Databricks Certified Data Engineer Associate(このページのSTEP 0〜11がおおむね対応)か、Data Analyst Associateが入口です。ただし、資格より「自分で作って動かしたものが1つある」ほうが、採用でも社内でも強く効きます。

卒業課題

次の条件を満たすものを、1つ完成させてください。これができれば、実務で任される水準です。

  1. 自分が興味のあるデータ(家計簿、業務のCSV、e-Statなどの公開データ)をボリュームに置く
  2. bronzeCOPY INTO で取り込む。ファイル名と取り込み時刻の列を持たせる
  3. silver:型を整え、QUALIFY で重複を排除し、MERGE で更新する。品質チェックのSQLを1本用意する
  4. gold:月別などの集計表を作る
  5. 3つをジョブでつなぎ、毎朝自動実行する。失敗通知を設定する
  6. goldをダッシュボードにする
  7. ノートブックをGitに置く
  8. 他人に3分で説明できるようにする(何のデータか、どう流れるか、いくらかかるか、壊れたらどうするか)
取り組むときのヒント

データは小さくてよい。100行でも構いません。評価されるのは規模ではなく、流れが最後まで通っていることです。
失敗を1回わざと起こす。壊れたファイルを混ぜて、通知が届き、生データから復旧できることを確認しておくと、説明に説得力が出ます。
費用を1行で言えるようにする。「毎朝5分、月あたり約○ドル」と言えれば十分です(STEP 12のSQLで調べられます)。
READMEを書く。図が1枚あると、それだけで伝わり方がまるで違います。bronze → silver → gold の矢印だけでも構いません。

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

名前と役割を知っておくだけで、調べる速度が変わります
やりたいことコマンド学ぶ時期
今の状態を確認するSELECT current_catalog(), current_schema(), current_user()STEP 0
一覧を見るSHOW CATALOGS / SHOW SCHEMAS IN c / SHOW TABLES / SHOW GRANTSSTEP 1
列の定義を見るDESCRIBE TABLE 名前 / DESCRIBE TABLE EXTENDED 名前STEP 0
使う場所を宣言するUSE CATALOG c; USE SCHEMA s;STEP 1
ファイル置き場を作るCREATE VOLUME c.s.filesSTEP 1
まとめの単位を全部指定GROUP BY ALLSTEP 3
失敗しても止まらない変換try_cast(x AS INT) / try_to_date()STEP 3
条件つきで数えるcount(*) FILTER (WHERE 条件)STEP 6
結果からテーブルを作るCREATE OR REPLACE TABLE X AS SELECT ...STEP 5
あれば更新・なければ追加MERGE INTO ... USING ... ON ...STEP 5
ファイルを直接のぞくSELECT * FROM read_files('/Volumes/...', format => 'csv')STEP 6
ファイルを取り込むCOPY INTO テーブル FROM '/Volumes/...'STEP 6
由来のファイル名を持たせる_metadata.file_nameSTEP 6
JSONから値を取るpayload:user.name::STRINGSTEP 8
配列を行に開くexplode(列) / variant_explode(列)STEP 8
最新1件だけ取るQUALIFY row_number() OVER (PARTITION BY ... ORDER BY ... DESC) = 1STEP 8
前月比を出すlag(値) OVER (ORDER BY 年月)STEP 8
履歴を見るDESCRIBE HISTORY テーブルSTEP 9
過去の状態を見るSELECT * FROM T VERSION AS OF 5STEP 9
過去に戻すRESTORE TABLE T TO VERSION AS OF 5STEP 9
一瞬で複製するCREATE TABLE T2 SHALLOW CLONE TSTEP 9
速くするALTER TABLE T CLUSTER BY (列)OPTIMIZE TSTEP 9
掃除するVACUUM T(先に DRY RUN で確認)STEP 9
差分を拾うtable_changes('T', 開始バージョン)STEP 9
自動更新される集計表CREATE OR REFRESH MATERIALIZED VIEW ...STEP 10
増えた分だけ処理するCREATE OR REFRESH STREAMING TABLE ... FROM STREAM ...STEP 10
品質ルールを付けるCONSTRAINT 名 EXPECT (条件) ON VIOLATION DROP ROWSTEP 10
権限を与えるGRANT USE CATALOG / USE SCHEMA / SELECT ... TO `グループ`STEP 11
個人情報を隠すALTER TABLE T ALTER COLUMN c SET MASK 関数STEP 11
コストを調べるsystem.billing.usage / system.billing.list_pricesSTEP 12
重いクエリを探すsystem.query.historySTEP 12
誰が見たかを調べるsystem.access.auditSTEP 11
ノートブックに入力欄を作るdbutils.widgets.text("名前", "既定値")STEP 10
ファイルを操作するdbutils.fs.ls() / dbutils.fs.put()STEP 6

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

お金の感覚をつかむ

「これを動かすといくらか」を見積もれるようになる
やること目安ひとこと
Free Editionで学習する0円このページの大半はここで学べる
小さめのSQLウェアハウスで10分の集計約$0.5前後学習中はこの程度。気にしなくてよい
小さめのSQLウェアハウスを1日つけっぱなし$60〜70前後自動停止を忘れたときの損害
汎用クラスター(中サイズ)を1日つけっぱなし数百ドルこれが数日続くと事故になる
毎朝5分のジョブを1か月$5〜15程度ジョブコンピュートを使った場合
同じジョブを汎用コンピュートで動かす上の約3倍いちばん多い無駄遣い
1TBを1か月保管$20〜25程度クラウド事業者への支払い。安い

※ 金額はすべて概算です。実際の単価はクラウド(AWS / Azure / GCP)・リージョン・プラン・契約によって変わり、サーバーレス以外ではクラウド事業者へのVM代が別途かかります。正確な数字は公式の価格ページと、STEP 12のシステムテーブルで確認してください。

請求で失敗しないための4か条 ① すべてのコンピュートに短い自動停止を設定する ② 定期処理はジョブコンピュートで動かす ③ コンピュートポリシーと予算アラートを設定する ④ 月に1回 system.billing.usage を見る。この4つだけで、Databricksの「思わぬ高額請求」のほぼすべてを防げます。

3か月の学習プラン例

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

用語集

調べものの途中で出てきたら、ここに戻ってください
レイクハウス lakehouse
何でも置けるデータレイクの上に、データウェアハウスの信頼性を乗せた形。Databricksの基本思想。
Delta Lake
Parquetファイルの束+変更履歴のログ。これがDatabricksのテーブルの正体。ACID・Time Travel・MERGEを支える。
Apache Spark
複数台のコンピューターに仕事を分けて処理する仕組み。Databricksの計算エンジンの土台。
Photon
Databricks独自の高速な実行エンジン。SQLの集計が数倍速くなる。
Unity Catalog
カタログ › スキーマ › テーブルの3階層でデータを管理する仕組み。権限・検索・リネージもここに集まる。
カタログ catalog
いちばん上の入れ物。パソコンでいうドライブ。環境や部門で分けるのが定番。
スキーマ schema
カタログの中の仕切り。フォルダに近い。bronze / silver / gold で分けることが多い。
ボリューム volume
カタログの中に作れるファイル置き場。/Volumes/カタログ/スキーマ/名前/ で読み書きする。
DBU
Databricksの利用量を数える単位。DBU数 × 単価 × 起動時間が請求額になる。
コンピュート compute
計算する機械。汎用(ノートブック用・高い)/ジョブ用(安い)/SQLウェアハウスの3種類がある。
ノートブック notebook
文章とコードと結果を上から並べて書ける紙。セル単位で実行する。
マジックコマンド
%sql %python %md など。そのセルだけ言語を切り替える指定。
dbutils
ノートブックで最初から使える道具箱。ファイル操作・入力欄・秘密情報の取り扱いなど。
DataFrame
PySparkでテーブルを表す箱。選ぶ・絞る・まとめるをつなげて書く。
遅延実行 lazy
結果が必要になるまで計算しないSparkの性質。まとめて最適化されるため速い。
MERGE
「あれば更新、無ければ追加」を1文で書く命令。実務のデータ更新はほぼこれ。
QUALIFY
ウィンドウ関数の結果をそのまま絞り込める句。最新1件の抽出・重複排除に使う。
VARIANT
JSONなど形の決まっていないデータをそのまま入れられる型。: で潜り :: で型を決める。
Time Travel
過去のバージョンのテーブルを参照・復元できる機能。既定で30日分の履歴が残る。
OPTIMIZE
小さくなったファイルをまとめて読み込みを速くする命令。追記が続くテーブルに実行する。
リキッドクラスタリング
CLUSTER BY でよく絞り込む列にデータを並べておく仕組み。後から列を変えられる。
VACUUM
不要になった古いファイルを物理削除する掃除。実行するとその分Time Travelが効かなくなる。
メダリオン
bronze(生)→ silver(整えた)→ gold(使う形)の3層でデータを整理する設計の型。
Auto Loader
置き場に届いたファイルを、増えた分だけ自動で取り込み続ける仕組み(cloudFiles)。
宣言的パイプライン Lakeflow / DLT
作りたいテーブルを書くだけで、順番・差分処理・品質チェックを引き受けてくれる仕組み。旧称 Delta Live Tables。
ジョブ job / workflow
ノートブックなどを決まった時刻・順番で自動実行する仕組み。失敗通知の設定が必須。
べき等 idempotent
何度実行しても結果が同じであること。自動化する処理はすべてこう作る。
リネージ lineage
このデータがどこから来てどこで使われているかの追跡図。変更の影響範囲を調べるのに使う。
システムテーブル
system カタログにある監査・課金・クエリ履歴。ふつうのSQLで調べられる。
DAB
Databricks Asset Bundles。ジョブなどの設定をファイルで管理し、dev/prodへ配る仕組み。

よくある質問

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

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

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

学べます。Free Editionはクレジットカード不要・期限なしの無料版で、サーバーレスのノートブックとSQL、Unity Catalogが使えます。このページのSTEP 0〜11の大半はここで進められます。会社のアカウントを使う場合は、STEP 1の自動停止とSTEP 12の予算設定を先に確認してください。費用の事故は、ほぼすべて「コンピュートの止め忘れ」から起きます。

SnowflakeやBigQueryとの違いは何ですか

いずれもクラウドのデータ基盤で、できることは大きく重なります。Databricksの特徴は、表形式のデータだけでなく、画像・音声・テキストなども同じ場所で扱えること、SQLとPythonの両方が対等に使えること、そして機械学習・AIまで一続きでできることです。Snowflakeは「SQLだけで完結する範囲の広さと運用の分かりやすさ」、BigQueryは「Google環境との相性」に強みがあります。学ぶ価値という点では、どれか1つを深くやれば他への移行は容易です。SQLとデータ設計の考え方は共通だからです。Snowflakeの学習ページも同じ構成で用意しています。

SQLとPython、どちらを先に覚えるべきですか

SQLです。理由は3つあります。①データの仕事の中心は集計であり、そこはSQLのほうが読みやすく速く書ける ②SQLは他のどの製品に移っても使える ③Pythonは、SQLで手が届かない場面に出会ってから学ぶほうが、必要性が分かって身につきます。このページはその順番で構成しています。STEP 8まで進んでからPython(STEP 7)に戻る、という進め方でも構いません。

ノートブックとSQLエディタ、どちらを使えばよいですか

集計だけをしたいならSQLエディタが軽くて快適です。文章やグラフを混ぜて、人に渡す資料にしたいときや、Pythonを混ぜたいときはノートブックです。学習中はノートブックをおすすめします。%md のセルに「なぜこう書いたか」をメモしながら進めると、それがそのまま自分専用の教科書になります。

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

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

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

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

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

まず落ち着いて、それ以上の操作をやめてください。Delta LakeにはTime Travelがあり、既定で30日分の履歴が残っています(STEP 9)。DESCRIBE HISTORY テーブル で事故のバージョンを特定し、VERSION AS OF で直前の状態を確認してから、RESTORE TABLE ... TO VERSION AS OF n で戻します。慌てて上書きを重ねるのが最悪の対応です。なお、VACUUM を実行した後の古いバージョンには戻れません。

資格(Databricks認定)は取るべきですか

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

機械学習やAIもDatabricksでやるのですか

できます。実験の記録を残す MLflow、モデルを動かし続ける仕組み、生成AIのための検索機能などが揃っています。ただし順番が大事です。データが整っていないところに機械学習を持ち込んでも、良い結果は出ません。STEP 10のsilver・goldが安定して回るようになってから、次の段階として取り組んでください。まずは「正しい数字が毎朝出てくる」ことのほうが、組織にとってはるかに価値があります。

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

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

社内で導入を検討されている場合、最初の関門は技術ではなく「誰がコストと権限に責任を持つか」です。Databricksは使った分だけ課金される仕組みのため、誰もが好きな大きさのコンピュートを起動できる状態にすると、費用が予想を超えます。逆に、STEP 12のコンピュートポリシーと自動停止、そして定期処理をジョブコンピュートに寄せる方針を最初に決めておけば、費用は驚くほど安定します。技術検証と同時に、この運用ルールを決めてください。

もうひとつ、導入時に決めておくべきなのがカタログの分け方です。開発と本番を同じカタログに混ぜてしまうと、後から分けるのは大変な作業になります。devprod を最初に分け、権限を個人ではなくグループに付ける。この2つを最初にやっておくだけで、1年後の苦労がまったく違います。

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

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