ふくふくHukuhuku Inc.
EP.09Democracy 11分公開: 2026-05-10

クエリの「型」を作る:パラメタライズドテンプレートで再現性を担保

「セルフサービス = 全員ゼロから sql 書く」と捉えると失敗する。よくある問いはテンプレ化し、パラメータだけ変えればよい状態に。Looker explore / dbt metrics / シンプルな yaml 雛形で実現する設計。

#クエリテンプレート#Looker#{{dbt|*}} metrics#再現性
CO📔 Google Colab で開く(上から順にセルを実行)
シェア

民主化を「全員に を書かせる」と捉えるのは間違いです。実際の問いの 8 割はパターン化できる頻出パターンをテンプレ化し、パラメータだけ変える仕組みを用意することで、自由度を奪わずに再現性を上げることができます。

テンプレート化が効くシーン

パターン
期間 × 軸別の集計「2024年Q3」の「カテゴリ別」売上
Top N「先月の」「離反率の高い」上位10セグメント
「2024年4月入会」顧客の「月次離反率」
「サインアップ → 初回購入 → 2回目購入」の通過率
A/B 比較「機能Aあり/なし」での「3日後の継続率」

Pattern 1: SQL の文字列テンプレート(最簡)

Jinja で SQL テンプレート
Python
from jinja2 import Template
template = Template("""SELECT  DATE_TRUNC({{ date_column }}, {{ granularity }}) AS period,  {% if dimension %}{{ dimension }},{% endif %}  COUNT(*) AS cnt,  SUM(amount_jpy) AS revenueFROM {{ table }}WHERE _PARTITIONTIME BETWEEN '{{ start_date }}' AND '{{ end_date }}'  {% if filters %}{% for f in filters %}AND {{ f }}{% endfor %}{% endif %}GROUP BY 1{% if dimension %}, 2{% endif %}ORDER BY 1LIMIT 1000;""")
sql = template.render(    table="proj.dwh.orders",    date_column="created_at",    granularity="month",    dimension="category",    start_date="2027-01-01",    end_date="2027-12-31",    filters=["region = 'east'"],)print(sql)
SQL injection への配慮

ユーザ入力を直接 Jinja に渡すのは危険。ホワイトリスト式の選択肢(ドロップダウン)で渡すか、列挙型でバリデーションすること。テンプレに渡る値は事前に検証する設計が必須。

Pattern 1 が有効なケース

① 既存ツールに乗せたい: Slack Bot (EP.04) や社内 ChatBot 内で SQL を組み立てる時。② 数十パターン程度の固定テンプレ: パターン数が爆発しない見込み。③ 最小依存で素早く始めたい: ツール導入が間に合わない、PoC 段階。❌ 向かない: アナリスト/業務担当が GUI でいじりたい、テンプレ数が 100+ になる、複数の BI クライアントから参照される。

Pattern 2: Looker Explore(ビジネス向き)

Looker の Explore はテンプレートそのもの。LookML で定義した dimensions / measures をユーザがGUI で組合せ、SQL は生成される「テンプレ + 」の鉄板パターン

LookML(簡略版)
YAML
view: orders {  sql_table_name: `proj.dwh.orders` ;;
  dimension: order_id { primary_key: yes }  dimension_group: created {    type: time    timeframes: [date, week, month, quarter, year]    sql: ${TABLE}.created_at ;;  }  dimension: region { type: string sql: ${TABLE}.region ;; }  dimension: category { type: string sql: ${TABLE}.category ;; }
  measure: count { type: count }  measure: revenue {    type: sum    sql: ${TABLE}.amount_jpy ;;    value_format_name: jpy_0  }  measure: aov {    type: number    sql: ${revenue} / NULLIF(${count}, 0) ;;  }}
explore: orders {  description: "注文データ。期間 / 地域 / カテゴリで自由に切れます"}
Pattern 2 が有効なケース

① 数百ダッシュ規模で運用: アナリスト・業務担当が日常的に GUI で切る。② 部署横断の利用: マーケ・営業・経営が同じ Explore を参照。③ 経営層の予算がある: Looker / 等 BI 製品ライセンスを既に取得済み or 取れる。 の継続観測: 月次・週次の定型ダッシュ需要が中心。❌ 向かない: BI 製品ライセンスがない / 取れない、コードファーストで運用したい、業務固有の複雑な操作(シミュレーション等)がある。

Pattern 3: dbt metrics(指標の正本管理)

指標の定義を dbt の `metrics:` に書くと、全クライアント(Looker / Streamlit / SQL)から同一定義で参照できる。「営業の言う売上」と「経理の言う売上」が違う問題を構造的に防ぐ。

