LASSIC Media らしくメディア
スタースキーマ設計の基本|ファクトとディメンションの分け方
監修・編集責任者:牛尾 昭昌(株式会社LASSIC 執行役員)
この記事の結論
- スタースキーマは、数値を持つファクト表を中心に、集計の切り口を持つディメンション表を周りに並べる分析用の設計です。
- 設計はファクトやディメンションより先に粒度を決め、粒度の違うデータを同じファクト表に混ぜないことから始めます。
- 属性の変化は列ごとにSCDのタイプ1(上書き)かタイプ2(行の追加)を選び、過去の実績をどの区分で見せるかを決めます。
※ 本記事は2026年10月時点の公式情報(省庁・公的機関、および製品やサービスを提供する事業者が公開している資料)に基づきます。
スタースキーマは、分析用のデータベースで、売上金額のような数値を記録するファクト表を中心に置き、日付・商品・店舗といった集計の切り口を持つディメンション表を周りに並べる設計です。図に描くと線が放射状に伸びて星の形に見えることから、こう呼ばれます。
ただ、ファクト表の1行が何を表すかを決めずに作り始めると、集計の数字が合わない、過去の実績が今の組織の区分で集計される、といった問題が後から出てきます。本記事では、Kimball Groupの解説、Microsoft・Google Cloudの公式ドキュメント、PythonのSQLiteで動かした例をもとに、分け方、粒度、履歴の持ち方(SCD)、スノーフレークスキーマとの違い、つまずきやすい点を整理します。
目次
スタースキーマとは
Kimball Groupは、次元モデルを、業務で起きた出来事の測定値と、それを説明する「誰が・何を・どこで・いつ・なぜ・どのように」という文脈とに分けて表すものと説明しています。次元モデルをリレーショナルデータベースの上に作ったものがスタースキーマで、ファクト表と、主キーと外部キーでつながったディメンション表から成ります。*1
Microsoft LearnのPower BIのガイドも、モデルの表をディメンションかファクトのどちらかに分類することを求めています。*2 レポートの表示はそれぞれ、データを絞り込み、グループにまとめ、集計する問い合わせを出すので、絞り込みとグループ化をディメンション表が、集計をファクト表が受け持つ形がよく合います。
図は、後の具体例で使う4つの表です。業務システムとの設計方針の違いは「OLTPとOLAPの違い」で、正規化の考え方は「正規化と非正規化の違い」で扱っているので、ここでは分析用の表の分け方に絞ります。
ファクト表とディメンション表の分け方
Kimball Groupによれば、ファクト表は現実の業務で起きた測定の出来事が生む数値を持ち、最も細かい粒度では、ファクト表の1行が1つの測定の出来事に対応します。*3 そのため、ファクト表の形は業務で実際に起きる出来事で決まり、後で作るレポートの形には左右されません。数値のほかに、関係するディメンションごとの外部キーを持ちます。
ディメンション表は、ファクト表の数値に文脈を与える表です。Kimball Groupは、ディメンション表を、値の種類が少ない文字列の属性を多く持つ、横に広く平らな非正規化の表と説明しています。*4 レポートの見出しに並ぶ「関東」「食品」のような文字も、たいていはこの属性の値です。
迷う列は、足し上げる対象か、絞り込みや見出しに使うかで分けます。売上金額や数量はファクト表へ、商品名や分類、店舗の地域はディメンション表へ置きます。Microsoftのガイドも、1つの表に両方の性質を混ぜないよう勧めています。
数値にも性質の違いがあります。Kimball Groupは、どのディメンションでも足し上げられる加算型、残高のように時間の方向には足せない半加算型、比率のような非加算型の3つに分けています。*5 比率は分子と分母を加算型の数値として持ち、集計した後で割ります。
粒度の決め方
粒度(グレイン)とは、ファクト表の1行が何を表すかの決まりです。Kimball Groupは、次元モデルの設計を、業務プロセスを選ぶ、粒度を宣言する、ディメンションを決める、ファクトを決める、という4つの判断の順で進めるとしています。*6 ディメンションやファクトより先に粒度を決めるのは、候補になる列がすべて粒度と矛盾しないことを確かめるためです。*7
同じ解説は、業務プロセスがデータを記録する最も細かい単位(原子的な粒度)から始めることを強く勧め、異なる粒度を同じファクト表に混ぜてはならないとしています。まとめた粒度は性能の調整には役立つものの、利用者がよく尋ねる問いを前もって決めてしまうからです。
売上なら、「レシートの明細1行につき1行」「レシート1枚につき1行」「店舗と日ごとに1行」のどれにするかを最初に決めます。店舗と日ごとの粒度にすると、後から商品別の数字は出せません。Microsoftのガイドも、ファクト表のデータは常にそろった粒度で読み込むことが重要だとしています。
具体例:SQLiteで売上を集計する
ここまでの分け方を、Pythonに標準で付いているsqlite3で確かめます。日付・商品・店舗の3つのディメンション表と、明細1行を1行とする売上のファクト表を作り、月別・分類別に集計します。
import sqlite3
con = sqlite3.connect(":memory:")
con.executescript("""
CREATE TABLE dim_date(date_key INTEGER PRIMARY KEY, ymd TEXT, month TEXT);
CREATE TABLE dim_product(product_key INTEGER PRIMARY KEY, name TEXT, category TEXT);
CREATE TABLE dim_store(store_key INTEGER PRIMARY KEY, store_id TEXT, region TEXT,
valid_from TEXT, valid_to TEXT, is_current INTEGER);
CREATE TABLE fact_sales(date_key INT, product_key INT, store_key INT,
receipt_no TEXT, qty INT, amount INT); -- 粒度:レシートの明細1行
INSERT INTO dim_date VALUES (20260310,'2026-03-10','2026-03'),(20260415,'2026-04-15','2026-04');
INSERT INTO dim_product VALUES (1,'コーヒー豆','食品'),(2,'マグカップ','雑貨');
INSERT INTO dim_store VALUES (1,'S01','関東','2020-01-01','9999-12-31',1),
(2,'S02','関西','2020-01-01','9999-12-31',1);
INSERT INTO fact_sales VALUES (20260310,1,1,'R1',2,2400),(20260310,2,1,'R1',1,1500),
(20260310,1,2,'R2',1,1200),(20260415,1,1,'R3',3,3600);
""")
q_star = """SELECT d.month, p.category, SUM(f.amount) FROM fact_sales f
JOIN dim_date d USING(date_key) JOIN dim_product p USING(product_key)
GROUP BY 1, 2 ORDER BY 1, 2"""
print(con.execute(q_star).fetchall())
日付の表の主キーは、20260310のような年月日の整数です。Kimball Groupは、ディメンションの主キーには業務システムのIDを使わず、1から順に振る意味を持たない整数(代理キー)を使うよう求めていますが、予測しやすく安定した日付のディメンションはこの決まりの例外としています。*8 手元のSQLite 3.49.1で実行した結果は次のとおりです。
| 月 | 商品の分類 | 売上金額(円) |
|---|---|---|
| 2026-03 | 雑貨 | 1500 |
| 2026-03 | 食品 | 3600 |
| 2026-04 | 食品 | 3600 |
ファクト表から日付と商品の表へ1回ずつ結合し、属性でグループにまとめて金額を足し上げているだけです。切り口を店舗の地域に変えるときも、結合する表とGROUP BYの列を差し替えれば済みます。
SCD:属性の変化と履歴の持ち方
店舗の所属地域や商品の分類のように、ディメンションの属性はゆっくりと、予定なく変わります。この変化の扱い方をSCD(Slowly Changing Dimension)と呼びます。Microsoftのガイドは、よく使われる型としてタイプ1とタイプ2を挙げ、列ごとに使い分けてもよいとしています。
タイプ1は、古い値を新しい値で上書きします。Kimball Groupは、実装が簡単で行も増えない一方、履歴を消してしまう方法であり、影響を受ける集計済みのファクト表やOLAPキューブを計算し直す必要があると注意しています。*9 タイプ2は、変更のたびにディメンションに新しい行と新しい代理キーを足し、その時点から後のファクトにはそのキーを入れます。足す列として、行の有効開始日、有効終了日、現在の行かどうかの印の少なくとも3つが挙げられています。*10
先ほどの続きで、店舗S02が2026年4月1日に関西から中部に移ったとします。古い行の有効終了日を3月31日にし、中部の新しい行を足してから、4月15日の明細を読み込みます。最後の2行は、比べるために上書きした場合です。
con.executescript("""
UPDATE dim_store SET valid_to='2026-03-31', is_current=0 WHERE store_key=2;
INSERT INTO dim_store VALUES (3,'S02','中部','2026-04-01','9999-12-31',1);
-- 4月の明細は、売れた日に有効だった行の代理キーを引いて入れる
INSERT INTO fact_sales SELECT 20260415, 2, s.store_key, 'R4', 2, 3000
FROM dim_store s WHERE s.store_id='S02'
AND '2026-04-15' BETWEEN s.valid_from AND s.valid_to;
""")
q = """SELECT d.month, s.region, SUM(f.amount) FROM fact_sales f
JOIN dim_date d USING(date_key) JOIN dim_store s USING(store_key)
WHERE s.store_id='S02' GROUP BY 1, 2 ORDER BY 1"""
print('タイプ2:', con.execute(q).fetchall())
con.execute("UPDATE dim_store SET region='中部' WHERE store_id='S02'") # 上書きした場合
print('タイプ1:', con.execute(q).fetchall())
| 履歴の持ち方 | 月 | 地域 | 売上金額(円) |
|---|---|---|---|
| タイプ2(行を足す) | 2026-03 | 関西 | 1200 |
| タイプ2(行を足す) | 2026-04 | 中部 | 3000 |
| タイプ1(上書き) | 2026-03 | 中部 | 1200 |
| タイプ1(上書き) | 2026-04 | 中部 | 3000 |
タイプ2では、移る前の3月の売上は関西のまま残ります。上書きすると3月の売上まで中部に数えられ、過去の地域別の実績が変わってしまいます。Microsoftのガイドも、営業担当者が別の地域に移る例を挙げ、ファクトの日付で有効だったキーを引いて読み込む必要があるとしています。顧客の電話番号のように過去の値で集計しない列はタイプ1、組織や分類のように当時の区分で見たい列はタイプ2が向いています。
スノーフレークスキーマとの違い
スノーフレークスキーマは、ディメンション表の中の階層を正規化して別の表に分けた形です。Kimball Groupは、階層の関係を正規化すると値の種類が少ない属性が2次の表として切り出され、これを繰り返すと多段の構造になると説明しています。*11 商品の分類を別の表に分けたのが、図の点線の部分です。
con.executescript("""
CREATE TABLE dim_category(category_key INTEGER PRIMARY KEY, category TEXT);
INSERT INTO dim_category VALUES (1,'食品'),(2,'雑貨');
CREATE TABLE dim_product_sf AS SELECT product_key, name,
CASE category WHEN '食品' THEN 1 ELSE 2 END AS category_key FROM dim_product;
""")
q_snow = """SELECT d.month, c.category, SUM(f.amount) FROM fact_sales f
JOIN dim_date d USING(date_key) JOIN dim_product_sf p USING(product_key)
JOIN dim_category c USING(category_key) GROUP BY 1, 2 ORDER BY 1, 2"""
print(con.execute(q_snow).fetchall())
print(con.execute(q_snow).fetchall() == con.execute(q_star).fetchall())
集計結果はスタースキーマの場合と同じで、最後の行は True になります。違うのは結合が1段増えることです。Kimball Groupは、平らなディメンションはスノーフレークと全く同じ情報を持つとしたうえで、利用者が理解しにくく、性能にも悪い影響を与えうるため、スノーフレークを避けるよう勧めています。
Power BIのガイドも、一般には1つの表にまとめる利点が分ける利点を上回るとし、分けると読み込む表が増え、複数の表にまたがる階層も作れないと挙げています。ただし、大きなディメンションでは重複の分だけ容量が増える可能性があるとも書かれています。
Google CloudのBigQueryのドキュメントは、入れ子と繰り返しのフィールドによる非正規化を勧める一方、スタースキーマは分析向けにすでに最適化された形であることが多く、さらに非正規化しても性能が大きく変わらない場合があるとしています。*12
つまずきやすい点
最も多いのは、粒度の違うデータを同じファクト表に入れることです。明細の売上の表に月ごとの売上目標の行を足すと、合計のたびに目標まで売上に数えられます。Microsoftのガイドは、日付とProductKeyを持つ売上目標の表で、日付の列に毎月1日の値しか無ければ粒度は月と商品になる、という例を挙げています。キーの列が同じでも、粒度が違えば別の表にします。
2つ目は、ファクト表どうしを直接結合することです。Kimball Groupは、出荷と返品のような2つのファクト表を顧客や商品の外部キーで直接結合すると、結果の行数を制御できず、誤った数字が返るとしています。*13 それぞれを別々に集計してから、共通の見出しの値で突き合わせます(ドリルアクロス)。
3つ目は、ファクト表ごとにディメンションを作り分けることです。売上と在庫で店舗の地域の区分が違うと、2つの数字を同じ行に並べられません。Kimball Groupは、別々の表の属性が同じ列名と同じ値の中身を持つことを適合(conformed)と呼び、複数のファクト表で使い回せば分析の一貫性と将来の開発の手間の削減が得られるとしています。*14
4つ目は、業務システムの店舗コードをそのままディメンションの主キーにすることです。タイプ2で行を足すと同じ業務IDの行が複数になるため、Kimball Groupもこれを業務IDを主キーにしない理由に挙げています。具体例で store_key と store_id を分けているのはこのためです。
外部に委託するときに確認しておきたい点
データウェアハウスやBIの構築を外部に頼むときは、まず、ファクト表ごとの粒度が文書で決まっているかを確かめます。1行が何を表すかが1文で書かれていない設計書は、後で数字が合わない原因になります。どのディメンションを共有するかの一覧があれば、適合ディメンションの抜けにも気づけます。
次に、SCDの型を列ごとにどう決めたかです。組織や分類が変わったときに過去の実績をどちらの区分で見せるかは、業務の担当者が決めることです。その結果を読み込みの処理(ETL)でどう実装し、どう試験するかまで確認します。
費用の考え方は「DWH構築のコスト削減」と「データ基盤構築を外注する費用と進め方」で扱っています。見積もりを比べるときは、業務プロセスと粒度の一覧、列ごとのSCDの型がそろっているかを先に見ると、範囲の違いが分かります。
まとめ:スタースキーマで確かめておきたい3つの点
スタースキーマで確かめておきたい点は3つです。第一に、足し上げる数値はファクト表へ、絞り込みや見出しに使う属性はディメンション表へ分けること。第二に、ディメンションやファクトより先に粒度を決め、粒度の違うデータを同じファクト表に混ぜないこと。第三に、属性の変化を列ごとにタイプ1かタイプ2で扱うと決め、業務IDとは別の代理キーを付けることです。
よくある質問
スタースキーマとスノーフレークスキーマは、どちらを選べばよいですか
Kimball GroupもPower BIのガイドも、ディメンションを平らにまとめたスタースキーマを基本として勧めています。元のデータが正規化されていても、読み込みの段階で1つのディメンション表にまとめられます。
タイプ2のディメンションで、今の値だけを見たいときはどうしますか
現在の行かどうかの印で絞り込みます。Microsoftのガイドは、現在の版の有効終了日を空か12/31/9999のような値にしておくことと、現在の版で絞り込むための印の列を持つことを挙げています。
日付のディメンション表は作るべきですか
Microsoftのガイドは、スタースキーマで最もよく見かける表が日付のディメンション表だとしています。週番号や会計期間、祝日かどうかを列に持たせておけば、問い合わせのたびにSQLで計算せずに済みます。
スタースキーマの設計とデータ基盤構築のご相談
元請(プライムベンダー)として、スタースキーマの設計や読み込み処理の実装から、データ基盤の開発と保守・運用までご提案します。
Remoguとリラシクなら、データ基盤の設計や構築に加わるITエンジニアも探せます。
Remoguは、リモート前提で全国から即戦力のITプロ人材を調達するサービスです。リラシクは、扱う求人がすべてリモートワークのITエンジニア専門転職エージェントです。どちらもLASSICが運営しています。
出典
- *1 参考:Kimball Group「Star Schemas and OLAP Cubes」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/star-schema-olap-cube/)。出典:次元モデルを測定値と「誰が・何を・どこで・いつ・なぜ・どのように」の文脈に分けること、スタースキーマがファクト表と主キー・外部キーでつながったディメンション表から成ることの説明を参照(2026年10月確認)
- *2 参考:Microsoft Learn「Understand star schema and the importance for Power BI」(https://learn.microsoft.com/en-us/power-bi/guidance/star-schema)。出典:ディメンション表とファクト表の分類、粒度の例(毎月1日の日付と商品の売上目標)、SCDタイプ1・2、スノーフレークディメンションを1つの表にまとめる利点と注意点の記述を参照(2026年10月確認)
- *3 参考:Kimball Group「Fact Table Structure」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/fact-table-structure/)。出典:ファクト表が測定の出来事の数値と各ディメンションの外部キーを持つこと、最も細かい粒度では1行が1つの出来事に対応することの説明を参照(2026年10月確認)
- *4 参考:Kimball Group「Dimension Table Structure」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/dimension-table-structure/)。出典:ディメンション表が値の種類の少ない文字列の属性を多く持つ横に広い非正規化の表であること、絞り込みとグループ化の対象になることの説明を参照(2026年10月確認)
- *5 参考:Kimball Group「Additive, Semi-Additive, and Non-Additive Facts」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/additive-semi-additive-non-additive-fact/)。出典:加算型・半加算型・非加算型の3分類と、非加算型は加算できる要素を持ち、最後に計算するという説明を参照(2026年10月確認)
- *6 参考:Kimball Group「Four-Step Dimensional Design Process」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/four-4-step-design-process/)。出典:業務プロセスの選択、粒度の宣言、ディメンションの特定、ファクトの特定の4つの判断を参照(2026年10月確認)
- *7 参考:Kimball Group「Grain」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/grain/)。出典:粒度をディメンションやファクトより先に宣言する理由、原子的な粒度から始める勧め、異なる粒度を同じファクト表に混ぜない原則を参照(2026年10月確認)
- *8 参考:Kimball Group「Dimension Surrogate Keys」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/dimension-surrogate-key/)。出典:業務システムのキーを主キーにしない理由、1から順に振る整数の代理キー、日付のディメンションの例外を参照(2026年10月確認)
- *9 参考:Kimball Group「Type 1: Overwrite」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/type-1/)。出典:タイプ1は上書きで履歴を消すこと、集計済みのファクト表やOLAPキューブの再計算が要ることを参照(2026年10月確認)
- *10 参考:Kimball Group「Type 2: Add New Row」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/type-2/)。出典:タイプ2は新しい行と新しい代理キーを足すこと、有効開始日・有効終了日・現在の行の印の3列を参照(2026年10月確認)
- *11 参考:Kimball Group「Snowflaked Dimensions」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/snowflake-dimension/)。出典:スノーフレークの成り立ち、平らなディメンションと同じ情報を持つこと、利用者の理解と性能の面から避けるべきとする記述を参照(2026年10月確認)
- *12 参考:Google Cloud「Use nested and repeated fields」(BigQuery)(https://cloud.google.com/bigquery/docs/best-practices-performance-nested)。出典:入れ子と繰り返しのフィールドによる非正規化の勧めと、スタースキーマはさらに非正規化しても性能が大きく変わらない場合があるとの記述を参照(2026年10月確認)
- *13 参考:Kimball Group「Multipass SQL to Avoid Fact-to-Fact Table Joins」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/multipass-sql/)。出典:ファクト表どうしを外部キーで直接結合すると結果の行数を制御できず誤った結果が返ること、ドリルアクロスの方法を参照(2026年10月確認)
- *14 参考:Kimball Group「Conformed Dimensions」(https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/conformed-dimension/)。出典:適合ディメンションの定義と、複数のファクト表で使い回すことの利点を参照(2026年10月確認)