派生列と命名規約
Stage 2 — 第2章 | dbt入門カリキュラム 推定学習時間:35〜45分 | 難易度:★★★☆☆
この章で学ぶこと
staging では、生のタイムスタンプ列に加えて cast(... as date) の日付列 を派生させることがよくあります。
これは SQL を 1 行短くするためではなく、「同じ日付の定義」を組織で共有する ための設計です。
dbt プロジェクトでは、列名も レイヤーごとのスコープ を持ちます。
staging ではソースに近い名前(attempted_date)、intermediate では横断的な名前(date_day)—— という使い分けが DS Playground でも採用されています。
この章を終えると、こんなことができるようになります:
- 日付派生列を staging で作る 理由と限界 を説明できる
*_at/*_dateの ペア命名 を読み書きできる- semantics を隠すリネーム・COALESCE を アンチパターン として見分けられる
- intermediate で 共通語彙 に揃える判断を説明できる
なぜ mart ではなく staging で日付を切るか
日次 KPI(DAU、会員数の日次推移など)は、ほぼ必ず 日付で GROUP BY します。
ここで mart ごとに cast(completed_at as date) を書くと、同じ「1 日」の境界がモデルごとに微妙にずれ、列名も event_date / day / dt と乱立します。
mart ごとに日付定義"] M1["mart A
cast(a as date)
event_date"] M2["mart B
cast(b as date)
day / dt"] GOOD["推奨
staging で1つの定義"] STG["staging
captured_date"] MA["mart 1
captured_date を利用"] MB["mart 2
captured_date を利用"] BAD --> M1 BAD --> M2 BAD -.->|"定義を一箇所へ"| GOOD GOOD --> STG STG --> MA STG --> MB classDef bad fill:#fef2f2,stroke:#dc2626,color:#1f2937 classDef good fill:#f0fdf4,stroke:#16a34a,color:#1f2937 class BAD,M1,M2 bad class GOOD,STG,MA,MB good
staging で 1 回だけ 日付列を派生させ、下流は captured_date や attempted_date を信頼する——これが一般的な規約です。
データアナリティクス入門の日付集計 で学んだ「分母・分子の日付定義を揃える」と同じ発想を、列レベル で固定しています。
命名規約:*_at と *_date
| サフィックス | 型の想定 | 用途 |
|---|---|---|
*_at |
timestamp(時刻付き) | 順序付け、精緻な時系列 |
*_date |
date(日付のみ) | 日次集計、カレンダー JOIN |
DS Playground では staging 横断でこのペアが使われます。
-- stg_supabase__consents
captured_at,
cast(captured_at as date) as captured_date
-- stg_supabase__problem_attempts
attempted_at,
cast(attempted_at as date) as attempted_date
名前に「何の日付か」を入れる
指標定義 で「売上」と一語で呼ばないのと同様、日付列も イベント名を冠 します。
| 良い例 | 悪い例 | 理由 |
|---|---|---|
captured_date |
date1 |
何の日付か不明 |
completed_date |
active_day |
「アクティブ」の定義が別途必要 |
paid_date |
conversion_date |
会計・マーケで意味が割れやすい |
「BI で見やすい短い名前」は、staging では ソース semantics を上書き しがちです。
ビジネス向けのラベルは mart の description や BI の表示名に任せ、staging では ソースに近い名前 を維持するのが安全です。
semantics を隠す派生 — アンチパターン
過度な COALESCE
-- アンチパターン: 異なる意味の時刻を 1 列に潰す
coalesce(completed_at, last_read_at, updated_at) as activity_at
「完了」「閲覧」「更新」が 1 列に混ざると、Metabase で「activity_at とは?」が再発します。
DS Playground では、イベント統合は intermediate の int_user_activity_events が activity_type 付きで UNION します。意味の混在は staging ではなく、型を揃えた統合層 で明示します。
意味の変わるリネーム
-- アンチパターン
is_granted as is_member
is_granted は 特定 consent_type の付与 であり、「会員であること」全体ではありません。
terms 同意ベースの会員 proxy は別モデル(int_member_status_current)が担います。staging で語彙をすり替えると、下流が 誤った前提 で JOIN します。
ルールを明示する派生:normalized_version
すべての派生列が「日付 cast」だけではありません。 ビジネスルールを含む派生 も staging に置けますが、その場合は ルールを隠さない命名と YAML が必須です。
stg_supabase__skill_check_results では、スキルチェックの version 列に null や空文字がある場合、旧版 genege-v3 とみなす normalized_version 列を派生させています。
| 列 | 役割 |
|---|---|
version |
生の値(null あり)—— 上書きしない |
normalized_version |
明示的 fallback ルール付きの派生列 |
# _staging__models.yml(抜粋)
- name: normalized_version
description: "空/nullのversionを旧版 `genege-v3` とみなして正規化したスキルチェックバージョン。"
data_tests:
- not_null
ポイントは 3 つです。
- 生列
versionを残し、加工前の状態を追える - 接頭辞
normalized_*で 加工列である ことを示す - description に fallback ルールを書く(次章 の test とセット)
singular test assert_skill_check_version_supported は、新バージョン追加時の 変更検知 として機能します(Stage 2 第4章)。
intermediate での共通語彙:date_day
複数 staging を UNION する intermediate では、ソースごとに異なる日付列名を 横断的な名前 に揃えます。
int_user_activity_events の一部(概念):
select
user_id,
attempted_at as occurred_at,
attempted_date as date_day,
'practice_problem_saved' as activity_type
from {{ ref('stg_supabase__problem_attempts') }}
union all
select
user_id,
completed_at as occurred_at,
completed_date as date_day,
'curriculum_page_completed' as activity_type
from {{ ref('stg_supabase__user_curriculum_page_progress') }}
where status = 'completed'
and completed_at is not null
| 列 | スコープ |
|---|---|
occurred_at |
統合後のイベント時刻(ソースごと元列名は異なる) |
date_day |
日次 grain 用の共通名(intermediate 以降) |
activity_type |
何のイベントか(enum) |
staging では attempted_date のように ソース固有名 を保ち、intermediate で date_day に 昇格 する。
名前のスコープがレイヤーごとに変わる、というのが dbt 設計の読みどころです。
attempted_date"] S2["staging
completed_date"] INT["intermediate
date_day"] MART["mart
GROUP BY date_day"] S1 --> INT S2 --> INT INT --> MART
タイムゾーンと caveats
cast(timestamp as date) は、データベースセッションのタイムゾーン解釈に依存します。
厳密な JST 日次境界が必要なプロジェクトでは、AT TIME ZONE や macro で 明示的に 境界を固定します。
DS Playground の overview では、カレンダー spine 未導入など 運用上の TODO も docs に残されています。 良い dbt プロジェクトは、数字だけでなく 「この日付列をどう読んではいけないか」 も YAML / overview に書きます。
体系コラム:カリキュラム上の位置づけ
| 項目 | 内容 |
|---|---|
| Stage / 章 | Stage 2 — 第2章 |
| 今回の論点 | 派生列は 明示的命名 で semantics を守る |
| 前章との接続 | staging 正規化 |
| 次章への伏線 | schema tests |
まとめ
派生列は「SQL を短くするため」ではなく、同じ定義を組織で共有するため に存在します。
DS Playground では *_at / *_date のペア、intermediate の date_day、ルール付き normalized_* が、その実装例です。
関連教材
- 前章: staging 正規化
- 次章: schema tests
- 関連: 日付集計
確認問題
問題 1
staging で cast(order_placed_at as date) as order_placed_date を作る 主な理由 として最も適切なのはどれですか。
A. timestamp 型を禁止しているから
B. 日次 mart など下流が共通の日付列定義を再利用できるようにするから
C. データウェアハウスが date 型しか返さないから
D. seed ファイルとの JOIN のためだけ
正解: B
問題 2
次のうち 命名として最も問題が大きい のはどれですか。
A. order_source(元 source 列)
B. is_shipped as is_paid
C. order_placed_date
D. normalized_status
正解: B
解説: 出荷済みと支払済みは別概念です。staging で語彙をすり替えると下流の前提が壊れます。
問題 3
複数 staging を UNION する intermediate で date_day という列名を使う 主な理由 はどれですか。
A. staging で必ず date_day という名前を付ける規則だから
B. ソースごとに異なる日付列名を 横断的な日次 grain 名 に揃えるため
C. BI ツールのパラメータ名だから
D. seed CSV の列名だから
正解: B
用語メモ(この章)
| 用語 | 意味(この章での使い方) |
|---|---|
| 派生列 | 既存列から計算した列 |
| semantics | 列が「何を意味するか」 |
*_at / *_date |
時刻付き / 日付のみのペア命名 |
date_day |
intermediate 以降の共通日次列名 |
normalized_* |
明示的ルール付きの派生列 |