dbt の metrics 定義
YAML
version: 2metrics:  - name: revenue    label: "売上 (JPY, 税込)"    model: ref('orders')    description: "確定済み注文の合計金額。返金は減算済み"    type: simple    type_params:      measure:        name: amount_jpy        agg: sum    filter: "{{ Dimension('orders__status') }} = 'completed'"
  - name: monthly_active_customers    label: "MAC"    type: simple    type_params:      measure:        name: customer_id        agg: count_distinct    time_granularity: month
Pattern 3 が有効なケース

① 部署で数字が食い違う問題が深刻: 「営業の売上 vs 経理の売上」が違って報告会で揉める。② 複数 BI ツールを併用: Looker と Tableau と Slack Bot から同じ metric を参照したい。 を既に運用: dbt プロジェクトがある(無いと導入コストが高い)。④ 中〜大規模組織: 数十人以上が同じ指標を見る。❌ 向かない: dbt 未導入、metric 数が 5 個以下で十分、BI 1 つだけで完結する小規模。詳細は dbt ハンドブック EP.14 Semantic Layer

Pattern 4: Streamlit でテンプレ + UI

Streamlit で「期間別売上テンプレ」を GUI 化
Python
import streamlit as stimport pandas as pdfrom datetime import datefrom google.cloud import bigquery
st.title("📊 期間別売上テンプレート")
# 入力start = st.date_input("開始日", date(2027, 1, 1))end = st.date_input("終了日", date(2027, 12, 31))granularity = st.selectbox("粒度", ["day", "week", "month"])region = st.selectbox("地域", ["全国", "east", "west", "central"])
# テンプレ展開where_region = "" if region == "全国" else f"AND region = '{region}'"sql = f"""SELECT  DATE_TRUNC(created_at, {granularity.upper()}) AS period,  SUM(amount_jpy) AS revenueFROM proj.dwh.ordersWHERE _PARTITIONTIME BETWEEN '{start}' AND '{end}'  {where_region}GROUP BY 1 ORDER BY 1"""
if st.button("実行"):    bq = bigquery.Client()    df = bq.query(sql).to_dataframe()    st.line_chart(df.set_index("period")["revenue"])    st.dataframe(df)
Pattern 4 が有効なケース

① 業務固有のフローが必要: 「シミュレーション」「複数操作の組合せ」「結果に応じた次操作」など、Looker の Explore では作りにくい体験。② Python エンジニアが内製で組める: SQL だけでは表現しきれない計算(pandas/numpy 必要)が混ざる。③ 1 ツールに 1 ユースケースで深く: 「価格シミュレータ」「在庫発注計画」など、対象業務が明確。④ 認証付きで社内限定運用: Streamlit Community Cloud は SSO 弱め、Cloud Run + IAP / Streamlit in などの本番構成を組める前提。❌ 向かない: 「ダッシュ並べたい」だけ(Looker の方が早い)、Python エンジニアがいない、保守を業務担当に渡したい。

4 パターンの早見表

観点P1: SQL文字列P2: LookerP3: dbt metricsP4: Streamlit
主な利用者Bot 経由の業務担当アナリスト・業務担当アナリスト + BI クライアント業務担当(GUI 操作)
初期コスト低(ライブラリのみ)中〜高(Looker ライセンス)中(dbt セットアップ)中(Python 環境 + ホスティング)
運用コスト高(テンプレ管理)低( で集中管理)中(コード保守)
指標統一❌(テンプレごと)△(LookML 内)✅(最強)❌(コードごと)
業務複雑度単純集計ダッシュ系指標定義複雑な操作・シミュレーション
規模小〜中中〜大中〜大ピンポイント
ふくふくの定型構成

中規模組織 (200〜1000 人) には: dbt metrics で指標正本 + Looker で日常ダッシュ + Streamlit で業務固有フロー + Slack Bot で問合せ の 4 段重ね。スタートアップ (50 人未満) なら: + Slack Bot 軽量 から。dbt 未導入なら、まずそこから (EP.07)。

テンプレ運用の落とし穴

1. テンプレが増殖して結局カオス

Looker のダッシュボードが 1000 個。テンプレ自体に所有者・利用ログ・を持たせる。3 ヶ月利用ゼロのテンプレは アーカイブ

2. テンプレで満たせない問いが「アナリスト依頼」化

テンプレ既存のものから10%でも外れると、結局アナリスト依頼に。「テンプレ + ちょい追加」の組合せを許す(Looker の `Filters` / Streamlit の入力欄拡張)。

3. 古い指標定義のまま放置

事業の変化で指標の定義も変わる。dbt metrics で正本管理 + Glossary(EP.07-08)の整合性を保つ。

次の話

EP.10 では、誰が何を見たかを追う『監査ログと利用ログ』。事故対応・コンプライアンス・継続改善の基盤。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp