緩やかに変化する次元(SCD, Slowly Changing Dimension)
1. 概要
A. 定義
緩やかに変化する次元(SCD, Slowly Changing Dimension)とは、データウェアハウスの次元(Dimension)テーブルにおいて属性値が時間とともに緩やかに変化する際、その変化をどのように保存・追跡するかを規定する次元モデリング技法である。変更履歴の保存可否とその方式に応じてType 0からType 6まで類型化する。
ラルフ・キンボール(Ralph Kimball)が体系化したこの概念の核心的な問いは、単純でありながら強力である。「顧客の住所がソウルから釜山に変わったとき、過去の注文はソウルで発生したものとして残すべきか、それともすべて釜山に変えるべきか」。この問いへの答えが分析結果の正確性を左右する。次元はファクト(Fact, 測定値)に文脈を与える「誰が・何を・どこで・いつ」に相当し、その文脈が変わるときにそれをどう扱うかがすなわち履歴管理戦略である。
SCDは「緩やかに(slowly)」という語が核心である。商品分類、顧客等級、組織所属、地域コードのようにまれに、不定期に変わる属性が対象であり、毎秒数千件ずつ流入する取引データ(ファクト)とは性質が異なる。急速に変わる属性はむしろファクトへ分離するか、別途のミニ次元として抽出するのが定石である。
B. 登場背景と必要性
従来の運用系(OLTP)データベースは「現在の状態」のみを管理する。顧客が住所を変えると既存レコードをUPDATEで上書きし、過去の住所は跡形もなく消える。運用業務には最新値さえあれば十分なので、これは合理的である。しかし分析系(OLAP)では話がまったく異なる。「地域別売上推移」を分析する際に顧客の過去の住所が消えていれば、3年前の売上を現在の住所基準で歪めて集計することになり、歴史的事実(historical truth)が損なわれる。
この問題は特に規制・監査の領域で致命的である。金融の信用格付変動履歴、保険の被保険者属性変化、医療の患者状態追跡は「その時点でどの値であったか」を正確に再現できなければならない。単純な上書きはこうした時点再現(point-in-time reconstruction)能力を根本的に取り除く。SCDはまさにこの間隙 — 運用系の「現在のみ維持」と分析系の「履歴保存要求」との間 — を埋めるために登場した。
さらにデータウェアハウスが全社的意思決定の単一の真実の源泉(Single Source of Truth)として定着するにつれ、次元変更を一貫した規則なしにその都度処理すれば、分析結果が分析者・クエリごとに異なる混乱が生じる。SCDは変更処理方式を類型として標準化し、どの属性は履歴を残しどの属性は上書きするかを設計時点で明示的に決定させる。これにより再現可能かつ監査可能な分析基盤が確保される。
2. 次元モデルにおけるSCDの位置と全体構造
SCDを理解するには、まず次元モデル(スタースキーマ)における次元とファクトの関係を見なければならない。ファクトテーブルは測定値(売上額・数量)とともに各次元を指す外部キーを持ち、この外部キーが運用系の自然キー(business key, 例: 顧客番号)ではなくサロゲートキー(surrogate key)である点がSCDの出発点である。サロゲートキーはDWが自ら生成する無意味な連番であり、「同じ顧客の異なる時点のバージョン」を別個の行として区別できるようにする。履歴を残すほぼすべてのSCD類型はこのサロゲートキーの上に成立する。
flowchart LR
subgraph SRC["運用系(OLTP)"]
OP[("顧客ソーステーブル<br/>(現在値のみ, UPDATE)")]
end
OP --> ETL["ETL / ELT ロード<br/>(変更検知・類型別処理)"]
subgraph DW["データウェアハウス(スタースキーマ)"]
DIM[("顧客次元<br/>(サロゲートキー・履歴バージョン)")]
FACT[("注文ファクト<br/>(測定値・次元FK)")]
FACT -->|"サロゲートキー参照"| DIM
end
ETL --> DIM
ETL --> FACT
DIM --> BI["BI / 分析<br/>(時点再現・趨勢分析)"]
FACT --> BI
上記構造の核心はETL段階の変更検知(change detection)である。ETLはソースの現在値とDWにすでにロードされた次元の最新バージョンを比較し、(1) 新規キーなら挿入、(2) 追跡対象属性が変わっていれば類型別規則に従って処理、(3) 変更がなければ通過させる。このとき「どの属性が追跡対象であり、各属性をどのTypeで扱うか」が設計の成果物であり、一つの次元の中でも属性ごとにTypeを混在させるのが一般的である(これが後述するType 6の基盤となる)。
変更検知の正確性を高めるため、実務では各属性のハッシュ値(hash diff)を保存しておきソースハッシュと比較するか、第2章で見たCDC([[change-data-capture]])から変更イベントを受け取って処理する。前者はバッチ比較に、後者は準リアルタイムロードに適する。いずれの方式でも「何が変わったか」を安定的に判別してはじめてSCD規則を正しく適用できる。
ここで必ず区別すべき二つの時間概念がある。一つは有効時間(valid time) — その値が現実で有効であった期間 — であり、もう一つはロード時間(transaction/load time) — DWがその事実を知った時点 — である。ソース遅延や遡及訂正のために二つの時間はずれうるため、両方を追跡する設計を双方向時間性(bitemporal)と呼ぶ。規制・監査要件が「現実でいつ有効だったか」と「我々がいつそう記録したか」の両方を要求する領域では、Type 2の有効期間にロード時刻列を追加した双方向次元設計が必要となる。
3. SCD類型別の処理方式 (Type 0 ~ 6)
SCDの類型は「変更をどのように保存するか」という一つの軸に沿って配列される。最も保守的なType 0(不変)から出発し、上書き(Type 1)、行追加による完全履歴化(Type 2)、限定的履歴(Type 3)、そしてそれらの結合(Type 6)へと発展する。以下では各類型がどのような状況のために考案され、どのような代償を払うかを順に述べる。
flowchart TB
START["次元属性の変更発生"] --> Q0{"変更を反映すべきか?"}
Q0 -- "いいえ(不変属性)" --> T0["Type 0<br/>原本値を恒久保存"]
Q0 -- "はい" --> Q1{"過去履歴は分析に必要か?"}
Q1 -- "不要(最新のみ)" --> T1["Type 1<br/>既存値を上書き"]
Q1 -- "必要(完全履歴)" --> T2["Type 2<br/>新バージョン行を追加"]
Q1 -- "直前値程度のみ" --> T3["Type 3<br/>以前値の列を追加"]
T2 --> T6["Type 6<br/>1+2+3 混合<br/>(現在・履歴を同時に支援)"]
T3 --> T6
A. Type 0 — 原本固定(Retain Original). 一度記録した値を絶対に変えない方式である。顧客の初回加入日、生年月日、固有識別子のように定義上不変であるか、不変とみなすべき属性に適用する。ソースで値が変わってもDWはこれを無視するか別途エラーに分類する。実装は最も単純だが、「不変」の判断が誤ると(例: 初期データ入力誤りを永遠に直せなくなる)かえって品質問題を固着させるため、真に変わってはならない属性にのみ限定的に適用する。
B. Type 1 — 上書き(Overwrite). 新しい値で既存値をUPDATEして過去を捨てる方式である。運用系の挙動と同一で最も直感的であり、次元の行数が増えないため保存・クエリコストが低い。電話番号の誤字修正、分類コード表記の訂正のように過去履歴が分析的に無意味であるか単純な誤り訂正である場合に適する。しかしType 1で上書きすると、その属性基準の過去ファクト集計が遡及変更(retroactive restatement)される。すなわち3年前の売上を今の分類基準で再集計することになり、「その当時の実際の姿」を再現できない。この遡及効果が許容されるか否かがType 1適用の判断基準である。
C. Type 2 — 行追加による履歴保存(Add New Row). SCDの事実上の標準であり最も広く使われる方式である。属性が変わると既存行を上書きせず新しいサロゲートキーを持つ新行を追加する。同じ自然キー(顧客番号)を共有しつつサロゲートキーが異なる複数バージョンが共存し、各バージョンは有効開始日(effective_date)・有効終了日(end_date)・現在フラグ(is_current)といったメタ列で区別される。新バージョンが生じると直前バージョンのend_dateを閉じ、is_currentをオフにする。この方式の最大の長所は完全な時点再現である。ファクトが発生した時点のサロゲートキーをそのまま参照するため、過去の注文は永遠に「その当時の顧客のソウル住所バージョン」に結びつく。代償は次元行数の増加と「現在バージョンのみ照会」クエリの複雑度(フラグ・日付条件が必要)である。金融の信用格付、保険の契約状態、組織所属変更など履歴がそのまま規制要件である領域で必須的に使われる。
D. Type 3 — 以前値の列を追加(Add New Attribute). 行ではなく列を追加して現在_地域と直前_地域を並べて置く方式である。全面的な組織改編のように「変更の直前と直後を同時に比較」しなければならないが、完全な履歴までは不要な場合に使う。保存コストが少なくクエリが単純だが、直前1段階(あるいは固定N段階)のみ保存するため二回以上の変更履歴は失われる。したがって変更がまれで「以前↔現在」比較が分析の核心である特殊状況に限られる。
四つの類型の性格の違いを整理すると次のとおりである。表は比較の補助手段にすぎず、実際の選択は上で説明した「履歴の必要性・遡及許容の可否・保存/クエリコスト」の均衡で決まる。
| 区分 | 変更処理 | 履歴保存 | 過去ファクトへの影響 | 行/列の増加 | 代表的適用 |
|---|---|---|---|---|---|
| Type 0 | 反映しない | 原本固定 | なし | なし | 生年月日・加入日 |
| Type 1 | 上書き | なし(現在のみ) | 遡及変更される | なし | 誤字・コード訂正 |
| Type 2 | 新行追加 | 完全保存 | 不変(時点固定) | 行増加 | 信用格付・契約状態 |
| Type 3 | 列追加 | 直前値のみ | 混在 | 列増加 | 組織改編の前後比較 |
| Type 6 | 1+2+3結合 | 完全+現在同時 | 選択的 | 行+列増加 | 現在・時点の同時分析 |
E. Type 4 · Type 6 — 結合型. Type 4は現在値をメイン次元に、履歴を別途のヒストリーミニ次元に分離し、現在照会の性能と履歴保存を同時に狙う。Type 6(名称は1+2+3=6に由来)は一つの次元にType 2のバージョン行、Type 1で常に最新値を維持する「現在値」列、そしてType 3式の以前値列をともに置く。こうすると同じテーブルから「その時点の値」(バージョン行)と「この顧客の現在値」(current列)の両方を照会でき、たとえば「過去のソウル居住時代の注文を、現在釜山に住む顧客の観点でもまとめて見る」二重分析が可能になる。柔軟だがETLロジックが複雑になるのが短所である。
4. 実装アーキテクチャとETL処理技法
実務ではSCDはETL/ELTパイプラインの次元ロード段階として実装される。最も典型的なパターンはSQLのMERGE(UPSERT)文で、ソースの現在スナップショットとDW次元を結合し、新規はINSERT、変更は類型別に処理する。Type 2の場合、単一のMERGEでは「既存バージョンを閉じる+新バージョンを開く」を一度に処理しにくいため、通常① 変更された自然キーの現在行のend_dateを閉じるUPDATE → ② 新バージョンのINSERTの二段階に分ける。この順序が逆転すると一つの自然キーにis_current行が二つ生じる整合性エラーが発生するため、トランザクションで括って原子性を保証しなければならない。
sequenceDiagram
participant S as ソーススナップショット
participant E as ETLエンジン
participant D as 次元テーブル
S->>E: 現在の顧客レコードを渡す
E->>D: 自然キーで現在バージョン(is_current=Y)を照会
alt 新規自然キー
E->>D: 新しいサロゲートキーでINSERT
else 追跡属性の変更(Type 2)
E->>D: 既存行のend_dateを設定・is_current=N
E->>D: 新バージョン行をINSERT(is_current=Y)
else 非追跡属性の変更(Type 1)
E->>D: 現在行をUPDATE(上書き)
else 変更なし
E-->>E: 通過(スキップ)
end
変更検知の信頼性を高めるため、実務では追跡対象属性を連結してハッシュ(例: SHA-256)指紋を作り次元行に保存する。ロード時にソースハッシュと保存ハッシュを一度だけ比較すれば、多数の列を逐一突き合わせずとも変更の有無を即座に判別でき、数千万行の次元の比較コストを大きく削減する。また有効期間のend_dateを実際の終了時刻の代わりに9999-12-31のような無限大の標識として置けば、「現在有効」の条件を範囲結合で単純に表現できる。
サロゲートキー設計も重要な実装ポイントである。運用系の自然キーをそのままファクトFKに使うと履歴バージョンを区別できないだけでなく、ソースシステム統合・キー再利用・NULL処理に脆弱になる。サロゲートキーはこうした運用系の変化からDWを絶縁(insulation)する役割を果たし、未知(unknown)・未到着(late arriving)などの特殊状況のための予約キー(例: -1)をともに運用する。ソースよりファクトが先に到着する遅延到着次元(late arriving dimension)問題は、一時的なプレースホルダ行を先に作り後で実際の値で埋める方式で処理する。
5. 類型選択の比較と適用事例
同じ「住所変更」であっても分析目的に応じて正しいTypeが異なる。この違いが生じる根本的な理由は、SCDがデータ保存技法ではなく「過去をどう解釈するか」というビジネス規則の実装だからである。以下の三つの事例はこの点を明確に示す。
第一に、金融の信用評価事例ではType 2が事実上強制される。顧客の信用格付がBBBからAに上方修正されたとき、過去の融資がどの格付時点で実行されたかを再現できなければ、監督当局のリスク再現要求(例: 特定四半期末のポートフォリオの格付分布)を満たせない。実際にバーゼル規制対応DWは格付・限度・担保属性をすべてType 2でロードし、任意の過去時点のエクスポージャを正確に再構成する。
第二に、EC商品カタログ事例では属性ごとにTypeを混合する。商品名の誤字修正はType 1(上書き)で十分だが、商品カテゴリの再分類はType 2で残してこそ「再分類以前基準の四半期売上」と「再分類以後基準」の両方を分析できる。一つの商品次元の中で数十個の属性がType 0/1/2で混在するのが普通であり、この設計文書そのものがデータガバナンスの成果物となる。
第三に、人事・組織分析事例ではType 3またはType 6が有用である。大規模な組織改編の直後に「改編前の所属基準の成果」と「改編後の所属基準の成果」を並べて比較しなければならないとき、直前所属列(Type 3)やcurrent/historical列の併存(Type 6)が分析者のクエリを大きく単純化する。ただし改編が二回以上繰り返されるとType 3だけでは中間履歴が失われるため、Type 2の併用が避けられない。
この三つの事例が共通して示唆するのは、誤ったType選択が静かなデータ誤りにつながるという点である。履歴が必要な属性をType 1で上書きすれば過去レポートが遡及的に改ざんされるが、システムは何らエラーを出さず、分析者は数値が変わった事実すら知らぬまま誤った意思決定を下しうる。逆に単純訂正属性をType 2で処理すれば意味のないバージョンが積み上がり次元が肥大化する。SCD設計レビューがデータ品質管理の核心的活動として扱われるべき理由がここにある。
6. 深化 — レイクハウス時代のSCDと最新動向
伝統的なSCDはRDBMSベースのDWを前提に発展したが、データレイクハウス([[data-lakehouse]])とELTパラダイムの拡散はその実装様相を変えつつある。かつてはETLツールが行を直接UPDATE/INSERTしたが、オブジェクトストレージ上のファイルは伝統的なUPDATEが高価であるか不可能であった。これを解決したのがDelta Lake・Apache Iceberg・Apache Hudiのようなオープンテーブルフォーマットで、これらはACIDトランザクションとMERGE INTOを支援し、レイク上でもType 2ロードを宣言的に遂行できるようにした。特にIceberg・Deltaのタイムトラベル(time travel)はテーブルスナップショット自体にバージョンを付与するため、一部の時点再現要求をSCDなしにストレージ層で解決する補完手段ともなる。
変換層ではdbtのsnapshot機能がSCD Type 2を事実上標準化した。分析エンジニアが追跡戦略(check/timestamp)と追跡列のみを宣言すれば、dbtが有効期間・現在フラグ管理と変更検知を自動生成する。これはSCDロジックを手で実装していた過去に比べ誤りを大きく減らし、バージョン管理・テストが可能なコード型データパイプライン(Analytics Engineering)の核心構成要素となった。
一方、SCDと対をなす設計課題として急変次元(RCD, Rapidly Changing Dimension)の処理がある。顧客の年齢帯・購買傾向スコアのように頻繁に変わる属性をType 2に置くとバージョンが爆発的に増える。このときは急変属性を本次元から切り離してミニ次元(mini-dimension)に分離し、ファクトが二つの次元をそれぞれ参照するよう設計する。SCDが「緩やかな変化」を扱う技法である以上、その境界外の速い変化を識別し分離する判断もまた次元設計者の役割である。
データベース標準の側面では、SQL:2011が導入したシステムバージョン時間テーブル(system-versioned temporal table)が注目される。PostgreSQL・SQL Server・MariaDBなどが支援するこの機能はFOR SYSTEM_TIME AS OF構文で過去時点の照会を言語レベルで提供し、アプリケーションの観点ではDBMSがType 2に類似した履歴保存を透過的に処理する。ただしこれは「行全体」のシステム時間履歴であるため、分析モデリングで要求される「属性別の追跡戦略の混合」やサロゲートキーベースのファクト連結とは志向が異なり、分析DWのSCDを完全に代替するよりは補完する性格が強い。
7. 考慮事項および示唆点 (技術士の観点)
SCDは単なるロード技法ではなく、データウェアハウスの信頼性・監査可能性・分析正確性を左右する設計決定であるため、技術士の観点では以下を総合的に考慮しなければならない。
- 適用戦略(属性単位のType マッピングの明示化): 次元全体ではなく属性ごとにTypeを決定し、これをデータ辞書・ガバナンス文書で明文化しなければならない。「どの変更が歴史的事実でどの変更が単純訂正か」はITではなく現業が答えるべき問いであるため、ビジネスとの合意過程を設計プロセスに含めなければならない。
- トレードオフ(履歴の完全性 ↔ 保存・クエリコスト): Type 2は履歴を完璧に保存する代わりに行の激増と「現在バージョン照会」クエリの複雑度を伴う。変更頻度の高い属性を無分別にType 2に設定すると次元が肥大化するため、高頻度属性はミニ次元分離やファクト化で設計を調整し均衡を取らなければならない。
- 整合性・原子性の保証: Type 2の「既存バージョンを閉じる+新バージョンを開く」は必ずトランザクションで括り、is_current重複・期間重複(overlap)・空白(gap)が生じないようにしなければならず、ロード後の有効期間連続性検証をデータ品質テストとして自動化しなければならない。冪等性([[idempotency]])の確保により再ロード時の重複バージョン生成を防ぐことも必須である。
- 展望と連携技術: CDC([[change-data-capture]])と結合すればバッチではなく準リアルタイムのSCDが可能となり、レイクハウスのオープンテーブルフォーマット・dbt snapshot・システムバージョンテーブルはSCD実装を宣言的・標準的に引き上げている。ただしどの技術を使っても「過去をどう解釈するか」というビジネス規則そのものは自動化されないため、技術選択より履歴ポリシーの明確な定義が先行しなければならない。
参考資料
- Kimball Group, "Slowly Changing Dimension (SCD)" — https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/
- dbt Labs, "Snapshots" — https://docs.getdbt.com/docs/build/snapshots
- Delta Lake Documentation, "Slowly changing data (SCD) Type 2 operation into Delta tables" — https://docs.delta.io/latest/delta-update.html
一言まとめ: SCDはデータウェアハウス次元の緩やかな属性変化をどのように保存・追跡するかを規定する技法であり、上書き(Type 1)・行追加による完全履歴(Type 2)・以前値列(Type 3)・混合(Type 6)を属性単位で選択して「過去をどう解釈するか」というビジネス規則を実装する。