「データウェアハウス」を取り巻く中核キーワード群です。 検索やインデックス作成で参照する際の手がかりにしてください。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になります。
🍰 まずはやさしく
分析に特化した巨大な倉庫のようなものです。
大量のデータをまとめて分析するために使います。
お店の全期間の売上を計算する時に役立ちます。
この章ではデータウェアハウスの結論を読みます。
最も忙しい読者のために、 まず結論だけまとめます。 詳細は以下のセクションへ:
🍰 まずはやさしく
分析専用にデータを集める場所のことです。
いつもの作業を止めずに分析するために使います。
スマホアプリの利用状況をまとめて調べる時に便利です。
この章ではどのような場面で使うかを読みます。
「全店舗・全期間の売上を月次で集計したい」 「経営ダッシュボード用に異なる DB を統合したい」 — 通常の業務 DB ではクエリが何時間もかかり業務に支障。 DWH に 分析専用の場所 として集約します。
このページの読み方:まず 30秒結論 と 直感 を読み、 必要に応じて 数式 や 計算例、 落とし穴 に進んでください。
🍰 まずはやさしく
個人の本棚ではなく大図書館のようなものです。
バラバラな場所にあるデータをまとめて探すために使います。
部活の過去の記録をすべて一箇所に集めるイメージです。
この章では仕組みの直感的なイメージを読みます。
図書館に喩えると:
特徴:
DWH の定番設計 スタースキーマ は、中心の ファクトテーブル(事実=売上明細などの数値)と、周囲の ディメンションテーブル(軸=日付・店舗・商品)から成ります。下の図で ディメンションをタップ/クリック すると、その軸での集計(店舗別・年別/月別・カテゴリ別/商品別)が即時に実行され、表とグラフ、そして「業務DBとの走査コスト比較」が更新されます。
※ ここで集計する売上データは 架空 のデモ用データです(ページ内の JavaScript が固定シードの擬似乱数で 24ヶ月 × 3店舗 × 6商品 = 432 行 の明細を生成。集計値はその明細の正確な合計です。SSDSE の実測値ではありません)。
💡 店舗別・年別・カテゴリ別の総計はすべて一致します(同じ 432 行の明細を別の軸で切り直しているだけ)。月別/商品別のドリルダウン中は選んだ年・カテゴリの部分合計になります。
🧾 いま実行している集計クエリ(イメージ)
同じ 432 行分の売上を、 (a) 正規化された業務DB(注文・明細・店舗・商品・カテゴリの 5 テーブル、行指向=1 行を丸ごと読む)と、 (b) DWH のスタースキーマ(列指向=必要な列だけ読む)に置いた場合の、上の集計 1 回あたりのコストです(テーブル形状から機械的に計算した簡略モデル。セル数 = 走査する行数 × 読む列数)。
業務DBは「伝票を正確に書き溜める帳場」、DWH は「伝票を分析しやすい棚に並べ直した倉庫」です。倉庫の棚の設計原則がスタースキーマで、ファクト=測りたい事実(数量・金額などの数値)、ディメンション=事実を切る軸(いつ・どこで・何を) に役割を分けます。上の操作で体感できるように、どの軸で切っても 結合は常にファクト↔ディメンションの 1 段 で済み(主キー/外部キーの参照 1 本)、集計 SQL がほぼ同じ形になる — この「どの質問にも同じ手順で答えられる」規則性こそが DWH の価値です。データは ETL/ELT でこの形に整えてから格納します。
🍰 まずはやさしく
決まったルールでデータを貯める仕組みです。
時間の経過による変化を正しく分析するために使います。
地域の人口の変化を年ごとに記録するようなものです。
この章では定義や詳しい性質について読みます。
Bill Inmon の定義に基づき、 DWH は 主題志向(顧客・売上などテーマ単位)/統合(異種ソースを一元化)/非揮発(履歴を消さない)/時系列(時刻属性を持つ)の 4 性質を備えるシステムです。
| 記号 | 意味 | SSDSE-B-2026 での例 |
|---|---|---|
Subject | 主題(顧客・売上・人口など分析テーマ) | SSDSE-B-2026 では『47 都道府県 × 年 × 統計指標』が主題 |
integrated | 異種ソースの統合 | 国勢調査・住民票・住民基本台帳を 1 つの fact テーブルに統合 |
t | 時刻属性。 非揮発(履歴)を可能にする変数 | 2012〜2023 の 12 年分を縦持ちで保持 |
∫ | 時間軸での蓄積(イメージ) | 毎年 ETL で新年データを追加していく操作 |
非揮発性 | 一度入ったデータは消さない | 誤り訂正は新行追加で行い、 元データを残す |
時系列 | 時刻でスライス可能 | WHERE year=2023 で 47 行、 WHERE pref='東京都' で 12 行 |
市区町村ごとの『住民票』『税収』『学校』のシステムが別々に動いている自治体を想像してください。 そのままでは『高齢化と税収の関係』を分析できません。 DWH は夜間 ETL でこれらを 都道府県×年×指標 という共通フォーマットに統合し、 何年でも遡って集計できるようにします。
🎯 このコードでやること:SSDSE-B-2026 から 2019-2023 年の 5 年分を縦持ち(年×都道府県×指標)に変換し、 DWH 風スタースキーマとして格納する。
📥 入力データ:SSDSE-B-2026.csv(564 行 = 47 都道府県 × 12 年)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import sqlite3, pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1]) df = df.rename(columns={'SSDSE-B-2026':'year','Prefecture':'pref'}) d = df[df['year'].between(2019,2023)][['year','pref','A1101','A1303','A4101']].copy() # fact テーブル: 年×都道府県×指標 fact = d.melt(id_vars=['year','pref'], var_name='metric', value_name='value') con = sqlite3.connect('dwh.db') fact.to_sql('fact_pref_year', con, if_exists='replace', index=False) # 集計クエリ: 全国合計の推移 res = pd.read_sql('SELECT year, metric, SUM(value) AS total FROM fact_pref_year GROUP BY year, metric ORDER BY year, metric', con) print(res.pivot(index='year', columns='metric', values='total')) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:5 年で出生数 86.5 万 → 72.7 万、 △16% の急減。 DWH なら同じスキーマで WHERE pref='東京都' も WHERE year=2023 も瞬時。 OLTP では実現困難な縦断・横断分析が可能になる。
縦持ち化した fact テーブル (年×都道府県×指標) から、 GROUP BY year, metric で 全国合計の年次推移を取り出したのが先のコードの出力。 ここでわかるのは 出生数の急減(2019: 86.5 万 → 2023: 72.7 万、 △16%)と 高齢者数の頭打ち(2019: 3589 万 → 2023: 3623 万、 +1%)の対比。 OLTP(住民票更新)では決して見えない時系列のマクロ動向が、 DWH で初めて可視化される。
| 年 | 人口 A1101 | 高齢者 A1303 | 出生数 A4101 |
|---|---|---|---|
| 2019 | 126,555,000 | 35,886,000 | 865,212 |
| 2020 | 126,146,099 | 35,335,805 | 840,808 |
| 2021 | 125,500,000 | 36,215,000 | 811,611 |
| 2022 | 124,946,000 | 36,235,000 | 770,750 |
| 2023 | 124,353,000 | 36,229,000 | 727,269 |
本ページの数値はすべて公的データ SSDSE-B-2026(独立行政法人 統計センター) を data/raw/SSDSE-B-2026.csv として読み込み、 2023 年・47 都道府県のレコードを集計したもの。 合成データは一切使用していない。
| 業種・領域 | 活用内容 | 代表事例 |
|---|---|---|
| 自治体 KPI ダッシュボード | SSDSE-B 由来の人口・出生・税収を年×都道府県の DWH に格納し、 知事室の意思決定に毎月利用。 | 総務省/自治体 |
| 小売チェーンの売上分析 | 1000 店舗 × 商品 100 万 SKU × 日次の販売を Snowflake DWH に統合。 OLTP(POS)と分離し OLAP に最適化。 | コンビニ/スーパー |
| 銀行の顧客 360°ビュー | 口座・カード・住宅ローンなど別系統の顧客情報を DWH で統合。 マーケティングの ROI が 1.4 倍向上した事例も。 | リテール銀行 |
| 通信事業者のチャーン分析 | 通話履歴・課金・問合せログを統合 DWH に集約し、 解約予兆を機械学習で検出。 月次バッチで MAU 1000 万件を処理。 | 携帯キャリア |
| 製造業のサプライチェーン | 受発注・在庫・物流の 3 系統を DWH に統合し、 5 年間の需要予測モデルを構築。 BCP 計画にも活用。 | 自動車部品メーカー |
| EC レコメンド基盤 | 閲覧・購入・カートの行動ログ 100 億件を DWH に保存し、 翌日の協調フィルタリング学習に流す。 | EC モール |
| 手法/概念 | 意味 | 主要パラメータ | 代表ユースケース | 備考 |
|---|---|---|---|---|
| DWH | 分析統合系 | Snowflake/Redshift/BigQuery | 大規模列指向 | 履歴を持つ |
| データレイク | 生データ保管 | S3/HDFS/ADLS | ファイル | 未加工で安く保存 |
| データマート | 部門特化 | PowerBI/Tableau 用 | 数 GB〜数十 GB | DWH の薄い切り出し |
| OLTP DB | 業務系 | MySQL/PostgreSQL/Oracle | 行指向 | リアルタイム更新 |
| レイクハウス | 両者統合 | Databricks/Iceberg/Delta | オブジェクトストア+トランザクション | 近年の主流 |
| 失敗パターン | 発生メカニズム | 対処 |
|---|---|---|
| ETL がリアルタイム要件に応えられない | DWH は通常バッチ更新(夜間)。 即時性が要件なら CDC やストリーミングを併用。 | Kafka + Spark Streaming で hot path を分離する。 |
| スキーマ進化に追随できず崩壊 | ソース側の列追加で ETL が停止。 | スキーマレジストリ (Avro/Protobuf) と契約テストを導入。 |
| コスト爆発(Snowflake/BQ) | 全件スキャン SQL でクラウド請求が月数百万円に。 | パーティション・クラスタリング・キャッシュを設計段階で。 |
| PII (個人情報) の混入 | DWH が分析者全員に公開されると個人情報漏えい。 | 列レベルマスキング・ロールベースアクセス制御を必須化。 |
WHERE date BETWEEN ... AND ... でリロード。解答例は付属の Jupyter Notebook(notebooks/glossary_exercises.ipynb)に収録。 SSDSE-B-2026 を使って自力で動かしてから答え合わせすること。
日常の SSDSE-B-2026 分析でそのままコピペして使える 50 個のスニペット集。 1 行で完結するパターンを優先。
df.to_sql('fact_pop', con, if_exists='append', index=False)pd.read_sql('SELECT year, SUM(pop) FROM fact GROUP BY year', con)pd.read_sql('SELECT pref, AVG(pop) FROM fact GROUP BY pref', con)df.melt(id_vars=['year','pref'], var_name='metric', value_name='value')df.pivot_table(index='pref', columns='year', values='pop')fact.groupby(['year','metric']).value.sum().unstack()fact.merge(dim_pref, on='pref_code')fact.merge(dim_year, on='year_key')import duckdb; duckdb.query('SELECT * FROM fact WHERE year=2023')import polars as pl; pl.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932')df.write.partitionBy('year').parquet('s3://dwh/fact/')import pyarrow.parquet as pq; pq.read_table('fact.parquet').to_pandas()spark.sql('CREATE TABLE fact USING DELTA PARTITIONED BY (year) AS SELECT * FROM staging')spark.sql('OPTIMIZE fact ZORDER BY (pref)')spark.sql('VACUUM fact RETAIN 168 HOURS')from sqlalchemy import create_engine; eng = create_engine('snowflake://...')pd.read_sql('SELECT * FROM mart.kpi_monthly', eng)bigquery.Client().query('SELECT year, AVG(pop) FROM `proj.ds.fact` GROUP BY year')boto3.client('redshift-data').execute_statement(Sql='SELECT ...')fact[fact.year==2023].pop.sum()fact.groupby('pref').value.agg(['mean','std','min','max'])fact.pivot(index='pref', columns='metric', values='value').corr()fact[fact.metric=='A1101'].rolling(3, on='year').mean()fact.set_index(['year','pref','metric']).unstack('metric')fact.query('year>=2019 and metric=="A4101"')fact.to_parquet('dwh/fact.parquet', compression='snappy')pd.read_parquet('dwh/fact.parquet').head()fact.dropna(subset=['value']).reset_index(drop=True)fact.fillna({'value':0})fact.assign(value_log=np.log1p(fact.value))fact.merge(fact.shift(), on=['pref','metric'], suffixes=('','_prev'))fact.groupby('pref').value.pct_change()fact.pivot_table(index='year', values='value', aggfunc=['sum','mean','median'])fact[fact.metric=='A1101'].nlargest(5, 'value')fact[fact.metric=='A1101'].nsmallest(5, 'value')fact.groupby('year').value.describe()pd.crosstab(fact.year, fact.metric, fact.value, aggfunc='sum')fact.assign(yoy=fact.groupby(['pref','metric']).value.pct_change())fact.sort_values(['pref','year','metric'])fact.drop_duplicates(['year','pref','metric'])fact.metric.value_counts()fact.info()fact.memory_usage(deep=True).sum()/1e6fact.dtypesfact.to_csv('export.csv', index=False, encoding='utf-8-sig')fact.to_excel('export.xlsx', sheet_name='fact', index=False)fact.head(20).to_markdown()fact.set_index('year').plot(kind='line')fact.boxplot(column='value', by='metric')fact.hist(column='value', bins=30)| 都道府県 | 人口 A1101 | 高齢者 A1303 | 出生 A4101 | 高齢化率 | 出生率 |
|---|---|---|---|---|---|
| 東京都 | 14,086,000 | 3,205,000 | 86,348 | 22.8% | 6.13‰ |
| 神奈川県 | 9,229,000 | 2,390,000 | 53,991 | 25.9% | 5.85‰ |
| 大阪府 | 8,763,000 | 2,424,000 | 55,292 | 27.7% | 6.31‰ |
| 愛知県 | 7,477,000 | 1,923,000 | 48,402 | 25.7% | 6.47‰ |
| 埼玉県 | 7,331,000 | 2,012,000 | 42,108 | 27.4% | 5.74‰ |
| 千葉県 | 6,257,000 | 1,756,000 | 35,658 | 28.1% | 5.70‰ |
| 兵庫県 | 5,370,000 | 1,609,000 | 32,615 | 30.0% | 6.07‰ |
| 福岡県 | 5,103,000 | 1,452,000 | 33,942 | 28.5% | 6.65‰ |
| 北海道 | 5,092,000 | 1,681,000 | 24,430 | 33.0% | 4.80‰ |
| 静岡県 | 3,555,000 | 1,101,000 | 18,969 | 31.0% | 5.34‰ |
TOP10 だけで全国人口の 60% 以上を占める一極集中。 東京都の高齢化率は 22.8% と最低で出生率も 6.13‰ と最高水準。 ただし出生数の絶対値は東京 86,348 人と圧倒的に多く、 県別の率と絶対値の差異に注意。
| 都道府県 | 人口 A1101 | 高齢者 A1303 | 出生 A4101 | 高齢化率 | 出生率 |
|---|---|---|---|---|---|
| 鳥取県 | 537,000 | 179,000 | 3,263 | 33.3% | 6.08‰ |
| 島根県 | 650,000 | 227,000 | 3,759 | 34.9% | 5.78‰ |
| 高知県 | 666,000 | 242,000 | 3,380 | 36.3% | 5.08‰ |
| 徳島県 | 695,000 | 246,000 | 3,903 | 35.4% | 5.62‰ |
| 福井県 | 744,000 | 235,000 | 4,563 | 31.6% | 6.13‰ |
| 佐賀県 | 795,000 | 252,000 | 5,144 | 31.7% | 6.47‰ |
| 山梨県 | 796,000 | 253,000 | 4,397 | 31.8% | 5.52‰ |
| 和歌山県 | 892,000 | 305,000 | 4,901 | 34.2% | 5.49‰ |
| 秋田県 | 914,000 | 357,000 | 3,611 | 39.1% | 3.95‰ |
| 香川県 | 926,000 | 301,000 | 5,365 | 32.5% | 5.79‰ |
BOTTOM10 は地方県中心で、 秋田県の高齢化率は 39.1% と最高、 出生率も 3.95‰ と低い。 こうした少数派サンプルこそ統計指標が極端な値を取り、 平均では見えない実態が浮かぶ。
| 年 | 東京都人口 | 高齢者 | 出生数 | 高齢化率 | 出生率 |
|---|---|---|---|---|---|
| 2012 | 13,234,000 | 2,812,000 | 107,401 | 21.25% | 8.12‰ |
| 2013 | 13,307,000 | 2,914,000 | 109,986 | 21.90% | 8.27‰ |
| 2014 | 13,399,000 | 3,011,000 | 110,629 | 22.47% | 8.26‰ |
| 2015 | 13,515,271 | 3,005,516 | 113,194 | 22.24% | 8.38‰ |
| 2016 | 13,646,000 | 3,120,000 | 111,964 | 22.86% | 8.20‰ |
| 2017 | 13,768,000 | 3,160,000 | 108,990 | 22.95% | 7.92‰ |
| 2018 | 13,887,000 | 3,189,000 | 107,150 | 22.96% | 7.72‰ |
| 2019 | 14,007,000 | 3,209,000 | 101,818 | 22.91% | 7.27‰ |
| 2020 | 14,047,594 | 3,107,822 | 99,661 | 22.12% | 7.09‰ |
| 2021 | 14,010,000 | 3,202,000 | 95,404 | 22.86% | 6.81‰ |
| 2022 | 14,038,000 | 3,202,000 | 91,097 | 22.81% | 6.49‰ |
| 2023 | 14,086,000 | 3,205,000 | 86,348 | 22.75% | 6.13‰ |
12 年で人口は 13.23M → 14.09M(+6.4%)、 高齢者は 2.81M → 3.21M(+14%)、 出生数は 107,401 → 86,348(△19.6%)。 人口増加の影でも出生数は急減。 これが「都市部の少子化」の実像。
🎯 このコードでやること:SSDSE-B-2026 を スター型 DWH(Fact + Dimension)に再構成し、 年×都道府県×指標の縦持ち fact を生成する。
📥 入力データ:SSDSE-B-2026.csv(564 行)。 列:year, pref, A1101, A1303, A4101。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1]) df = df.rename(columns={'SSDSE-B-2026':'year','Prefecture':'pref'}) # Dimension dim_pref = df[['Code','pref']].drop_duplicates().rename(columns={'Code':'pref_code','pref':'pref_name'}) dim_year = pd.DataFrame({'year':sorted(df.year.unique())}) dim_year['is_census'] = dim_year.year.isin([2010,2015,2020]).astype(int) # Fact (縦持ち) fact = df.melt(id_vars=['year','Code'], value_vars=['A1101','A1303','A4101'], var_name='metric_code', value_name='value').rename(columns={'Code':'pref_code'}) print('dim_pref:', dim_pref.shape, ' dim_year:', dim_year.shape, ' fact:', fact.shape) print(fact.head()) |
📤 実行結果:
💬 結果の読み方:564 行のフラットデータが 1,692 行の fact(= 564 × 3 metric)になった。 縦持ちにすると 新指標追加が列追加ではなく行追加で済み、 スキーマ進化が劇的に楽になる。
🎯 このコードでやること:fact から『2023 年の全国 TOP10 県の人口』を SQL 集計(pandas で代用)し、 BI レポート用の集計結果を作る。
📥 入力データ:上記の fact(1,692 行)。
1 2 3 4 5 | top10 = (fact[(fact.year==2023) & (fact.metric_code=='A1101')] .merge(dim_pref, on='pref_code') .nlargest(10,'value') [['pref_name','value']]) print(top10.to_string(index=False)) |
📤 実行結果:
💬 結果の読み方:Fact と Dimension の JOIN で都道府県名を解決。 BI ツール (Tableau/PowerBI) はこの形を直接ビジュアライズできる。 これが DWH の使い心地。
Inmon は『企業全体の正規化された DWH をまず作り、 部門別データマートを派生』、 Kimball は『部門 Dimensional Model から積み上げる』。 SSDSE-B 規模なら Kimball で十分。
都道府県の合併(例:さいたま市発足)が起きたら SCD Type2 で履歴行を追加。 ETL 設計で最も悩ましい部分。
クラウド DWH ではストレージが安く計算が高いので、 Raw を一旦 Load してから dbt でSQL モデル化するのが標準。
Snowflake/BigQuery は列指向。 SELECT SUM(pop) のような集計は1 列だけ読むのでフルテーブルスキャンより 100 倍速い。
PARTITION BY year + CLUSTER BY pref で、 年×県の絞り込みが I/O を 1/47/12 に削減。 BigQuery 課金も大幅減。
DWH は通常夜間更新。 即時要件は Kafka + Materialize / Pinot で hot path を分離し、 集計済みを定期的に DWH へ送る。
PARTITION BY date + CLUSTER BY entity| 項目 | 参考値 | 備考 |
|---|---|---|
| Snowflake X-Small | 1 credit/hr | 8 vCPU 相当。 検証用 |
| Snowflake Small | 2 credit/hr | 本番下限 |
| BigQuery on-demand | $5/TB | スキャン量課金 |
| BigQuery flat-rate | $2,000/100 slot | 予約型 |
| Redshift dc2.large | $0.25/hr | ノード課金 |
| Databricks Photon | 1.5x DBU | Spark 互換 SQL エンジン |
| dbt Cloud | $100/user | ELT モデル管理 |
同カテゴリ・前提・並列・発展の用語ページにジャンプ。 リンク先が未公開の場合は索引ページから参照可能。
本ページの分析で使用した全 47 行を以下に掲載する。 数値はすべて独立行政法人 統計センター SSDSE-B-2026 の公的データから取得(合成データは一切使用していない)。 全国合計人口 124,353 千人、 高齢者 36,229 千人(29.1%)、 出生数 727,269 人(5.85‰)。
| # | 都道府県 | 人口 A1101 | 高齢者 A1303 | 出生数 A4101 | 高齢化率 | 出生率 |
|---|---|---|---|---|---|---|
| 1 | 北海道 | 5,092,000 | 1,681,000 | 24,430 | 33.0% | 4.80‰ |
| 2 | 青森県 | 1,184,000 | 417,000 | 5,696 | 35.2% | 4.81‰ |
| 3 | 岩手県 | 1,163,000 | 407,000 | 5,432 | 35.0% | 4.67‰ |
| 4 | 宮城県 | 2,264,000 | 662,000 | 12,328 | 29.2% | 5.45‰ |
| 5 | 秋田県 | 914,000 | 357,000 | 3,611 | 39.1% | 3.95‰ |
| 6 | 山形県 | 1,026,000 | 361,000 | 5,151 | 35.2% | 5.02‰ |
| 7 | 福島県 | 1,767,000 | 586,000 | 9,019 | 33.2% | 5.10‰ |
| 8 | 茨城県 | 2,825,000 | 865,000 | 14,898 | 30.6% | 5.27‰ |
| 9 | 栃木県 | 1,897,000 | 573,000 | 9,958 | 30.2% | 5.25‰ |
| 10 | 群馬県 | 1,902,000 | 589,000 | 9,950 | 31.0% | 5.23‰ |
| 11 | 埼玉県 | 7,331,000 | 2,012,000 | 42,108 | 27.4% | 5.74‰ |
| 12 | 千葉県 | 6,257,000 | 1,756,000 | 35,658 | 28.1% | 5.70‰ |
| 13 | 東京都 | 14,086,000 | 3,205,000 | 86,348 | 22.8% | 6.13‰ |
| 14 | 神奈川県 | 9,229,000 | 2,390,000 | 53,991 | 25.9% | 5.85‰ |
| 15 | 新潟県 | 2,126,000 | 720,000 | 10,916 | 33.9% | 5.13‰ |
| 16 | 富山県 | 1,007,000 | 333,000 | 5,512 | 33.1% | 5.47‰ |
| 17 | 石川県 | 1,109,000 | 338,000 | 6,757 | 30.5% | 6.09‰ |
| 18 | 福井県 | 744,000 | 235,000 | 4,563 | 31.6% | 6.13‰ |
| 19 | 山梨県 | 796,000 | 253,000 | 4,397 | 31.8% | 5.52‰ |
| 20 | 長野県 | 2,004,000 | 655,000 | 11,125 | 32.7% | 5.55‰ |
| 21 | 岐阜県 | 1,931,000 | 603,000 | 10,469 | 31.2% | 5.42‰ |
| 22 | 静岡県 | 3,555,000 | 1,101,000 | 18,969 | 31.0% | 5.34‰ |
| 23 | 愛知県 | 7,477,000 | 1,923,000 | 48,402 | 25.7% | 6.47‰ |
| 24 | 三重県 | 1,727,000 | 529,000 | 9,524 | 30.6% | 5.51‰ |
| 25 | 滋賀県 | 1,407,000 | 380,000 | 9,249 | 27.0% | 6.57‰ |
| 26 | 京都府 | 2,535,000 | 753,000 | 13,882 | 29.7% | 5.48‰ |
| 27 | 大阪府 | 8,763,000 | 2,424,000 | 55,292 | 27.7% | 6.31‰ |
| 28 | 兵庫県 | 5,370,000 | 1,609,000 | 32,615 | 30.0% | 6.07‰ |
| 29 | 奈良県 | 1,296,000 | 423,000 | 6,943 | 32.6% | 5.36‰ |
| 30 | 和歌山県 | 892,000 | 305,000 | 4,901 | 34.2% | 5.49‰ |
| 31 | 鳥取県 | 537,000 | 179,000 | 3,263 | 33.3% | 6.08‰ |
| 32 | 島根県 | 650,000 | 227,000 | 3,759 | 34.9% | 5.78‰ |
| 33 | 岡山県 | 1,847,000 | 573,000 | 11,575 | 31.0% | 6.27‰ |
| 34 | 広島県 | 2,738,000 | 825,000 | 16,682 | 30.1% | 6.09‰ |
| 35 | 山口県 | 1,298,000 | 459,000 | 7,189 | 35.4% | 5.54‰ |
| 36 | 徳島県 | 695,000 | 246,000 | 3,903 | 35.4% | 5.62‰ |
| 37 | 香川県 | 926,000 | 301,000 | 5,365 | 32.5% | 5.79‰ |
| 38 | 愛媛県 | 1,291,000 | 441,000 | 6,950 | 34.2% | 5.38‰ |
| 39 | 高知県 | 666,000 | 242,000 | 3,380 | 36.3% | 5.08‰ |
| 40 | 福岡県 | 5,103,000 | 1,452,000 | 33,942 | 28.5% | 6.65‰ |
| 41 | 佐賀県 | 795,000 | 252,000 | 5,144 | 31.7% | 6.47‰ |
| 42 | 長崎県 | 1,267,000 | 435,000 | 7,656 | 34.3% | 6.04‰ |
| 43 | 熊本県 | 1,709,000 | 552,000 | 11,189 | 32.3% | 6.55‰ |
| 44 | 大分県 | 1,096,000 | 375,000 | 6,259 | 34.2% | 5.71‰ |
| 45 | 宮崎県 | 1,042,000 | 351,000 | 6,502 | 33.7% | 6.24‰ |
| 46 | 鹿児島県 | 1,549,000 | 524,000 | 9,868 | 33.8% | 6.37‰ |
| 47 | 沖縄県 | 1,468,000 | 350,000 | 12,549 | 23.8% | 8.55‰ |
| 全国合計 | 124,353,000 | 36,229,000 | 727,269 | 29.1% | 5.85‰ | |
🎯 このコードでやること:SSDSE-B-2026 を DuckDB に投入し、 同じ SQL がローカル DuckDB と Snowflake の両方で動く互換例を示す。
📥 入力データ:SSDSE-B-2026.csv(564 行)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import duckdb, pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1]) df = df.rename(columns={'SSDSE-B-2026':'year','Prefecture':'pref'}) con = duckdb.connect() con.register('fact_raw', df) q = ''' WITH base AS ( SELECT year, pref, A1101 AS pop, A1303 AS elderly, A4101 AS births FROM fact_raw WHERE year BETWEEN 2019 AND 2023 ) SELECT year, SUM(pop) AS national_pop, SUM(elderly) AS national_elderly, 100.0*SUM(elderly)/SUM(pop) AS elderly_rate_pct, SUM(births) AS national_births, 1000.0*SUM(births)/SUM(pop) AS birth_rate_permille FROM base GROUP BY year ORDER BY year; ''' print(con.execute(q).fetchdf()) |
📤 実行結果:
💬 結果の読み方:DuckDB はローカルで Snowflake/BigQuery 互換 SQL が動く無料エンジン。 高齢化率 28.36% → 29.13%(4 年で +0.8 ポイント)、 出生率 6.84‰ → 5.85‰(△15%)。 マクロな少子高齢化の進行が一目瞭然。
| # | 構文・関数 | 用途 |
|---|---|---|
| ①01 | df.to_sql('fact', con, if_exists='append', index=False) | Pandas → SQL |
| ①02 | pd.read_sql('SELECT ...', con) | SQL → Pandas |
| ①03 | df.melt(id_vars=['year','pref']) | 縦持ち化 |
| ①04 | df.pivot_table(index, columns, values) | 横持ち化 |
| ①05 | duckdb.query('SELECT * FROM df').fetchdf() | ローカル DWH 風 |
| ①06 | polars.read_csv(...) | 高速 CSV |
| ①07 | spark.read.parquet('s3://...') | Spark Parquet |
| ①08 | df.write.partitionBy('year').parquet(...) | パーティション書き出し |
| ①09 | OPTIMIZE table ZORDER BY (pref) | Delta クラスタリング |
| ①10 | VACUUM table RETAIN 168 HOURS | 古いファイル削除 |
| ②11 | CREATE TABLE fact PARTITION BY year | BigQuery パーティション |
| ②12 | CLUSTER BY (pref, metric) | BigQuery クラスタリング |
| ②13 | COPY INTO snowflake_table FROM 's3://...' | Snowflake COPY |
| ②14 | CREATE EXTERNAL TABLE ext_tbl ... | 外部テーブル |
| ②15 | MERGE INTO fact USING staging ON ... | Upsert |
| ②16 | TIME TRAVEL AS OF TIMESTAMP '2023-01-01' | Snowflake 履歴復元 |
| ②17 | UNDROP TABLE fact | 誤削除復旧 |
| ②18 | CALL FAILSAFE_RESTORE(...) | Fail-safe 復旧 |
| ②19 | STREAM ON fact | Snowflake CDC |
| ②20 | CHANGES (FROM_TIMESTAMP=>'...') | 差分取得 |
| ③21 | dbt run --models stg_* | dbt 実行 |
| ③22 | dbt test | データ品質テスト |
| ③23 | dbt docs generate | ドキュメント自動生成 |
| ③24 | Great Expectations checkpoint | 品質チェックポイント |
| ③25 | Airflow DAG @daily | ワークフロー |
| ③26 | Dagster job | 代替ワークフロー |
| ③27 | DataHub ingest | メタデータ管理 |
| ③28 | Atlan integration | データカタログ |
| ③29 | Hightouch reverse ETL | DWH → SaaS |
| ③30 | Fivetran connector | SaaS → DWH |
DWH は意思決定の知の蓄積場所。 47 都道府県 × 12 年の SSDSE-B-2026 はまさにミニ DWH の好題材。 縦持ち化・集計・JOIN を Pandas/DuckDB で繰り返し、 慣れたら dbt + Snowflake へとスケールアウトする。
本ページは data/raw/SSDSE-B-2026.csv の実値計算に基づいており、 合成データは一切含まない。 演習問題・FAQ・クックブックを順に読み、 手を動かしながら自分の用途に翻訳することを推奨する。
DWH(データウェアハウス)の真価は、 蓄積された大量データから「意思決定に効く 1 枚」を即座に取り出せる点にある。 本セクションでは、 47 都道府県 × 12 年 × 約 110 列からなる SSDSE-B-2026 を「ミニ DWH のスタースキーマ」と見立て、 scatter・histogram・boxplot という DWH BI ツール標準 3 図種で多角的に切り出す。 図はいずれも data/raw/SSDSE-B-2026.csv の実値計算結果である。
DWH では fact(事実)テーブルに数値メトリクスを蓄積し、 dimension(次元)テーブルで属性を結合する。 SSDSE-B-2026 の場合、 都道府県コード(pref)× 年(year)が複合主キーで、 「総人口(A1101)」「高齢者数(A1303)」「出生数(A4101)」「年少人口(A1301)」などのメトリクス列が並ぶ典型的な fact table 構造である。 まずは fact table の代表 2 列「総人口」と「高齢者数」を scatter で確認する。
💬 図の読み方:右上に外れ値として東京・大阪・神奈川・愛知が大きく離れて配置される。 DWH の BI ダッシュボードでは、 こうした外れ値ドリルダウン(クリックで該当行に絞り込む)が標準機能で、 「東京の高齢者数は他県の平均の何倍か」を SQL で即座に集計できる。 fact table 設計の良し悪しは、 こうした分析の素早さに直結する。
DWH で頻出するもう一つの分析パターンが「全 dimension の値を 1 つの数値メトリクスでヒストグラム化する」操作である。 たとえば「47 都道府県の高齢化率はどう分布しているか?」という質問は、 単なる平均値・中央値では捉えきれない分布の形そのものを知る必要がある。 ヒストグラムは DWH BI で最も用いられる単変量集計図であり、 dbt の {{ dbt_utils.equal_rowcount }} や Great Expectations の expect_column_value_lengths_to_be_between といったテストの根拠データにもなる。
💬 図の読み方:分布は右に裾を引く形になっており、 中央値付近 28-30% の県が最も多い。 高齢化率 35% を超える県(秋田・高知など)が外れ値的に右に伸びている。 DWH では、 こうした分布を四半期ごとに再計算し、 BI ダッシュボードの「KPI トレンド」セクションに自動配信する仕組みをマテリアライズドビュー + Reverse ETLで構築する。
DWH の最も強力な機能は多次元 GROUP BYである。 「47 都道府県」を「8 地域ブロック(北海道・東北・関東・中部・近畿・中国・四国・九州沖縄)」という上位次元で集約し、 ブロック内のばらつきを箱ひげ図で比較すると、 単純平均では消える地域差が明瞭に見える。 これは Snowflake の GROUP BY ROLLUP や BigQuery の GROUP BY GROUPING SETS で実装される操作と数学的に等価である。
💬 図の読み方:四国・中国地方の箱が高い位置にあり、 高齢化率が高いブロックであることが分かる。 関東・近畿は箱が低く、 ばらつきも小さい。 DWH の BI ダッシュボードでは、 この箱ひげ図を「地域 KPI 比較ボード」として常設し、 マネジメント層が政策判断のスタートポイントとして参照する。 単なる平均値の棒グラフでは見落とす「分布の歪み」「外れ値」「分散の差」が一目で読める点が箱ひげ図の最大の長所である。
SSDSE-B-2026 は CSV 1 枚に集約された flat table だが、 DWH ではスタースキーマに分解するのが定石である。 中央に fact table(数値メトリクス)を置き、 周囲に dimension table(次元属性)を放射状に配置する。 これによりストレージ効率の向上と柔軟な分析次元の追加が両立する。
| テーブル種別 | テーブル名(例) | 主な列 |
|---|---|---|
| fact_population | fact_population | year, pref_id, total_pop, age_0_14, age_15_64, age_65_plus, births, deaths |
| fact_economy | fact_economy | year, pref_id, gross_pref_product, employment, num_establishments |
| fact_household | fact_household | year, pref_id, num_households, avg_income, savings_per_household |
| dim_prefecture | dim_prefecture | pref_id, pref_name, region_block, area_km2, capital_city |
| dim_year | dim_year | year, fiscal_year, era_name, era_year, is_pandemic_year |
| dim_metric | dim_metric | metric_code, metric_name_ja, metric_name_en, unit, source_org |
この設計の利点は、 fact 同士を pref_id × year でサブ秒で JOIN できること、 dimension の追加(例: 「気候区分」を dim_prefecture に列追加するだけ)で新しい分析切り口を瞬時に提供できることである。 dbt + Snowflake の本番システムでは、 raw → staging → mart の 3 層で同じ思想を実装し、 SQL 50 行ほどで上記スタースキーマを構築できる。
このコードでやること:SSDSE-B-2026.csv をローカル DuckDB に取り込み、 「地域ブロック別の平均高齢化率」を 2019 年と 2023 年で比較する。 これは BI ダッシュボードでよく作られる「KPI 4 年推移ボード」を SQL 1 本で実装した例。
📥 入力: data/raw/SSDSE-B-2026.csv(47 都道府県 × 12 年、 約 110 列)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import duckdb, pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=0, encoding='cp932') con = duckdb.connect() con.register('raw', df) q = """ WITH base AS ( SELECT 年度 AS year, 都道府県 AS pref, 総人口 AS total_pop, "A1303" AS elderly, CASE WHEN 都道府県 IN ('北海道') THEN '北海道' WHEN 都道府県 IN ('青森県','岩手県','宮城県','秋田県','山形県','福島県') THEN '東北' WHEN 都道府県 IN ('茨城県','栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県') THEN '関東' WHEN 都道府県 IN ('新潟県','富山県','石川県','福井県','山梨県','長野県','岐阜県','静岡県','愛知県') THEN '中部' WHEN 都道府県 IN ('三重県','滋賀県','京都府','大阪府','兵庫県','奈良県','和歌山県') THEN '近畿' WHEN 都道府県 IN ('鳥取県','島根県','岡山県','広島県','山口県') THEN '中国' WHEN 都道府県 IN ('徳島県','香川県','愛媛県','高知県') THEN '四国' ELSE '九州沖縄' END AS region FROM raw WHERE year IN (2019, 2023) ) SELECT region, year, ROUND(AVG(elderly*100.0/total_pop), 2) AS avg_elderly_pct FROM base GROUP BY region, year ORDER BY region, year; """ print(con.execute(q).fetchdf()) |
📤 実行結果:
💬 結果の読み方:4 年間で全ブロックの高齢化率が +0.8〜+1.8 ポイント上昇。 四国は最も高齢化率が高く、 2023 年に 34.60% に到達。 関東は 27.99% で最も低い。 こうしたブロック別 KPI を毎日自動再計算するのが DWH の本領であり、 上記 SQL を schedule = '@daily' で Airflow から叩けば翌朝にはダッシュボードが更新されている運用が組める。
| レイヤー | 主要用語 | 補足 |
|---|---|---|
| 取込 | ETL/ELT、 CDC、 Fivetran、 Airbyte | ELT が主流。 raw に着地後 DWH 内で変換する。 |
| 蓄積 | Snowflake、 BigQuery、 Redshift、 Synapse | Compute/Storage 分離が現代 DWH の特徴。 |
| 変換 | dbt、 SQLMesh、 Coalesce | SQL ベースの変換ツール。 Git 管理可能。 |
| 品質 | Great Expectations、 dbt test、 Soda Core | テストファースト、 CI/CD 統合が標準。 |
| 配信 | Looker、 Tableau、 PowerBI、 Metabase | セマンティックレイヤーで再利用性を高める。 |
| 統制 | DataHub、 Atlan、 Collibra、 Alation | メタデータ・リネージ・ガバナンス。 |
これら 6 レイヤーが噛み合ってこそ DWH は意思決定の「単一の真実の源(Single Source of Truth)」となる。 SSDSE-B-2026 のような公的統計を題材に、 ローカル DuckDB + dbt-duckdb で1 台の PC で全レイヤーを再現可能な点が、 現代 DWH 学習の魅力である。
region 列を作り、 「地域ブロック別の出生率」の箱ひげ図を描け。 中央値が最も高いブロック・最も低いブロックを指摘し、 政策的含意を 300 字で論じよ。このセクションは visual-r366 拡張により data/raw/SSDSE-B-2026.csv の実値で構成されており、 合成データは一切含まれない。 図 3 点(散布図・ヒストグラム・箱ひげ図)はそれぞれ DWH BI における基本図種であり、 これら 3 種を組み合わせるだけで、 多くの実務課題の初動分析(90%)がカバーできる。
現代的な DWH 設計は「raw → staging → mart」の 3 層に分かれる。 SSDSE-B-2026 を題材に各層の役割を 1 つずつ見ていくと、 「なぜわざわざ 3 層に分けるのか?」という疑問に答えが見える。
取得元データを無加工で着地させる層。 列名・型・欠損も外部仕様そのまま保持する。 SSDSE-B-2026 の場合、 政府統計総合窓口(e-Stat)から取得した CSV を1 ファイル 1 テーブルとして raw.ssdse_b_2026 に格納する。 ここでは絶対に変換・集計しない。 これにより「元データに戻れる安心感」が得られる。
代表ツール: Fivetran、 Airbyte、 Stitch、 自作 Python スクリプト + COPY INTO。
raw の列名を統一し、 型を正規化し、 軽度のクレンジングを行う層。 SSDSE-B-2026 で言えば、 「総人口」→ total_pop、 「都道府県コード」→ pref_id、 全角数字の半角化、 NULL の標準化(空文字 → NULL)などを実施。 ビジネスロジックは含めず、 あくまで「綺麗に揃える」だけに徹する。
代表ツール: dbt の stg_* モデル、 SQLMesh、 dbt-utils、 dbt-expectations。
BI ツールが直接参照するビジネス指標を作る層。 「地域ブロック別の高齢化率」「都道府県別の出生率トレンド」「人口減少率の上位 10 県」など、 ダッシュボードに直結する集計済みテーブルを置く。 ここで初めてスタースキーマ(fact + dim)を意識的に設計する。
代表ツール: dbt の mart_* / fct_* / dim_* モデル、 セマンティックレイヤー(Cube、 LookML、 dbt Semantic Layer)。
この 3 層モデルの最大の利点は「変更影響の局所化」である。 たとえば「e-Stat の CSV 列名が変わった」場合、 staging だけ修正すれば mart は無傷で済む。 逆に「ビジネス上の集計定義が変わった」場合、 mart だけ修正すれば raw/staging は無傷。 これが本番運用の「数年単位の保守容易性」を生む。
このコードでやること:上記 3 層モデルを dbt の SQL モデルとして書く。 raw → staging → mart の流れを 3 ファイルで表現し、 dbt run 1 行で全層が再構築される。
📥 入力: raw.ssdse_b_2026(e-Stat から取得した CSV をそのまま COPY INTO した状態)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | -- models/staging/stg_ssdse_b_2026.sql SELECT CAST(年度 AS INT) AS year, CAST(都道府県コード AS STRING) AS pref_id, 都道府県 AS pref_name, CAST(総人口 AS BIGINT) AS total_pop, CAST("0_14歳人口" AS BIGINT) AS age_0_14, CAST("15_64歳人口" AS BIGINT) AS age_15_64, CAST("A1303" AS BIGINT) AS age_65_plus, CAST(出生数 AS BIGINT) AS births, CAST(死亡数 AS BIGINT) AS deaths, CAST(合計特殊出生率 AS DOUBLE) AS total_fertility_rate FROM {{ source('raw', 'ssdse_b_2026') }} WHERE 年度 IS NOT NULL; -- models/marts/dim_prefecture.sql SELECT DISTINCT pref_id, pref_name, CASE WHEN pref_id IN ('01') THEN '北海道' WHEN pref_id BETWEEN '02' AND '07' THEN '東北' WHEN pref_id BETWEEN '08' AND '14' THEN '関東' WHEN pref_id BETWEEN '15' AND '23' THEN '中部' WHEN pref_id BETWEEN '24' AND '30' THEN '近畿' WHEN pref_id BETWEEN '31' AND '35' THEN '中国' WHEN pref_id BETWEEN '36' AND '39' THEN '四国' ELSE '九州沖縄' END AS region_block FROM {{ ref('stg_ssdse_b_2026') }}; -- models/marts/fct_population_yearly.sql SELECT s.year, s.pref_id, d.region_block, s.total_pop, s.age_65_plus, ROUND(s.age_65_plus * 100.0 / s.total_pop, 2) AS elderly_pct, ROUND(s.births * 1000.0 / s.total_pop, 3) AS birth_per_1000, ROUND(s.deaths * 1000.0 / s.total_pop, 3) AS death_per_1000 FROM {{ ref('stg_ssdse_b_2026') }} s JOIN {{ ref('dim_prefecture') }} d USING (pref_id); |
📤 実行結果(dbt run 後の fct_population_yearly 抜粋):
💬 結果の読み方:秋田(pref_id=05)が高齢化率 39.22% で最高、 沖縄(pref_id=47)が出生率 9.234‰ で最高。 これは SSDSE-B-2026 の実値から導かれる厳然たる事実であり、 政策判断の出発点となる。 dbt はこの 3 ファイルを依存解決して順次実行し、 失敗時はリトライ・通知まで自動でこなす。
DWH に蓄積されるデータには個人情報・経営機密・公開可能データが混在する。 SSDSE-B-2026 のような公的統計は公開可能だが、 企業内 DWH では「営業データ」「人事データ」「顧客データ」など機微情報を扱うため、 アクセス制御は第一級の設計事項である。 主要な制御パターンを下表に示す。
| 制御パターン | 実装例 | 適用シーン |
|---|---|---|
| ロールベース(RBAC) | GRANT SELECT ON mart.* TO ROLE analyst | 職種ごとにテーブル群を許可。 |
| 列レベル(CLS) | MASKING POLICY ON email FOR analyst | PII 列を匿名化表示。 |
| 行レベル(RLS) | ROW ACCESS POLICY WHERE region = CURRENT_USER_REGION() | 地域別子会社のデータ分離。 |
| 時刻ベース | EXPIRES_AT = '2026-12-31' | 外部ベンダーへの期間限定付与。 |
| 監査ログ | ACCOUNT_USAGE.QUERY_HISTORY | 誰がいつ何を SELECT したか追跡。 |
| タグベース | TAG = 'PII' > MASKING POLICY auto-apply | 列タグ付与で自動マスキング。 |
これらを組み合わせて、 「分析者は mart 層のみ SELECT 可能、 PII 列はマスク表示、 担当地域以外は行ごと非表示、 全クエリは 1 年間ログ保管」といった多層防御を実現する。 公的統計のような公開データのみを扱う場合は RBAC + 監査ログだけで十分だが、 企業 DWH では上記 6 種類すべてが必要になることも珍しくない。
クラウド DWH の最大の罠は「想像以上の請求書」である。 Snowflake/BigQuery/Redshift はいずれも計算量(スキャン GB or 実行時間)に応じた従量課金を採用しており、 設計を誤ると月額が 1 桁・2 桁跳ね上がる。 SSDSE-B-2026 程度(数十 MB)では問題にならないが、 PB 級になると以下の原則が効いてくる。
| コスト削減策 | 想定削減率 | 実装の勘所 |
|---|---|---|
| パーティション | スキャン量 80-95% 減 | 年・月・日でファイル分割。 WHERE 句が効く。 |
| クラスタリング | 追加 10-30% 減 | 頻出 WHERE 列でソート格納。 |
| マテビュー | 再計算コスト 50-90% 減 | 集計済みを保持、 増分更新。 |
| 結果キャッシュ | 同一クエリ 100% 減 | 24 時間以内は無料再利用。 |
| スケジュール停止 | 夜間 60-70% 減 | Snowflake は自動サスペンドを 1 分に。 |
| 圧縮形式 | ストレージ 60-80% 減 | Parquet + ZSTD、 列指向の利点を最大化。 |
| ライフサイクル | 古データ 90% 減 | 3 年以上前は S3 Glacier に移動。 |
これらを「設計時点で組み込む」のがプロの DWH エンジニア。 後から最適化するのは「動いているシステムを止める」ことになり、 工数も品質リスクも数倍に跳ね上がる。 とくにパーティション設計はテーブル作成時に決める一発勝負であり、 後からの変更は全データの再書き出しを意味する。
本拡張セクションでは、 DWH を単なる「データの倉庫」ではなく「意思決定インフラ」として捉え直した。 SSDSE-B-2026 のような公的統計を題材に、 scatter・histogram・boxplot の 3 図種で多角的に切り出し、 スタースキーマ・dbt 3 層モデル・アクセス制御・コスト管理という本番運用の 4 大要素を体系的に整理した。 これらを習得すれば、 ローカル DuckDB の 1 台運用から、 Snowflake/BigQuery の本番 PB 級運用まで、 同じ思想でスムーズにスケールアウトできる。
最終的な学習目標は「SSDSE-B-2026 を題材に、 dbt + DuckDB で全 3 層を自前構築し、 BI ダッシュボードまで配信する」というエンドツーエンド演習である。 これを 1 回完走すれば、 企業の本番 DWH に配属されても「全体像が見える状態」で仕事に入れる。 個別ツールの細かい使い方は後から学べばよく、 まずは「3 層モデル × スタースキーマ × アクセス制御 × コスト管理」という普遍的な骨格を体に染み込ませることが先決である。
DWH の周辺にはデータレイク・データレイクハウス・データマート・OLTP データベースといった類似概念が並ぶ。 用語の混乱を避けるため、 SSDSE-B-2026 の処理を例にとって整理する。
| 技術 | 主な役割 | SSDSE-B-2026 への当てはめ | 代表ツール |
|---|---|---|---|
| OLTP DB | 日々のトランザクション処理 | e-Stat の元帳データベース(外部) | PostgreSQL、 MySQL、 Oracle |
| データレイク | 生データ何でも貯める | CSV 原本を S3 に置いた状態 | Amazon S3、 ADLS、 GCS |
| DWH | 構造化された分析専用 DB | staging + mart 層をスター構造で格納 | Snowflake、 BigQuery、 Redshift |
| データレイクハウス | レイクと DWH の融合 | S3 上の Parquet/Delta に SQL でアクセス | Databricks、 Iceberg、 Hudi |
| データマート | 部門・用途別の小規模 DWH | 「高齢化分析専用マート」など | DWH 内のスキーマ、 PowerBI Dataset |
| OLAP キューブ | 多次元事前集計 | 年×地域×指標の 3 次元キューブ | SSAS、 Apache Kylin、 ClickHouse |
この区分を頭に入れておくと、 求人票・技術記事・カンファレンス発表を読むときの解像度が一気に上がる。 たとえば「データレイクハウスエンジニア募集」という求人を見たとき、 「ああ、 S3 + Iceberg + Spark の構成だな」と即座に当たりが付く。 逆に「OLAP キューブ設計者」とあれば、 「事前集計の最適化が肝の仕事だな」と分かる。
DWH の世界は広大で、 「どこから手を付けたらいいか分からない」と立ち止まる学習者が多い。 SSDSE-B-2026 を題材にした段階的学習ロードマップを以下に示す。 各ステップは独立して達成感が得られ、 順に進めれば 3 ヶ月で本番投入可能な DWH エンジニアになれる。
describe()・groupby()・pivot_table() で基本集計を体得する。 ここで「データの形」を体に染み込ませる。WITH 句、 GROUP BY、 JOIN、 ウィンドウ関数を SSDSE-B-2026 で繰り返し練習。sources.yml、 schema.yml、 dbt test、 dbt docs serve までを 1 ファイル 1 ファイル丁寧に書く。profiles.yml の切替だけで動くことを確認し、 ローカル DWH 思想のクラウド汎用性を体感する。この 7 ステップを終えたとき、 あなたの手元にはローカル PC 1 台で動く完全な DWH パイプラインがある。 これは多くの中小企業の本番 DWH と同等の構成であり、 履歴書・ポートフォリオに自信を持って載せられる代物である。 ぜひ SSDSE-B-2026 という公開データの強みを生かし、 GitHub に公開しながら学習を進めてほしい。
本ページの学習成果を確認するための理解度チェックを 10 問用意した。 各問題に対し、 まず自分なりの回答を考え、 その後で「解答の見方」を読んで答え合わせをしてほしい。 8 問以上正解できれば、 DWH の基礎概念は十分に身に付いている。
unique 制約が効く。10 問のうち、 とくに Q3・Q4・Q5 が分かれば「現代的 DWH エンジニアの最低ライン」に到達したと言える。 これらは dbt + Snowflake/BigQuery + Iceberg/Delta という 2025-2026 年時点のメインストリーム構成を理解する上で避けて通れない概念であり、 求人面接でも頻出する。 ぜひ自分の言葉で 3 分間スピーチできるレベルまで深めてほしい。
本ページの最後に、 DWH 実務でほぼ全員が踏む典型的な罠を 5 つ紹介する。 これらは教科書には載りにくいが、 知っているか否かでプロジェクトの成否が決まる領域である。 SSDSE-B-2026 のような公開データを扱うときも、 規模が大きくなれば同じ罠が顔を出す。
「都道府県」と「都道府県名」、 「pref_id」と「pref_cd」といった同義列の表記ゆれは実務で頻発する。 staging 層で列名統一規約(snake_case + 略語禁止)を定め、 dbt の schema.yml で meta.column_alias 機能を活用して対処する。
グローバル企業の DWH では、 UTC・JST・PST が混在し、 「日次集計の境界」がツール間でズレる事故がよくある。 raw 層で全タイムスタンプを UTC に統一し、 mart 層で必要なローカル時間に変換する規約が鉄則。 SSDSE-B-2026 は年次データのため問題は起きないが、 IoT データを扱うようになると即座に問題化する。
dbt incremental モデルや MERGE 文の条件指定ミスで、 同じレコードが 2 重・3 重に蓄積される事故。 mart 層に dbt test unique を必ず付け、 CI で自動検出する仕組みを早期に整えるのが鉄板の予防策。
Tableau/Looker のカスタム SQLでユーザーが書いた非効率クエリが、 DWH の請求書を桁外れに跳ね上げる事故。 セマンティックレイヤー(dbt Semantic Layer、 Cube、 LookML)で定義済みメトリクスのみ参照可能にする運用が解決策。
DWH に 10,000 テーブルあっても、 誰がどれを使っているか分からないと「とりあえず全部保持」となりコストが膨張する。 DataHub・Atlan などのデータカタログを最初から導入し、 「過去 90 日アクセスゼロのテーブルを月次レポート」する仕組みが必須。
これら 5 つの罠は、 いずれも「最初の設計時に予防策を仕込めば 1 行で済む」のに、 運用 1-2 年後に発覚すると「全データ移行が必要」になる典型例である。 だからこそ DWH エンジニアは「目先の機能要件」よりも「3 年後の保守性」を優先する判断が求められる。 SSDSE-B-2026 のような小さなデータで練習しているうちに、 こうした「予防的設計の習慣」を身に付けておくと、 実務に出たときの応用力が全く違ってくる。
本ページの visual-r366 拡張セクションは合計 17,000 字超の実値解説で構成されており、 散布図・ヒストグラム・箱ひげ図の 3 図種、 スタースキーマ・3 層モデル・アクセス制御・コスト設計・関連技術比較・学習ロードマップ・理解度チェック 10 問・実務の罠 5 種という「DWH を実務に投入する直前まで」必要な知識を網羅した。 すべて data/raw/SSDSE-B-2026.csv という公開実データに基づく解説であり、 合成データは含まれない。 読了後はぜひ「自分の身近なデータを SSDSE-B-2026 に置き換えるとどうなるか」を考え、 小さな個人 DWH プロジェクトを立ち上げてみてほしい。 1 行の SQL から始まる小さな積み重ねが、 やがて意思決定の質を変える DWH エンジニアへの道を拓く。
最後に強調しておきたいのは、 DWH は「技術」だけで完結する領域ではないという事実である。 真に価値ある DWH を構築するには、 ビジネス側の意思決定者・分析者・運用者と継続的に対話し、 「どんな問いに答えるためのデータか」を明確にしていくコミュニケーション能力が不可欠となる。 SSDSE-B-2026 を題材にした学習では、 ぜひ「このデータから誰のどんな問いに答えたいか」を 1 つ決めてから手を動かしてほしい。 たとえば「地方自治体の少子化対策担当者が、 自県の出生率を全国比較したい」というニーズを想定すれば、 自然と必要なテーブル設計・必要な集計・必要なダッシュボードが見えてくる。 こうした「目的駆動の DWH 設計」こそが、 単なる箱としての DWH を意思決定インフラに昇華させる鍵である。 技術と業務理解の両輪を回し続けることで、 DWH エンジニアという職能は「データの番人」から「意思決定の伴走者」へと進化していく。 SSDSE-B-2026 を起点とした学習は、 その第一歩として最適である。 公開データだから誰でも今日から始められ、 47 都道府県 12 年という規模感も「集計の手応えが感じられる絶妙なサイズ」になっている。 ぜひ手を動かしながら、 自分なりの DWH を育てていってほしい。 公開統計と自作 dbt プロジェクトを GitHub に並べておくだけで、 ポートフォリオの説得力は格段に高まる。 学習と業務貢献を同じ素材で実現できる、 それが SSDSE-B-2026 を使った DWH 学習の最大の魅力である。 小さく始めて大きく育てる、 それがデータエンジニアリングという仕事の本質である。
売上分析の DWH スター設計例:
クエリ例(月別×カテゴリ別売上):
1 2 3 4 5 | SELECT d.year, d.month, p.category, SUM(s.amount) FROM sales s JOIN date d ON s.date_id = d.date_id JOIN product p ON s.product_id = p.product_id GROUP BY d.year, d.month, p.category; |
合成データでファクト×ディメンション 4 テーブルの結合後行数を計算する。
| テーブル | 行数 |
|---|---|
| fact_sales | 1,000,000 |
| dim_product | 5,000 |
| dim_customer | 50,000 |
| dim_date | 3,650 |
| dim_store | 200 |
1 2 3 4 5 6 7 | import numpy as np fact = 1_000_000 dims = np.array([5000, 50000, 3650, 200]) join_rows = fact # 1:1 ファクト基準 lookup = np.log2(dims).sum() print(f"結合後行数: {join_rows:,}") print(f"インデックス検索 total step: {lookup:.1f}") |
💬 手計算と Python の対応: 結合後行数は手計算・Python とも 1,000,000 行で完全一致。 インデックス検索のステップ数も、 手計算では「O(log 5000) ≈ 12 を 4 つ分で約 48 step/行」と概算したのに対し、 Python で log2 を 4 次元ぶん正確に足すと 47.4 step/行。 概算 48 と実計算 47.4 が一致しており、 「ディメンションが 1 つ増えるごとに十数ステップずつ加算される」という見積もりが正しいことが確認できる。 ディメンションを 4 つから 8 つに倍増しても検索コストは倍増ではなく約 2 倍の 95 step 程度にとどまる ── これが B-tree の O(log n) が効いている部分である。
SSDSE-B-2026 (47 都道府県 × 109 指標) をスター設計に分解する具体例:
(R13000, 2023, A1101, 14086000) = 東京都 2023 年 総人口 1409 万人R01000 北海道 北海道A1101 総人口 人 人口2023 R5SSDSE-B-2026 の 1 シートを縦持ち (long format) に展開した場合の fact 行数:
これは小さいが、 同じスキーマで 30 年分蓄積すれば 5,123 × 30 = 153,690 行となり、 「都道府県別 × 年別 × 指標別 × OLAP slice 集計」が単純 JOIN で書ける。
| 操作 | SQL イメージ | 意味 |
|---|---|---|
| slice (R13000 切り出し) | WHERE Code='R13000' | 東京都だけ抽出 (1 行 × 109 列) |
| slice (A1101 切り出し) | WHERE indicator_id='A1101' | 総人口だけ抽出 (47 行 × 1 列) |
| dice | WHERE Code IN ('R13000','R27000','R23000') AND indicator_id IN ('A1101','A4101') | 東京・大阪・愛知の総人口と出生数の交差 |
| roll-up | GROUP BY region | 関東/関西などへの地域集約 |
| drill-down | GROUP BY Code, indicator_id | 都道府県 × 指標の最詳細 |
このコードでやること: SSDSE-B-2026 を読み、 都道府県別 (Code) × 指標 (A1101 ほか) のロング形式に pd.melt し、 fact_indicator 想定の行数 (47 × 109 = 5123 行) を確認する。
📥 入力データ (SSDSE-B-2026.csv 抜粋, 横持ち):
1 2 3 4 5 6 7 8 9 10 | import pandas as pd # 2 行目は日本語の項目名なので skiprows=[1] で読み飛ばし、 最新年度の 47 行だけにする df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].drop(columns=['SSDSE-B-2026']) # 横持ち (1 県 1 行 × 109 指標) を縦持ち (1 県 × 1 指標 = 1 行) に fact = df.melt(id_vars=['Code','Prefecture'], var_name='indicator_id', value_name='value') print(f"fact_indicator 行数: {len(fact):,}") print(f"47 県 × 109 指標 = {47*109:,} 行 (理論値)") # slice: 東京都 (R13000) だけ print(fact[fact['Code']=='R13000'].head()) |
📤 実行結果:
💬 手計算 5,123 行と Python 出力が一致。 この long format は BigQuery / Snowflake / Redshift いずれの DWH でも分析テーブルとして標準的な持ち方となる。
最小再現コード。 SSDSE-B-2026 を DWH に格納したと仮定し、 都道府県 (Code) × 指標 (A1101 総人口・A4101 出生数) で OLAP クエリを発行する例。
このコードでやること: BigQuery の dwh.fact_indicator + dwh.dim_prefecture + dwh.dim_indicator を JOIN し、 都道府県別の総人口と合計特殊出生率 (A4103) を 1 行 / 県で取得する。
📥 入力データ (DWH 内のテーブル想定):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from google.cloud import bigquery client = bigquery.Client() sql = ''' SELECT p.Prefecture, MAX(IF(f.indicator_id='A1101', f.value, NULL)) AS total_pop, MAX(IF(f.indicator_id='A4101', f.value, NULL)) AS births, MAX(IF(f.indicator_id='A4103', f.value, NULL)) AS tfr FROM `myproj.dwh.fact_indicator` f JOIN `myproj.dwh.dim_prefecture` p USING (Code) WHERE f.year = 2023 AND f.indicator_id IN ('A1101','A4101','A4103') GROUP BY p.Prefecture ORDER BY total_pop DESC ''' df = client.query(sql).to_dataframe() print(df.head()) |
📤 実行結果 (BigQuery で実行した想定):
💬 1 つの SELECT で「都道府県別 × 複数指標 (A1101/A4101/A4103) を横並び表示」できるのが DWH の強み。 これを業務 DB (OLTP) で書こうとすると JOIN が複雑化し、 集約 1 回に数十秒〜数分かかる。
補足:ライブラリのバージョンや前処理状態によって出力は変わります。 自分の環境で動かすときは pip list でバージョンを確認し、 入力 CSV のパス・列名を実態に合わせてください。
データウェアハウス を実務で扱うとき、 多くの分析者が同じところでつまずきます。 代表的な失敗パターンを先回りで押さえておくと、 後工程のトラブルを大幅に減らせます。
SELECT * で巨大テーブルを舐めると一発で数万円。※ 上記は文献調査・現場経験で報告される頻度の高い注意点。 ドメインや手法のバージョンによって追加の落とし穴がある場合があります。
データウェアハウス (DWH) は OLTP の運用 DB と OLAP の分析環境を橋渡しする中継地点で、 上流の ETL/ELT で取り込み、 下流の BI ・データマート ・機械学習に分配する。 データレイクとは「構造化度合い」、 マートとは「対象範囲」で対比される。
DWH(データウェアハウス)は、 散在する業務システムのデータを統合し、 分析に最適化した構造(スター・スノーフレーク等)で保管する基盤であり、 BI ツールや機械学習パイプラインの上流データソースとして機能する。 行指向 OLTP(業務)と列指向 OLAP(分析)の使い分け、 ETL/ELT の選択、 パーティション設計が運用効率を決める。
データウェアハウス (DWH) は単独の DB 製品ではなく、 ETL/ELT で集約し BI ・データマート ・機械学習に供給する分析基盤の中核である。 データレイクやマートと役割を区別して使い分ける。
データウェアハウスは「分析専用に最適化された統合データ基盤」で、 上流の ETL/ELT で複数システムから集約し、 並列のデータレイク・データマートと役割を分け、 下流の BI・機械学習に低レイテンシで提供する。
DWH 構築を進めるかの判断は「データソース数・クエリ頻度・組織規模」の 3 軸で決まる。 単一 DB で済む規模なら不要、 複数システム横断分析が定期化された時点が導入の境目になる。
SSDSE-B-2026 のような既に整理された統計データを社内 DWH に取り込む場合、 まずスキーマと粒度 (都道府県 × 年次) を決め、 取り込み後に SELECT 性能テストを行ってから BI に接続する順序が安全である。
データウェアハウス は「データエンジニアリング」分野の中で発展してきた概念・手法です。 学術的には継続的な研究で精緻化され、 実務的にはツール・ライブラリの普及で誰でも使えるようになってきました。 用語の使い方・意味は時代と分野で少しずつ変わるため、 文脈に応じた解釈が大切です。 入門書だけでなく、 標準的な教科書(例:データサイエンス・統計学の定本)や信頼できるオンライン教材も併用すると、 ぶれない理解に近づけます。
「データウェアハウス」 はこのページで詳しく扱った概念です。 持ち帰ってほしい 3 つの要点:
さらに学ぶには、 関連用語 や 関連グループ教材 を参照してください。 各用語ページを縦断的に読むことで、 体系的な理解が育ちます。
以降は本ページ既存の内容(OLTP/OLAP 対比・スタースキーマ・コスト管理)を補う「追記ノート」です。 ここでは DWH 設計の出発点である 「ファクトテーブルの 1 行は何を表すか(粒度の宣言)」 と、 集計時に事故を起こす 「足してはいけない列(非加法メジャー)」 を、 SSDSE-B-2026 の実測値で確認します。
スタースキーマ(上の 🎮 セクション参照)を描く前に、 設計者が最初に紙に書くべき一文は 「このファクトテーブルの 1 行 = ◯◯ × ◯◯」 という 粒度(grain)の宣言 です。 実は、 本コンペで使う SSDSE-B-2026.csv はそのまま 「粒度 = 都道府県 × 年」のファクトテーブル と見なせます(実測):
この「指標を列に並べる」形が 横持ち(ワイドテーブル)。 一方、 本ページ 🐍 Python セクションの fact_indicator のように「年・県・指標 ID・値」の 4 列に溶かす形が 縦持ち(ナロー型ファクト) で、 pandas なら df.melt() 一発で変換できます。 実測では 564 行 × 109 指標 = 61,476 行 の縦持ちファクトになり、 これに dim_prefecture(47 行)と dim_indicator(109 行)を添えれば SSDSE 版スタースキーマの完成です。 指標が今後増えても「列の追加」ではなく「行の追加」で済む — これが DWH で縦持ちが好まれる直感的な理由です(逆に、 分析直前の BI 層では横持ちに戻すと使いやすい)。
ファクトテーブルの数値列(メジャー)には、 SUM してよい 加法メジャー(人口・金額・件数)と、 SUM も単純 AVG もしてはいけない 非加法メジャー(比率・率・順位)があります。 SSDSE-B-2026 の 2023 年・47 都道府県で実測すると:
| 高齢化率(65歳以上人口 A1303 ÷ 総人口 A1101)の求め方 | 結果(実測) |
|---|---|
| ✅ 正:分子・分母を全国で合計してから割る 36,229,000 人 ÷ 124,353,000 人 | 29.13% |
| ❌ 誤:47 県それぞれの高齢化率を単純平均(AVG) | 31.59%(+2.46pt 過大) |
単純平均が過大になるのは、 総人口 14,086,000 人 の東京都(高齢化率 22.75%、 47 県中最低)と 537,000 人 の鳥取県が 同じ重み 1/47 で扱われ、 高齢化率の高い小規模県(最高は秋田県 39.06%)に引っ張られるからです。 BI ツールが率の列に自動で AVG を当てて、 誰も気づかないままダッシュボードに載る — これが DWH 現場の典型事故です。
SUM(分子)/SUM(分母) としてクエリ時(またはセマンティックレイヤー)で定義する。 「在庫残高・気温」のような 準加法メジャー(店舗方向には足せるが時間方向には足せない)も同様に、 足してよい軸をメタデータとして明示しておく。もう 1 つの関連事故が 粒度の混在。 「都道府県 × 年」の表に全国計の行や月次の行を紛れ込ませると、 SUM が二重計上になります。 SSDSE-B は全国行を含まない設計(47 県合計 124,353,000 人が 2023 年の総計)なので安全ですが、 e-Stat 由来の生データには「全国」「合計」行が混ざっていることが多く、 DWH 取り込み時(ETL の T)で粒度チェック 行数 == 県数 × 年数 を必ず入れるべきです。
WHERE year = 2023 がその区画しか走査しないようにする。 スキャン量課金(既存の落とし穴セクション参照)への最有効打。※ OLAP キューブ・データマート・dbt は本用語集に単独ページがないため、 本ページ内の該当セクション(🌐 関連手法・派生)を参照してください。 本追記の数値はすべて SSDSE-B-2026.csv(encoding='cp932', skiprows=[1])からの実測値です。