はじめに
2026年10月21日に、JEDAIでDatabricksのメトリクスビューのもくもく会をオンラインで開催します。
座学資料はこちらです。
この記事は、その副読本です。当日は冒頭30分で私が画面を見せながら説明しますが、その内容を先に文章で読めるようにしておきます。読んでから参加すると、もくもくタイムに余裕を持って入れます。読まずに来ても実演で追いつけるので、そこは心配しないでください。
参加されない方も、この記事だけで完結するように書いています。使ったノートブックはGitHubに置いてあるので、手元で同じことを再現できます。
題材はメトリクスビューです。Unity Catalogには、指標の定義をカタログのオブジェクトとして置いておける仕組みがあります。SQLからも、AI/BIダッシュボードからも、Genieからも、同じ定義を参照できます。
名前だけ見ると「集計済みのビューに名前を付けたもの」に見えます。私も最初はそう思っていました。実際に作ってみると、発想がほぼ逆でした。
動作確認はDatabricks Free Editionのサーバレスで行いました。もくもく会もFree Editionでやるので、手元に環境がなくても大丈夫です。
メトリクスビューを一言で言うと
集計した表ではなく、指標の計算方法を登録しておくビューです。
数字そのものは持ちません。持っているのは求め方だけです。月ごとか、カテゴリごとか、県ごとか。どの単位で計算するかは、聞く人がクエリのときに決めます。
この一行だけ読んでも、たぶんピンと来ないと思います。私も来ませんでした。なので、何が困っていたのかから書きます。
題材
架空のECショップ「もくもく商店」の注文データを使います。6,000件の注文と、顧客マスタです。
SELECT * FROM orders LIMIT 10;
orders は注文1件が1行で、注文日、顧客ID、商品カテゴリ、金額、ステータスを持っています。customers には都道府県と会員ランクが入っています。生成するコードはノートブックにあります。乱数シードを固定してあるので、手元で実行しても同じ数字になります。
集計したビューを配ると何が起きるか
切り口ごとにビューが1本ずつ増える
「月ごとの売上と客単価が見たい」と言われたとします。毎回SQLを書かせるわけにもいかないので、ビューにして配ります。
CREATE OR REPLACE VIEW v_sales_monthly AS
SELECT
DATE_TRUNC('MONTH', order_date) AS order_month,
SUM(amount) AS total_sales,
COUNT(*) AS order_count,
SUM(amount) / COUNT(*) AS avg_order_value,
COUNT(DISTINCT customer_id) AS unique_customers
FROM orders
WHERE status = '完了'
GROUP BY DATE_TRUNC('MONTH', order_date);
次に「カテゴリごとにも見たい」と言われます。GROUP BY に書いた月がこのビューの単位なので、カテゴリでは出せません。2本目を作ります。続いて都道府県で聞かれて3本目、会員ランクで4本目。
ここまでは手間の話です。面倒ではありますが、作れば済みます。
集計した表から出し直すと、値そのものが狂う
ダッシュボードから毎回6,000行を読むのは重いので、毎晩バッチで日次サマリを作っているとします。よくある構成です。
CREATE TABLE daily_sales_summary AS
SELECT
order_date,
SUM(amount) AS total_sales,
COUNT(*) AS order_count,
COUNT(DISTINCT customer_id) AS unique_customers
FROM orders
WHERE status = '完了'
GROUP BY order_date;
ここで「2026年3月は何人のお客さんが買ってくれましたか」と聞かれます。手元にあるのは31日ぶんの行です。足します。
SELECT
SUM(total_sales) AS `売上合計`,
SUM(order_count) AS `注文数`,
SUM(unique_customers) AS `購入顧客数`
FROM daily_sales_summary
WHERE order_date BETWEEN '2026-03-01' AND '2026-03-31';
売上合計 1,721,180円、注文数 339件、購入顧客数 335人。
注文データから直接数えると、こうなります。
SELECT
SUM(amount) AS `売上合計`,
COUNT(*) AS `注文数`,
COUNT(DISTINCT customer_id) AS `購入顧客数`
FROM orders
WHERE status = '完了'
AND order_date BETWEEN '2026-03-01' AND '2026-03-31';
売上合計と注文数は一致します。購入顧客数だけが 335人と246人 で合いません。
なぜ購入顧客数だけが合わないのか
聞かれているのは「3月に1回でも買った人の数」です。同じ人が何回買っても1人です。日次サマリの unique_customers も同じ定義で、その日に買った人の数です。
日次サマリの行に残っているのは、その日に買った人の数だけです。3月1日は7人、3月2日は10人。この7人と10人に同じ人がいるかどうかは、この表からは分かりません。誰が買ったのかは、集計した時点で消えているからです。
内訳を見てみます。
-- まず、お客さんごとに「3月に何日買ったか」を数える
WITH `顧客ごとの購入日数` AS (
SELECT
customer_id,
COUNT(DISTINCT order_date) AS `購入日数`
FROM orders
WHERE status = '完了'
AND order_date BETWEEN '2026-03-01' AND '2026-03-31'
GROUP BY customer_id
)
-- 次に、購入日数ごとに人数を数える
SELECT
`購入日数`,
COUNT(*) AS `人数`
FROM `顧客ごとの購入日数`
GROUP BY `購入日数`
ORDER BY `購入日数`;
1日だけ買った人が173人、2日買った人が59人、3日が12人、4日が2人でした。2日以上買った73人が、買った日数ぶん重複して数えられています。59 + 12×2 + 2×3 = 89 で、335と246の差にぴったり一致します。
売上合計と注文数が合うのは、3月1日の売上と3月2日の売上が別のお金だからです。同じ注文が2つの日に入ることもありません。重なりようがないので、足せば正しい値になります。
足せる指標と、足せない指標
集計した表からまとめ直すとき、指標は3種類に分かれます。
| 種類 | 例 | 日次サマリから月の値を出せるか |
|---|---|---|
| そのまま足せる | 売上合計、注文数 | 出せる。足すだけ |
| 分子と分母が残っていれば出せる | 客単価、キャンセル率 | 売上合計 1,721,180円 ÷ 注文数 339件 で 5,077円 |
| そもそも計算できない | 購入顧客数、中央値 | 出せない。誰が買ったのかが残っていない |
2種類目に注意が要ります。集計した表に客単価の列だけを残すと、月の客単価が出せなくなります。
3月1日の客単価が2,206円、3月2日が10,886円だったとします。この2つの数字だけでは、2日間の客単価は決まりません。
| もし注文数が | 2日間の売上 | 2日間の注文数 | 2日間の客単価 |
|---|---|---|---|
| 7件と10件 | 124,300円 | 17件 | 7,312円 |
| 100件と1件 | 231,486円 | 101件 | 2,292円 |
同じ2,206円と10,886円でも、件数が違えば答えが変わります。集計した表に残すべきなのは、割り算の結果ではなく、分子の売上合計と分母の注文数です。
これは日次サマリに限った話ではありません。ダッシュボードの合計行、フィルタを外したとき、Excelに落として足したとき、どれでも同じことが起きます。
困りごとは同じところから来ている
切り口ごとにビューが増えるのも、足し直すと値が狂うのも、集計した表を共有したこと から来ています。
数字は、集計した時点で単位が決まります。別の単位で見たくなったら、ビューを作り直すか、足し直して間違えるかのどちらかです。かといって、見たい人に毎回SQLを書かせるわけにもいきません。
共有するものを、集計した表ではなく計算方法に変えられないか。それがメトリクスビューです。
メトリクスビューを作る
登録するのはフィールドとメジャーの2種類
定義に書くものは2つだけです。ここを押さえると、あとは書き写すだけになります。
フィールド は、集計する単位に使える列です。注文日、商品カテゴリ、都道府県、会員ランク。もとのテーブルの1行1行に値としてそのまま入っているので、集計しなくても取り出せます。ドキュメントには「ディメンションとも呼ばれます」という言い添えがあるので、他のBIツールを触ってきた方はそちらの言い方のほうが馴染みがあるかもしれません。YAMLでは fields に書きます。
メジャー は、集計のしかたを書いたものです。売上合計なら SUM(amount)、購入顧客数なら COUNT(DISTINCT customer_id)。式だけを登録して、値は計算しません。YAMLでは measures に書きます。
違いは、値がいつ決まるかです。
| フィールド | メジャー | |
|---|---|---|
| 中身 | もとの行に入っている値 | 集計のしかた |
| 例 | 注文日、カテゴリ、都道府県 | 売上合計、注文数、購入顧客数 |
| 値が決まるとき | 行を見た時点で決まっている | 集計する単位が決まってはじめて決まる |
3月の購入顧客数が246人なのか335人なのかは、3月全体で数えるのか、日ごとに数えてから足すのかが決まらないと決まりませんでした。メジャーが単位なしでは値を持てないのは、これと同じことです。だからメトリクスビューは値を持たず、聞かれてから計算します。
YAMLで定義する
CREATE VIEW ... WITH METRICS LANGUAGE YAML AS $$ ... $$ の中にYAMLを書きます。
CREATE OR REPLACE VIEW sales_metrics
WITH METRICS
LANGUAGE YAML
AS $$
version: 1.1
comment: "もくもく商店の売上指標"
source: orders
filter: status = '完了'
joins:
- name: customer
source: customers
'on': source.customer_id = customer.customer_id
fields:
- name: order_date
expr: order_date
comment: "注文日"
- name: order_month
expr: DATE_TRUNC('MONTH', order_date)
comment: "注文月"
- name: category
expr: category
comment: "商品カテゴリ"
- name: prefecture
expr: customer.prefecture
comment: "顧客の都道府県"
- name: member_rank
expr: customer.member_rank
comment: "会員ランク"
measures:
- name: total_sales
expr: SUM(amount)
comment: "売上合計 (円)"
- name: order_count
expr: COUNT(*)
comment: "注文数"
- name: avg_order_value
expr: MEASURE(total_sales) / MEASURE(order_count)
comment: "客単価 (1注文あたりの金額)"
- name: unique_customers
expr: COUNT(DISTINCT customer_id)
comment: "購入顧客数"
$$;
読みどころは4つです。
filter は、誰がクエリしても必ずかかる条件です。完了した注文だけを対象にする、という決めごとを定義側に置けます。使う人が WHERE status = '完了' を書き忘れて数字がずれる、ということが起きません。
joins に結合を書いておくと、使う側は JOIN を書かずに prefecture や member_rank をほかのフィールドと同じように扱えます。on をクォートしているのは、YAMLが on を真偽値のtrueとして解釈するためです。ここは最初に引っかかったところでした。
avg_order_value は、MEASURE(total_sales) / MEASURE(order_count) と書いています。売上合計と注文数をそれぞれメジャーとして持っておき、割り算はあとから行います。さきほど見たとおり、割り算の結果だけを持つと別の単位では出せなくなるので、この書き方が効いてきます。
そして、GROUP BY が一度も出てきません。 どの単位で集計するかを、このビューは決めていません。
切り口を変えて聞く
GROUP BYは聞く人が決める
書き方のきまりは2つです。集計する単位は GROUP BY に書き、メジャーは MEASURE() で囲みます。
-- 月ごと
SELECT
order_month,
MEASURE(total_sales) AS `売上合計`,
ROUND(MEASURE(avg_order_value)) AS `客単価`
FROM sales_metrics
WHERE order_month >= '2026-01-01'
GROUP BY order_month
ORDER BY order_month;
-- カテゴリごと。ビューは作り直していない
SELECT
category,
MEASURE(total_sales) AS `売上合計`,
ROUND(MEASURE(avg_order_value)) AS `客単価`
FROM sales_metrics
GROUP BY category
ORDER BY `売上合計` DESC;
-- 都道府県ごと。JOINを書いていない
SELECT
prefecture,
MEASURE(total_sales) AS `売上合計`,
MEASURE(unique_customers) AS `購入顧客数`
FROM sales_metrics
GROUP BY prefecture
ORDER BY `売上合計` DESC;
さきほどは切り口ごとに3本のビューを作りましたが、こちらは1本のままです。変えているのは SELECT の1列目と GROUP BY だけです。
最初の問いにも戻ってみます。GROUP BY を書かなければ、WHERE で絞った範囲の全体が1行で返ります。
SELECT
MEASURE(total_sales) AS `売上合計`,
MEASURE(order_count) AS `注文数`,
MEASURE(unique_customers) AS `購入顧客数`
FROM sales_metrics
WHERE order_date BETWEEN '2026-03-01' AND '2026-03-31';
246人が返ります。集計した表を持っておらず、聞かれた範囲を orders から数え直しているので、足し直しによる重複が起きません。
SELECT * は使えない
SELECT * FROM sales_metrics;
これはエラーになります。
[METRIC_VIEW_MISSING_MEASURE_FUNCTION] The usage of measure column
[total_sales,order_count,avg_order_value,unique_customers] of a metric view
requires a MEASURE() (or AGG()) function to produce results.
エラーの文に並んでいる4つは、すべてメジャーです。集計する単位が決まらないと値が決まらないので、単位を指定していない SELECT * では値を作れません。当然といえば当然なのですが、テーブルと同じ感覚で中身を覗こうとして最初にここで止まりました。欲しい列を明示して、メジャーは MEASURE() で囲む。これがメトリクスビューへの聞き方です。
クエリのときにJOINはできない
メトリクスビューは、クエリのときにほかのテーブルと直接 JOIN できません。組み合わせたいときは、いったん WITH 句で結果を受けてから結合します。
結合を定義側の joins に書いておくのは、そのためでもあります。使う人が結合の条件を間違えて数字がずれる余地を、定義の中で消しておけます。
定義が1か所にあるということ
カタログエクスプローラーから開くと、「メジャー」と「フィールド」という2つの見出しで、登録したものが一覧になります。元になっているテーブルと filter の条件も表示されます。
客単価の計算式は、ここにしかありません。変えたいときはここだけ直せば、使う側の全員に同時に反映されます。Unity Catalogのオブジェクトなので、権限の管理もテーブルと同じです。定義するのはデータチーム、使うのは全員、という分け方ができます。
そして、SQLを書かない人も同じ定義を使えます。
AI/BIダッシュボードにデータとして sales_metrics を追加し、X軸に category、Y軸に total_sales を選ぶと、MEASURE() は自動で付きます。Genieスペースに追加して「2026年3月は何人のお客さんが買いましたか」と日本語で聞けば、生成されるSQLに MEASURE() が入り、SQLで聞いたときと同じ246人が返ります。
同じ定義は、アラートからも、Power BIやTableauのような外部BIツール、Excelからも参照できます。どこから見ても、購入顧客数の計算式はひとつです。
Genieオントロジーの中での位置づけ
メトリクスビューは、SQLを書く人のための仕組みであると同時に、AIに正しい数字を答えさせるための土台でもあります。
Databricksは、社内で使う言葉や指標の意味をまとめる仕組みを「Genieオントロジー」として整理しています。現時点ではプレビューです。中身は2階建てになっています。
| 誰が用意するか | 中身 |
|---|---|
| 人が定義する | メトリクスビュー (指標)、ドメイン (データのまとまり)、ページ (業務の言葉の説明) |
| Databricksが集める | 既存のノートブックやクエリから拾った定義、よく使われている情報 |
メトリクスビューは、このうち「人が定義する」側の中心にあります。購入顧客数の定義を1か所に置いておけば、人がSQLで聞いても、ダッシュボードで見ても、Genieに日本語で聞いても、返ってくる数字が揃います。
Genieに正しく答えさせるために例題を大量に登録する、という運用をしている方もいると思います。指標の定義そのものを先に置けるなら、そちらのほうが筋がいい場面は多そうです。
今回扱わなかったこと
定義に書けることはほかにもあります。もくもく会でも時間の都合で扱いませんが、名前だけ挙げておきます。ノートブックの末尾に読み物として入れてあるので、持ち帰って試してみてください。
-
display_nameとsynonymsとformat。画面に出す名前、別名、通貨や小数点の書式を付けられます。英語の列名に日本語の表示名と同義語を付けておくと、「売上」「売り上げ」「売上高」のような言い方の揺れにも対応しやすくなります - ウィンドウメジャー。「過去7日間の購入顧客数」「前年同月の売上」のように、期間をずらしたり広げたりする指標を定義できます
- パラメーター。クエリ時に値を渡して計算を変えられます
- マテリアライズ。よく使う集計を事前計算しておき、クエリを自動で速いほうに振り分けます
もくもく会に参加される方へ
当日は18:30からの90分です。前半30分は、この記事に書いたことを私が画面で実演します。そのあと環境の準備を全員でやって、35分のもくもくタイム、最後に答え合わせという流れです。
自分で手を動かしていただくのは、記事でいうと「切り口を変えて聞く」のところです。1本のメトリクスビューに対して SELECT と GROUP BY を書き換える課題が3つあります。データを作るコードもメトリクスビューの定義も完成した状態で配るので、YAMLを一から書く必要はありません。課題の解答は、それぞれの課題のすぐ下に折りたたんで置いてあります。詰まったら開いて構いません。
当日までにやっておくとよいこと
必須ではありません。当日の準備時間に全員でやりますが、先に済ませておくと余裕ができます。
- Databricks Free Edition にサインアップしておく
-
GitHubのリポジトリ から
metric_view_mokumoku.pyをワークスペースにインポートしておく。Gitフォルダとして取り込んでも、Raw URLを指定してインポートしても構いません - ノートブックのPart 0とPart 1を実行して、題材データを作っておく
コンピュートはサーバレスで動きます。メトリクスビューの作成や MEASURE() でエラーが出る場合は、ノートブック右上のコンピュートをSQLウェアハウスに切り替えてください。
質問は当日チャットで拾います。この記事の内容で分からなかったところをそのまま聞いてもらっても構いません。
まとめ
メトリクスビューを作ってみて分かったことをまとめます。
-
集計した表ではなく、指標の計算方法を登録しておくビュー。 数字は持たず、どの単位で集計するかは聞く人が
GROUP BYで決める - 登録するのはフィールドとメジャーの2種類。フィールドはもとの行にある値、メジャーは集計のしかた
-
定義に
GROUP BYは出てこない。 切り口を変えてもビューは1本のまま -
SELECT *はエラーになる。 集計する単位が決まらないとメジャーの値が決まらないため - 比率や平均は、割り算の結果ではなく分子と分母をそれぞれメジャーとして持つ。
MEASURE(total_sales) / MEASURE(order_count)の形 -
filterとjoinsを定義側に置けるので、使う人の書き忘れや結合ミスで数字がずれる余地を減らせる - YAMLの
onはクォートが要る。YAMLが真偽値のtrueとして解釈するため - 定義は1か所。SQL、ダッシュボード、Genie、外部BIツールのどこから見ても同じ数字になる
一番の収穫は、「集計済みのビューを配る」という当たり前にやっていたことが、何を失っているのかを言葉にできたことでした。集計した瞬間に、誰が買ったのかという情報が消えます。消えたあとで人数を足し直すと、静かに間違った数字が出ます。エラーは出ません。
購入顧客数のような指標を日次サマリから足している構成は、おそらく珍しくありません。もし心当たりがあれば、注文データから直接数えた値と突き合わせてみると、面白いことになるかもしれません。
もくもく会では、ここに書いたことを手元で動かしてもらいます。読んで分かった気になるのと、SELECT * を実行してエラーを出してみるのとでは、残り方が違うと思っています。当日お待ちしています。
参考リンク
- Unity Catalogのメトリクスビュー
- メトリクスビューの作成
- メトリクスビューのクエリー
- メトリクスビューの基本的なモデリング
- メトリクスビュー YAML構文リファレンス
- メトリクスビューの高度なテクニック
- メトリクスビューのマテリアライズ
- パラメーターの使用
- Genieオントロジー
- Unity Catalogセマンティクス

