TL;DR
- Databricks の Metric View は、クエリ時に他のテーブルとも他の Metric View とも「直接 JOIN」できない。やると
METRIC_VIEW_JOIN_NOT_SUPPORTEDエラーになる。 - 正解は 各 Metric View を CTE(
WITH句)でMEASURE()+GROUP BYして集計してから、CTE 同士を JOIN すること。 - Genie エージェントで複数 Metric View をまたぐ質問をすると、生成 SQL が「CTE 化する正しい形」と「直接 JOIN する誤った形」に揺れることがある。これは Genie の SQL 生成が非決定的なため。
- ナレッジストアの JOIN 定義は「実行時の結合」ではなく「生成のヒント」 にすぎない。安定化には Example SQL(CTE 形の見本)を登録して生成を寄せるのが最も効く。
この記事の SQL は samples.tpch(Databricks に標準で付属するサンプル)でそのまま試せます。
Metric View とは(前提)
Metric View は、KPI(メジャー)と分析軸(ディメンション)を YAML で一元定義する Unity Catalog オブジェクトです。BI ツールや Genie から「売上」「注文数」といったビジネス指標を一貫した定義で参照でき、集計は MEASURE() 関数を通して評価します。
まず samples.tpch から Metric View を 2 つ作ります。
CREATE SCHEMA IF NOT EXISTS main.mv_demo;
-- 注文側の Metric View
CREATE OR REPLACE VIEW main.mv_demo.orders_mv WITH METRICS
LANGUAGE YAML
AS $$
version: 0.1
source: samples.tpch.orders
dimensions:
- name: custkey
expr: o_custkey
measures:
- name: order_count
expr: COUNT(1)
- name: total_revenue
expr: SUM(o_totalprice)
$$;
-- 顧客側の Metric View
CREATE OR REPLACE VIEW main.mv_demo.customer_mv WITH METRICS
LANGUAGE YAML
AS $$
version: 0.1
source: samples.tpch.customer
dimensions:
- name: custkey
expr: c_custkey
measures:
- name: total_acctbal
expr: SUM(c_acctbal)
$$;
単体で参照するときは、メジャーを MEASURE() で包みます。
SELECT custkey, MEASURE(total_revenue) AS revenue
FROM main.mv_demo.orders_mv
GROUP BY custkey;
本題:Metric View は直接 JOIN できない
顧客ごとに「売上合計」と「口座残高合計」を並べたい、という素朴な要求を考えます。2 つの Metric View を custkey で JOIN すれば良さそうに見えます。
-- ❌ これはエラーになる
SELECT o.custkey, MEASURE(o.total_revenue) AS rev, MEASURE(c.total_acctbal) AS bal
FROM main.mv_demo.orders_mv o
JOIN main.mv_demo.customer_mv c ON o.custkey = c.custkey
GROUP BY o.custkey;
結果:
[METRIC_VIEW_JOIN_NOT_SUPPORTED] The metric view is not allowed to use joins.
Metric View と通常テーブルの直接 JOIN も、同じエラーになります。
-- ❌ これもエラー(相手が通常テーブルでも同じ)
SELECT o.custkey, MEASURE(o.total_revenue) AS rev, c.c_name
FROM main.mv_demo.orders_mv o
JOIN samples.tpch.customer c ON o.custkey = c.c_custkey
GROUP BY o.custkey, c.c_name;
これは公式ドキュメントにも明記された仕様です。バグではありません。
メトリックビューは、クエリ時に他のテーブルと直接結合できません。メトリックビューを別のテーブルと結合するには、メトリックビューのクエリを CTE でラップし、その CTE の結果を結合します。
正解:CTE で集計してから JOIN する
各 Metric View を WITH 句(CTE)の中で先に集計し、「ただの集計済み結果」に変えてから、CTE 同士を JOIN します。
-- ✅ CTE 化してから JOIN する
WITH orders AS (
SELECT custkey,
MEASURE(total_revenue) AS revenue,
MEASURE(order_count) AS order_count
FROM main.mv_demo.orders_mv
GROUP BY custkey
),
cust AS (
SELECT custkey,
MEASURE(total_acctbal) AS acctbal
FROM main.mv_demo.customer_mv
GROUP BY custkey
)
SELECT o.custkey, o.revenue, o.order_count, c.acctbal
FROM orders o
JOIN cust c ON o.custkey = c.custkey
ORDER BY o.revenue DESC
LIMIT 5;
ポイントは 2 つです。
- メジャーは CTE の中で
MEASURE()を使って評価し、GROUP BYで粒度を確定させる。 - JOIN キーはディメンションとして各 CTE に残し、その列で CTE 同士を JOIN する。(メジャーではなく、CTE に残した次元で結合する)
CTE 化した時点で中身は通常の集計結果になっているため、METRIC_VIEW_JOIN_NOT_SUPPORTED の制約に触れずに結合できます。
Metric View は、別の Metric View を source にして積み重ねる「合成(composability)」もできます。その際、派生側で元のメジャーを参照するときは MEASURE() で包みます(例: expr: MEASURE(total_revenue))。
Genie で起きる「揺れ」
ここからが本題です。Genie エージェントに複数の Metric View を登録し、「顧客ごとに売上合計と口座残高合計を並べて」と自然言語で聞くと、生成される SQL が
- CTE 化してから JOIN する正しい形 → 成功
-
Metric View 同士を直接 JOIN する誤った形 →
METRIC_VIEW_JOIN_NOT_SUPPORTEDで失敗
の 2 通りに揺れることがあります。
これは Genie が壊れているのではなく、LLM による SQL 生成が非決定的なためです。「Metric View は直接 JOIN できない」という一定の制約に対して、生成が正しい形と誤った形の間でぶれ、誤った形に振れたときにエラーになります。
ナレッジストアの JOIN 定義は「実行」ではなく「生成ヒント」
「じゃあ Genie のナレッジストアに JOIN 関係を定義すれば直る?」と考えたくなります。ここが理解のキモです。
ナレッジストアの JOIN 定義は、クエリを実行可能にするものではありません。 それは Genie(LLM)に「この 2 つは custkey で、多対一で関係する」と教えるメタデータ(生成のヒント) です。実行エンジンのレベルでは、Metric View は依然として直接 JOIN 不可のままです。
つまり Genie は、与えられた関係を実行可能な CTE 形へ「翻訳」 しなければなりません。揺れの正体は、この翻訳が成功するか(CTE 形になる)/失敗するか(直接 JOIN のまま出す)です。
実験:join 定義あり / なしで生成 SQL を比べる
同じ Metric View 2 つを載せた Genie エージェントを 2 つ用意し、片方だけナレッジストアに JOIN 定義を入れて、同じ質問を投げて生成 SQL を比較しました(Genie Conversation API で生成 SQL を取得)。
| 条件 | 試行数 | 生成パターン | 直接 JOIN 失敗 |
|---|---|---|---|
| JOIN 定義あり | 4 問 | すべて CTE | 0 件(全成功) |
| JOIN 定義なし | 3 問 | すべて CTE | 0 件(全成功) |
- この検証(検証用に新しく作った最小構成の Genie エージェントで、執筆時点の Genie を使用)では、合計 7/7 で CTE 形に翻訳され、直接 JOIN の失敗は再現しませんでした。
-
JOIN 定義の有無で差は出ず、定義なしでも Genie は自力で
custkeyを見つけて CTE 化しました。今回は両 Metric View に同名のcustkeyディメンションがあり、関係が自明だったためと考えられます。
Genie は非決定的で、今回のサンプルは単純(共有キー 1 つ・メジャー少数)です。「再現しなかった」=「起きない」ではありません。実務のように結合キー名が異なる/関係が複雑/メジャーが多いケースでは、生成の揺れが観察されやすくなります。
この結果からの示唆:
- JOIN 定義の本当の役割は「CTE 化させること」ではなく「どのキーで・どの関係で結ぶかを教えること」。 結合キー名が Metric View 間で異なるほど、この定義の価値(正しいキーで結ぶ)が効いてきます。
- CTE 化させたいなら、Example SQL(正解の見本)で生成構造を固定するのが確実。
安定化のためのチューニング
効果の大きい順に挙げます。
1. instructions に明示する
Metric View を他の Metric View またはテーブルと JOIN する場合、
Metric View を直接 JOIN してはいけない。
必ず各 Metric View を個別の CTE として定義し、
必要な Measure は MEASURE() で集計し、JOIN キーの粒度で GROUP BY した後、
CTE 同士を JOIN すること。
2. Example SQL(CTE 形の見本)を登録する ← 最も効く
毎回の質問に対して人が SQL を書くのではありません。 代表的な横断質問を数本、CTE 形式の正解 SQL とセットで登録します。Genie はこれをお手本として学習し、似た質問に同じパターンを当てはめます。上の「正解:CTE で集計してから JOIN する」の SQL がそのまま見本になります。
3. JOIN キーと粒度を固定する
Example SQL と説明文で、結合キー・時間粒度・各 CTE の粒度・JOIN 後に再集計してよいメジャーか・多対一か一対多か・Fan-out が発生しないか、を明示します。
4. エージェントに載せる Metric View を絞る
1 つの Genie エージェントに登録できるテーブル/ビューの上限は 30 ですが、上限まで積まず、必要なものだけに絞って小さく始めるほど、選択のあいまいさが減り精度が安定します。
まとめ
- Metric View は直接 JOIN 不可。
METRIC_VIEW_JOIN_NOT_SUPPORTEDを見たら「CTE 化してから JOIN」に直す。 - Genie の揺れは非決定的生成が制約にぶつかった結果。ナレッジストアの JOIN 定義は生成ヒントであって実行保証ではない。
- 安定化は Example SQL(CTE 形の見本)で生成を固定するのが本命。
後片付け
DROP SCHEMA IF EXISTS main.mv_demo CASCADE;
