論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
ELT
Extract-Load-Transform
データエンジニアリング

🔖 キーワード索引

この用語と一緒に検索・参照されやすいタグ。 関連ページに飛ぶときの手がかりにも使えます。

#データエンジニアリング#ETL#ELT#データ基盤#DWH

elt」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「elt」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。

ELTExtractLoadTransformdbtBigQuerySnowflakeDWHETL との対比

これらのキーワードは「elt の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。

💡 30秒で分かる結論

🍰 まずはやさしく

データを先に保存して後で加工する方法です。

効率よくデータを分析するために使います。

スマホの写真を全部保存して後で分ける感じです。

ここではELTの結論を短くまとめます。

ELT(Extract-Load-Transform)は、 データを先にロードしてから DWH 内で変換する近代的データ基盤パターン。 ETL の進化形。

ここまでが要点です。 ただし実際に使う前に、 このページの「⚠️ よくある落とし穴」で挙げた DWH コスト爆発/変換ロジックの散逸/PII を生で保持 には必ず目を通してください。 つまずくのは知識が無いときより、 知ってはいたが確認を飛ばしたときです。

📍 文脈:「ELT」はどんな場面で出てくる?

🍰 まずはやさしく

大きなデータを扱うときの基本ルールです。

仕事で分析基盤(データの土台)を作る時に使います。

部活の大量の記録を整理する場面に似ています。

どのような場面でこの考え方が役立つか読みます。

SSDSE のような単一 CSV はETL/ELT 不要ですが、 企業の分析基盤では避けて通れない概念。 「データレイクハウス」「Modern Data Stack」の中核を成します。

この用語は一見すると単独で理解できそうに見えますが、 実際には前提となる概念(測定・尺度・サンプリングなど)と組合せて初めて意味を持ちます。 「定義を覚える」より「どんな問いに答える道具なのか」を捉えるのが効率的です。

🎨 直感で掴む

🍰 まずはやさしく

素材のまま冷蔵庫に入れて後で料理するイメージです。

状況に合わせて柔軟にデータを変えるために使います。

買い物した食材をまず全部棚に入れる感覚です。

直感的にELTがどんなものか理解しましょう。

「ELT」を最初に学ぶときは、 厳密な定義よりイメージを優先しましょう。 以下は具体例・比喩を用いた直感的理解の入口です。

💡 学習のコツ:上の比喩は厳密ではない点に注意。 直感で全体像を掴んだら、 次の「📐 定義・数式」で正確な意味を押さえ、 最後に「🧮 実値で計算してみる」で実感を伴った理解に到達するのが効率的です。

📐 定義・数式

🍰 まずはやさしく

データの流れを順番に決めた仕組みのことです。

間違いのない正確な処理を行うために使います。

テストの点数を集めてから平均を出す流れに似ています。

ELTの正確な定義と仕組みについて読みます。

直感の次は、 厳密な定義を確認します。 数式は言語の一種で、 一度書き慣れれば「言葉より速く伝えられる」便利な道具。 慣れていない方は、 各記号が何を表すかを「🔬 数式を言葉で読み解く」で 1 つずつ確認してください。

【ELT パイプラインの基本構造】
$$ \text{Source} \xrightarrow{\text{Extract}} \text{Raw Zone} \xrightarrow{\text{Load}} \text{DWH} \xrightarrow{\text{Transform (SQL)}} \text{Mart} $$
生データをRaw → Staging → Martの 3 層で管理するのが標準(メダリオン構造)。
📌 読み方のコツ:数式を見たら「左辺は何を定義しているか」「右辺の各項は何の合計・積・比か」を声に出して読み下してみる。 これだけで理解が大きく進みます。

📐 ELT と ETL の決定的な違い — 順序が変えるアーキテクチャ

ETL (Extract → Transform → Load) と ELT (Extract → Load → Transform) は字面が似ているが、 内部の計算リソース配置とスケール戦略が根本的に違う。 ETL は中間レイヤ (ETL サーバ) で変換、 ELT は変換を DWH 内 (BigQuery/Snowflake/Redshift) で実行する。

観点ETL (古典)ELT (現代)
変換実行場所専用 ETL サーバ (Informatica, Talend)DWH 内 (SQL/dbt)
前提DWH の計算力が限定的クラウド DWH は無限スケール
生データ保持通常破棄 (変換後のみ DB に入る)生データを raw layer で保持 (再変換可)
変換の柔軟性変更時にパイプライン再デプロイSQL/dbt で気軽に再実行
適したデータ量中規模 (~TB)大規模 (TB~PB)
スキル要件ETL ツール GUI/プログラムSQL + Jinja (dbt) + YAML
コスト構造サーバ + ライセンス固定DWH の計算秒数で従量
代表ツールInformatica PowerCenter, Talend, SSISFivetran/Airbyte + dbt + Snowflake/BigQuery

💬 「クラウド DWH 革命」(2010s 後半) が ELT 流行の前提。 Snowflake/BigQuery が秒単位課金 + 並列クエリで 計算リソースが事実上無限になり、 「データを動かさずに加工する方が速くて安い」になった。 ETL 時代の「データ移動が高コスト・DB は計算しない」前提が逆転した。

📐 Slowly Changing Dimensions (SCD) — ディメンション履歴の管理

マスター系テーブル (Dimension) のレコードが時間とともに変わるとき、 どう履歴を残すか。 これが SCD 問題。 4 種類の戦略があり、 dbt の snapshot 機能や Type 2 が最も普及している。

タイプ挙動用途
SCD Type 0変更を無視 (初回値を固定)生年月日など本来不変の属性
SCD Type 1UPDATE で上書き、 履歴消失スペルミス修正など履歴不要
SCD Type 2変更ごとに新行追加、 valid_from/valid_to で期間付与最も普及、 過去時点の状態を再現可能
SCD Type 3「現在の値」と「前の値」を列で持つ変化が稀で 1 つ前だけ必要
SCD Type 4履歴を別テーブルに分離本表は軽量にしたい時
SCD Type 6Type 1 + 2 + 3 のハイブリッド最大柔軟、 複雑

💬 ELT の Transform 層で SCD Type 2 を実装するなら dbt snapshot が便利。 updated_at 列 (もしくはハッシュ) を基準に変更検知し、 dbt_valid_from / dbt_valid_to 列を自動付与してくれる。 都道府県マスタが滅多に変わらなくても、 「2020 年に静岡県の県庁所在地コードが変わった」のような史実は SCD2 で残せる。

📐 Data Lineage — 上流変更の影響範囲を可視化

ELT で 100+ モデルを積み上げると、 「raw.users テーブルのスキーマが変わったら何が壊れる?」という影響解析が必要になる。 dbt は dbt docs generate で全モデルのリネージ DAG を HTML で自動生成。 OpenLineage / Marquez など別エコシステムもある。

ツール特徴使いどころ
dbt docsSQL ref() から自動推論dbt 内のモデル間リネージ
OpenLineage + MarquezAirflow/Spark/dbt 横断的に収集マルチエンジン環境
Atlan / DataHubエンタープライズカタログ + リネージ数百テーブル規模
Monte Carloデータオブザーバビリティ + 異常検知SLA 必須の本番 DWH
SqlLineage (OSS)SQL 文を解析して列レベル lineageCI でリネージ自動検証

📐 バッチ vs ストリーミング ELT の選び分け

要件バッチ ELTマイクロバッチストリーミング
遅延要件数時間 OK5〜15 分数秒
主要ツールAirflow + dbtdbt + scheduler 短間隔Kafka + Flink/Spark Streaming
運用コスト低 (1 人月)高 (運用専任必要)
DWH コスト高 (常時 warehouse 稼働)
複雑度高 (順序・重複・遅延データ)
典型ユースケース経営 BI、 月次レポートマーケ ダッシュボード不正検知、 アラート、 在庫

💬 「ストリーミング ELT」は技術的に華やかだが、 99% の業務要件はバッチで十分。 「リアルタイムが必要」と言い出した時、 (a) ビジネス価値が運用コスト + DWH コスト増を上回るか、 (b) マイクロバッチ (15 分間隔) で要件を満たせないか、 を必ず確認すべき。

❓ ELT Deep FAQ — 実務でよく出る 10 問

疑問答え
Q1. ELT と Data Lake の関係は?Data Lake は raw 層に近い概念。 ELT 三層 (raw/staging/mart) の raw = Data Lake、 staging+mart = Data Warehouse と捉えると整理しやすい。
Q2. Medallion アーキテクチャと同じ?基本同じ。 Bronze (raw) → Silver (staging) → Gold (mart)。 Databricks が広めた用語で、 ELT の標準的階層化を別名で呼んでいる。
Q3. SQL 苦手な分析者は ELT どう使う?dbt の models は SQL だが、 BI ツール (Looker, Tableau, Metabase) から mart を読むだけなら GUI で完結。 dbt は基盤チームが書き、 分析者は mart を消費するロールが標準。
Q4. ETL から ELT への移行のリスクは?既存変換ロジックの SQL 化、 raw 層への切替に伴うストレージ増、 SQL チューニング知見の必要、 コスト管理失敗の可能性。 段階的に移行する。
Q5. PII (個人情報) の扱い?raw 層に入る前に hash/mask する。 BigQuery なら Column-level access、 Snowflake なら Dynamic Data Masking で「権限ある人だけ平文」を実現。
Q6. dbt と Apache Hive の違い?Hive は分散 SQL エンジン (実行)、 dbt は SQL を整理するフレームワーク (オーガナイザー)。 dbt の出力先として Hive/Spark/BigQuery/Snowflake すべて選べる。
Q7. ELT の dev/prod 環境分離は?dbt は profiles.yml で dev (個人 schema) / prod (mart schema) を切替。 dev では sample データだけ走らせコスト圧縮。
Q8. dbt と Airflow どちらをスケジューラに?dbt Cloud は組み込みスケジューラあり (簡易)。 複雑な依存 (E+L+dbt+ML+通知) が要れば Airflow/Dagster で全体オーケストレーション、 dbt は T 層担当。
Q9. ELT の監視メトリクスは何を取る?(1) 行数推移、 (2) freshness (最終更新時刻)、 (3) クエリ時間、 (4) コスト、 (5) テスト成功率。 Monte Carlo / Datadog で監視。
Q10. ELT パイプラインを CI/CD に組み込むには?GitHub Actions で PR 時 dbt build --select state:modified+ を実行 → 変更モデルのみテスト。 マージ後本番デプロイ。

🔬 数式を言葉で読み解く — 数式を「言葉」に翻訳

数式を眺めるだけでは身につかないので、 各記号がどんな役割を担っているかを言葉で押さえます。 「数式を音読する習慣」がつくと、 論文や教科書を読むスピードが体感で 2 倍ほど上がります。

Source
業務 DB・SaaS・ファイル等
Extract
データ抽出(連携ツール)
Load
DWH/レイクへの投入
Transform
SQL/dbt による変換
Mart
分析向けに整形されたテーブル
📚 補足:同じ記号でも分野・教科書によって意味が違うことがあります(例: $\hat{y}$ は予測値だが、 統計の文脈では推定量を意味することも)。 不明確なときは、 必ずその文書の記号定義表を確認しましょう。

🧮 実値で計算してみる

数式だけでは「実感」が湧きにくいので、 具体的な数値で 1 度手計算してみると理解が定着します。 以下の例は、 本サイトで扱う SSDSE-B-2026 や公開教材に近い形式で用意しました。

典型的な構成と役割:

役割更新頻度
Raw生データ保管(変更しない)到着次第JSON / CSV そのまま
Staging型・命名統一日次SQL で軽い整形
MartBI 用集計日次/時次KPI・ダッシュボード用

手計算で得た値と、 後述の Python 実装で算出した値が一致することを確認すると、 「数式とコードの対応関係」がクリアに見えるようになります。

🧮 SSDSE-B-2026 で年度パーティション ELT を実演

大規模 fact テーブルのパーティション設計を SSDSE-B-2026 で疑似体験する。 年度をパーティションキーにして「2023 年だけスキャン」を実現すると、 12 年分すべてスキャンする場合と比べてコストが約 1/12 になる。

🎯 このコードでやること: DuckDB で年度パーティション風に PARTITIONED BY 句相当の方法 (= PARQUET 出力 + Hive スタイルパーティション) でデータを書き出し、 「2023 年のみ」を読み込む SELECT がフルスキャンよりどれだけ速いか測る。

📥 入力データ:

mart.fact_birth_rate (564 行, year ∈ {2012..2023}) 出力先: data/parquet/fact_birth_rate/year=YYYY/000.parquet (Hive パーティション)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import duckdb, time
con = duckdb.connect('dwh.duckdb')

# Parquet 出力 + Hive スタイル年度パーティション
con.execute("""
COPY (SELECT * FROM mart.fact_birth_rate) TO 'data/parquet/fact_birth_rate'
(FORMAT PARQUET, PARTITION_BY (year), OVERWRITE_OR_IGNORE);
""")

# ① パーティション枝刈りあり (2023 のみ)
t0 = time.time()
df1 = con.execute("SELECT * FROM 'data/parquet/fact_birth_rate/year=2023/*.parquet'").df()
print(f'パーティション枝刈り: {len(df1)} 行、 {time.time()-t0:.4f} 秒')

# ② フルスキャン (全 12 年)
t0 = time.time()
df2 = con.execute("SELECT * FROM 'data/parquet/fact_birth_rate/**/*.parquet' WHERE year=2023").df()
print(f'フルスキャン:        {len(df2)} 行、 {time.time()-t0:.4f} 秒')

📤 実行結果:

出力ディレクトリ構造: data/parquet/fact_birth_rate/ year=2012/000.parquet year=2013/000.parquet ... year=2023/000.parquet パーティション枝刈り: 47 行、 0.0023 秒 フルスキャン: 47 行、 0.0181 秒 ← 8 倍遅い 【BigQuery 換算 ($5/TB)】 パーティションなし 10TB テーブル: 1 クエリ $50 パーティションあり 2023 だけスキャン: $50 / 12 = $4.17 → 92% 削減

💬 読み方: パーティション枝刈りは DWH の最重要 cost-saving 戦略。 SSDSE-B-2026 の 564 行レベルでも 8 倍速いし、 1 億行・10 TB スケールでは差が直接コスト 12 倍違いになる。 BigQuery では PARTITION BY DATE(event_time)、 Snowflake では CLUSTER BY、 dbt では config(partition_by={...}) で同等を実現。

📋 ELT パイプライン構築 完全チェックリスト

フェーズチェック項目ツール例
Extractソースに対する API/JDBC コネクタ、 schema drift 対応Fivetran, Airbyte, Stitch, custom Python
Loadraw 層に無変換投入、 ロードバッチ ID 付与DWH の COPY INTO / bq load / Snowpipe
Transformstaging → mart の dbt モデル、 ref() で依存dbt, Dataform, SQLMesh
Testnot_null, unique, accepted_values, relationshipsdbt test, Great Expectations
Documentmodel description, column description, lineagedbt docs, DataHub, Atlan
Orchestrateスケジュール、 リトライ、 アラートAirflow, Dagster, Prefect, dbt Cloud
Monitorfreshness, 行数異常、 SLAMonte Carlo, Datafold, elementary
Governアクセス制御、 PII マスキング、 監査ログImmuta, BigQuery IAM, Snowflake roles
Cost Controlパーティション、 incremental、 quotaDWH 標準機能 + 内製モニタリング
CI/CDPR で dbt build state:modified+、 マージで本番デプロイGitHub Actions, CircleCI

💬 このチェックリストの 10 項目すべてに「誰が・どのツールで・どう実装しているか」答えられれば、 ELT 基盤として一級品。 4〜6 項目しか埋まっていない場合は「未成熟」とみなし、 残りを順次整備していく。 特に Test と Monitor が抜けていると、 数か月後にデータ品質崩壊が発覚する典型パターンに陥る。

📜 ELT の歴史 — クラウド DWH 革命がもたらしたパラダイムシフト

出来事意義
1990sInformatica, Oracle Warehouse BuilderETL ツール商用時代、 GUI 中心
2006Hadoop / MapReduce分散処理で TB 級データ可
2010HiveHadoop 上で SQL、 ELT 萌芽
2011Google BigQuery GAサーバレス DWH、 ペタバイトを秒で
2014Snowflake 1.0計算/ストレージ分離、 マルチクラウド
2016dbt v0.1 (Fishtown Analytics)SQL ベース ELT フレームワーク誕生
2018Fivetran / Airbyte 隆盛Extract+Load の SaaS 化
2020+Modern Data Stack 確立Fivetran + dbt + Snowflake/BQ が標準
2022+Data Mesh、 Lakehouse、 OpenTable FormatIceberg/Delta/Hudi で DWH 境界が再溶解

💬 ELT は「クラウド DWH の出現 + dbt の登場」という 2 つの偶然が重なって 2018 年頃から急速に普及。 ETL を駆逐したのではなく「使い分けるもの」になった ── レガシー DB 連携や複雑な業務ルールは ETL、 クラウド主導のアナリティクスは ELT、 ストリーミングは Flink + dbt のような融合形態が現代の選択肢。

🎯 まとめ — ELT を「ちゃんと運用する」ための 5 段階

  1. Stage 1 — Hello ELT: 1 つの SaaS (Fivetran) + 1 つの DWH (BigQuery) + dbt 数モデルで小さく回す。 raw/staging/mart の三層を守る。
  2. Stage 2 — Test 駆動: dbt test を全 mart モデルに入れる。 not_null/unique/accepted_values で契約を明文化、 PR で CI 検証。
  3. Stage 3 — コスト最適化: パーティション・クラスタリングを mart に適用、 incremental materialization で再構築コストを削る。 月次予算アラートを設定。
  4. Stage 4 — オブザーバビリティ: Monte Carlo / elementary で freshness・行数・スキーマ変化を監視。 SLA を定義しダッシュボード化。
  5. Stage 5 — ガバナンス: アクセス制御 (IAM/Role)、 PII マスキング、 監査ログ、 Data Catalog (DataHub) で組織横断的に発見可能に。

💬 多くの組織は Stage 1〜2 でしばらく止まる。 Stage 3 以降に進むには「分析者だけでなくデータエンジニア」がチームに必要。 ELT は「SQL だけで完結する分」プログラミング寄りのスキルセットを軽視しがちだが、 Stage 4〜5 ではむしろ本格的な SRE / DevOps スキルが要求される ── 「ELT が簡単に見えるのは Stage 1 だけ」というのが現場の真実。

🐍 ELT のコストモデルを SSDSE-B-2026 で評価する

ELT は「DWH (BigQuery/Snowflake) の従量課金 = スキャン GB あたりコスト」が中心。 ETL は「専用 ETL サーバの維持コスト」が中心。 ここでは SSDSE-B-2026 の都道府県データをロードし、 行数とスキャン量から想定コストを試算する。 数式を言葉で読み解く工程を入れつつ、 ELT が「クエリ最適化と相性が良い」理由を明確にする。

📐 ELT 月額コストの定義

$$ \mathrm{Cost}_{ELT} = c_{store} \cdot V_{store} + c_{scan} \cdot \sum_{q=1}^{Q} V_q^{scan} $$

数式を言葉で読み解く: c_store はストレージ単価 (GB/月)、 V_store は保管総量。 c_scan はスキャン単価 (BigQuery で約 6.25 USD/TB)、 V_q^scan はクエリ q が舐めるバイト数。 ELT の運用コストは「クエリの設計」が支配的で、 パーティション・カラムナ・SELECT * 回避で大きく下げられる。

🧮 SSDSE-B-2026 を BigQuery に乗せた想定で実値計算

実値: SSDSE-B-2026 は 47 都道府県 × 約 100 列 × 7 年 ≈ 33,000 行。 1 行平均 1.2 KB として保管量は 40 MB、 1 日 100 クエリ × 平均 5 MB スキャンと仮定。

項目単価月量月コスト
ストレージ0.02 USD/GB0.04 GB0.0008 USD
スキャン6.25 USD/TB15 GB (100q × 30day × 5MB)0.094 USD
合計≈ 0.095 USD/月

🐍 BigQuery 風 ELT パイプラインを Python で試算

🎯 このコードでやること:SSDSE-B-2026 をロード後、 想定スキャン量と単価から月額コストを試算し、 さらに SELECT *SELECT col1, col2 に絞った場合の削減効果を比較する。

📥 入力例 (SSDSE-B-2026 の主要カラム):

SSDSE-B-2026.csv (47 都道府県 × 12 年度, cp932, skiprows=[1]) columns: SSDSE-B-2026(年), Code, Prefecture, A1101(総人口), A1303(65歳以上), A4101(出生数), A4200(死亡数), ... rows: 564, columns: 112, raw size: 約 0.6 MB parquet (columnar): 約 0.15 MB
 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
# ELT コスト試算: BigQuery スキャン単価ベース
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
total_rows = len(df)
total_cols = len(df.columns)
storage_gb = df.memory_usage(deep=True).sum() / 1e9

# BigQuery 単価
PRICE_STORAGE = 0.02   # USD per GB-month
PRICE_SCAN_TB = 6.25   # USD per TB scanned

# 想定: 月 100 クエリ × 30 日
queries_per_month = 100 * 30

# SELECT * の場合: 全カラムスキャン
scan_select_all_gb = storage_gb * queries_per_month
cost_select_all = scan_select_all_gb / 1024 * PRICE_SCAN_TB

# SELECT 5 cols の場合: 5/total_cols だけスキャン
scan_select_5_gb = storage_gb * (5 / total_cols) * queries_per_month
cost_select_5 = scan_select_5_gb / 1024 * PRICE_SCAN_TB

storage_cost = storage_gb * PRICE_STORAGE

print(f"行数={total_rows}, 列数={total_cols}, ストレージ={storage_gb*1000:.2f} MB")
print(f"月ストレージコスト    : {storage_cost:.6f} USD")
print(f"SELECT * (全列)       : {cost_select_all:.4f} USD/月")
print(f"SELECT 5列 (列指定)   : {cost_select_5:.4f} USD/月  ({(1-cost_select_5/cost_select_all)*100:.1f}% 削減)")

📤 実行すると次の出力が得られる:

行数=564, 列数=112, ストレージ=0.57 MB 月ストレージコスト : 0.000011 USD SELECT * (全列) : 0.0104 USD/月 SELECT 5列 (列指定) : 0.0005 USD/月 (95.5% 削減)

💬 列指定で 95% 以上 スキャン量が削減される。 これが BigQuery / Snowflake などカラムナ DWH のメリットであり、 ELT パターンで「SELECT * 禁止」が現場ルールになる理由でもある。

⚠️ ELT コスト管理の落とし穴

  • ダッシュボード暴走:Looker/Tableau の自動更新でクエリが分単位で走り、 月額が想定の 100 倍になる事例多発。 必ず maximum_bytes_billed でガード。
  • パーティション無視:時系列カラムが PARTITION 化されていないと WHERE 句があっても全テーブルスキャンになる。 dbt の partition_by 設定必須。
  • マテリアライズドビュー乱用:MV はストレージと更新コストがかかる。 「読み回数 × スキャン削減効果」が更新コストを上回らないなら使わない方が安い。
  • SELECT * の常態化:上の試算通り、 単純な列絞りで 95% コスト削減できる。 BI 設計時点で必要列を意識する。

🧮 数式に値を入れて手で計算する: ELT vs ETL の処理時間

合成データで ELT (Load 先行) と ETL (Transform 先行) の総時間を比較する。

Step 1: 工程別時間 [分]

パターンExtractLoadTransform合計
ETL102030 (CPU)60
ELT102010 (DB 内)40

Step 2: 高速化倍率

ELT は DB の SQL エンジンで Transform → 3 倍高速 合計差 = 60 - 40 = 20 分短縮 (33% 削減)

🐍 Python で再現

1
2
3
4
5
etl = 10 + 20 + 30
elt = 10 + 20 + 10
print(f"ETL: {etl} 分")
print(f"ELT: {elt} 分")
print(f"短縮: {etl-elt} 分 ({(etl-elt)/etl*100:.0f}%)")

📤 実行結果

ETL: 60 分 ELT: 40 分 短縮: 20 分 (33%)

💬 手計算 (Step 2) 33% 削減と Python 出力が完全一致。

🐍 Python 実装

公的統計(SSDSE-B-2026)を題材に、 最小限の Python コードで動作させます。 ファイルパス(data/raw/SSDSE-B-2026.csv)は自分の環境に合わせて変更してください。 まずはこのまま動かすことが理解の最短ルートです。

🎯 解説: ELT パイプラインの第 1 段(E + L)を Python で実装。 SSDSE-B-2026 を「まず Raw 層に取り込み」(Extract → Load)、 次に SQL/pandas で「Staging 層に変換」(Transform)するという ELT 思想を、 ファイルベースのミニ Lakehouse 構成(CSV → Parquet)で再現する。 列リネーム・派生列追加(高齢化率=A1303/A1101)が Transform の典型。
📥 入力例: data/raw/SSDSE-B-2026.csv(公的統計、 564 行 × 112 列、 cp932) 必要列: A1101(総人口)、 A1303(65 歳以上人口) 年列 "SSDSE-B-2026"・Prefecture → year, pref へ標準化 Raw 層は原本そのまま保存(再計算可能性のため)、 Staging 層から下流が参照
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import os
os.makedirs('data', exist_ok=True)   # 書き出し先を先に作る
import os
import pandas as pd
import pyarrow  # parquet の読み書きに必要

os.makedirs('data/staging', exist_ok=True)  # 保存先のフォルダを作っておく

# ── この抜粋だけで動くように、Raw 層の表 raw を読み込む ──
raw = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])

# SSDSE-B-2026 を Raw → Staging へ変換する最小 ELT 例
raw = raw[raw['SSDSE-B-2026'] == 2023]              # 2023 年度だけ抽出
stg = (raw.rename(columns={'SSDSE-B-2026': 'year', 'Prefecture': 'pref'})
          .assign(高齢化率=lambda d: d['A1303'] / d['A1101']))   # 65歳以上 / 総人口
stg = stg[['year', 'pref', 'A1101', 'A1303', '高齢化率']]
stg.to_parquet('data/staging/ssdse_b.parquet')
print(stg.head())
📤 実行例: year pref A1101 A1303 高齢化率 0 2023 北海道 5092000 1681000 0.330 1 2023 青森県 1184000 417000 0.352 2 2023 岩手県 1163000 407000 0.350 … 12 2023 東京都 14086000 3205000 0.228 … 46 2023 沖縄県 1468000 350000 0.238 → 出力: data/staging/ssdse_b.parquet(カラムナ形式、 47 行 × 5 列)
💬 読み方: ETL(先に Transform)と ELT(先に Load)の本質的な違いは「変換ロジックの保管場所」。 ELT では Raw 層に生データが残るため、 後から要件が変わっても再変換でき、 監査・再現性に強い。 dbt や Dataform は SQL ベースで Staging→Mart 変換を宣言的に書ける現代的ツール。 Parquet を選ぶのは列指向圧縮で BigQuery/Snowflake と相性が良いため。 CSV のままだと毎回パース+全列読込みで遅い。

▶ 実行 を押せばこのページの中でそのまま動きます(ライブラリもデータも同梱済みで、 準備は要りません)。 手元の Python に移して動かすときは pip install pandas が必要です。 読んでいるデータは data/raw/SSDSE-B-2026.csv。 日本語を含むので encoding='cp932' の指定を落とさないでください。

本サイトの全コードは 論文一覧ページ から実例として確認できます。 自分のデータで試したい場合は、 列名・欠損記号・単位の違いだけ調整すれば、 ほぼそのまま流用できます。

👣 ステップバイステップ実例

「ELT」を初めて使う方向けに、 ハンズオン的な実行手順を整理します。 上の Python 実装と組み合わせて、 1 度自分の手でなぞってみることを強く推奨します。

  1. 環境準備:このページのコードは ▶ 実行 ボタンでそのまま動くので、 まずは何も入れずに試す。 手元で動かしたくなったら Python 3.9 以上に pandas・scipy・matplotlib を入れ、 Jupyter Notebook か Google Colab を使うと試行錯誤しやすい。
  2. データ取得:本サイト題材の SSDSE-B-2026 を data/raw/ に配置(または自分のデータを用意)。 列名と単位を確認。
  3. 探索的に観察df.head()df.describe()df.isna().sum() で全体像を把握。 ここで欠損や外れ値の見当を付ける。
  4. 前提検証:ELT をこのデータに当てはめてよいか(このページの「⚠️ よくある落とし穴」で挙げた DWH コスト爆発・変換ロジックの散逸 など)を確認。 NG なら別手法を検討。
  5. 本処理:上のコードブロックを参考に、 関数を呼び出して値を取得。 中間出力をその都度プリントして合っているか確認。
  6. 結果可視化:散布図、 棒グラフ、 ヒートマップなど、 解釈しやすい図を 1〜2 枚作る。 タイトルには結論を書く。
  7. 解釈・記録:「📝 レポートでの報告」の 5 点セットに沿って Notebook に書き残す。 後の自分のために結論・限界・次の一手を明記。
  8. 共有:Notebook を GitHub や Drive に置き、 関係者にレビュー依頼。 ピアレビューで穴が見つかることが多いので大事。

この 8 ステップを 1 度回すと、 「用語を読んで分かった気になる」段階から「実際に使える」段階に進めます。 知識は身体で覚えるのが結局のところ最速です。

🖼️ ELT パイプラインの可視化サンプル

ELT は 抽出(Extract)→ ロード(Load)→ 変換(Transform)の順で動くため、 ロード直後の生データと、 SQL 変換後の集計データを並べて見ると、 パイプラインのどこで欠損や偏りが入り込むかを直感的に把握できる。 ここでは SSDSE-B-2026 都道府県データ(df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932'))を BigQuery 風データウェアハウスにロードした想定で、 3 種類の標準図を提示する。

🎨 図1:人口と死亡数の散布図(ELT 後の変換結果)

このコードでやること:ELT パイプラインでロード済みの raw 表(raw_b2026)から、 2023 年度の都道府県 47 行を SQL で SELECT し、 そのまま matplotlib に渡して散布図を描く。 これは Transform レイヤの代表例で、 BI ツールが裏で実行している処理に等しい。

📥 入力データ(SSDSE-B-2026 の 2023 年度抜粋、 A1101=総人口 / A4200=死亡数):

Code Prefecture A1101(総人口) A4200(死亡数) R01000 北海道 5092000 75120 R13000 東京都 14086000 137241 R27000 大阪府 8763000 104964 R47000 沖縄県 1468000 15110
人口と死亡数の散布図
図1:ELT で変換した 2023 年度の都道府県 47 行を散布図化(横軸:総人口、 縦軸:死亡数)

💬 読み方:人口が多い都道府県ほど死亡数も多く、 右上に東京・大阪など大都市が並ぶ強い正の相関が見える(高齢化率の違いで直線からのズレも生じる)。 ELT で そのまま全行ロードしている証拠で、 ETL のように事前集約していたらこの分布は見えない。 ELT の利点は「後から SQL で何度でも切り直せる」点にあり、 BI ダッシュボードの大半はこの段階で十分実用になる。

🎨 図2:人口のヒストグラム(ロード直後の生データ品質チェック)

このコードでやること:ELT の T 工程に入る前の ロード直後の生データに対して、 分布形状を確認する。 ヒストグラムは「ロードに失敗した行は無いか」「単位ミス(千人と人の混在)が無いか」の最初のスモークテストになる。

📥 入力データ(SSDSE-B-2026 の総人口列 A1101、 2023 年度・47 都道府県):

count 47 mean 2,645,808 std 2,797,551 min 537,000 (鳥取県) max 14,086,000 (東京都) (単位:人)
人口のヒストグラム
図2:人口のヒストグラム(ロード直後の品質チェック)

💬 読み方:右に長い裾を持つ強い右裾分布。 平均 265 万人に対し中央値はもっと小さく、 ロード自体は正常(明らかな異常値・単位ミスは無い)と判断できる。 ELT であれば、 この確認結果を基に T 工程で LOG10(population) 列を追加するだけで対数化が完結する。 ETL のように事前に物理列を作り直さなくて済むのが ELT の機動力だ。

🎨 図3:地域別人口の箱ひげ図(Transform 工程の集計結果)

このコードでやること:T 工程で SQL の CASE WHEN によって 47 都道府県を 4 地域(北海道・東日本・中日本・西日本)に再分類し、 地域ごとの人口分布を箱ひげ図で比較する。 ELT 上で「集計軸を変えたい」と言われた瞬間に SQL 一発で対応できる例。

📥 入力データ(2023 年度の総人口を 4 地域に再分類した概算、 SSDSE-B-2026 から派生):

region n median Q1 Q3 北海道 1 5,092 - - 東日本 17 1,420 900 2,400 中日本 13 1,890 870 3,500 西日本 16 1,310 710 2,500 (単位:千人)
地域別人口の箱ひげ図
図3:地域別人口の箱ひげ図(中日本の中央値が最大、 各地域に大都市外れ値)

💬 読み方:中日本(東京・名古屋圏)は中央値と上ひげが他地域を上回り、 西日本は大阪・福岡の外れ値が目立つ。 ELT であれば、 別途「人口密度ベースで地域を再定義したい」とリクエストが来ても、 SQL 上で CASE WHEN を書き換えるだけで即対応できる。 これが ETL(事前に物理テーブルを作るアプローチ)に対する最大の差別化点。

🔧 ELT パイプラインを 3 行で表現する(実装例)

現代の ELT は「ロードまでは生のまま、 Transform は SQL に任せる」哲学に立つ。 ここでは SSDSE-B-2026 を BigQuery 風の DWH にロードして、 SQL で集計・特徴量化する典型的なフローを示す。

このコードでやること:(1) pandas で CSV を読み(cp932・説明行を skiprows=[1] で除去)、 (2) DWH(ここでは SQLite で代用)に raw_b2026 としてそのままロードし、 (3) SQL の SELECT で「人口 100 万人以上の県の総人口・死亡数」を集計する。 ETL と違って Python 側で groupby せず、 SQL に投げているのがポイント。

📥 入力データ(SSDSE-B-2026.csv の 2023 年度・先頭 2 行、 実在列 A1101=総人口 / A4200=死亡数):

SSDSE-B-2026,Code,Prefecture,A1101,A4200,... 2023,R01000,北海道,5092000,75120,... 2023,R13000,東京都,14086000,137241,...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import sqlite3

# (1) Extract: 公的データを CSV で取得
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]            # 2023 年度だけ

# (2) Load: 生のまま DWH(SQLite で代用)に投入
con = sqlite3.connect('warehouse.db')
df.to_sql('raw_b2026', con, if_exists='replace', index=False)

# (3) Transform: SQL で「人口 100 万人以上の県の総人口・死亡数合計」を計算
sql = """
SELECT
  COUNT(*)   AS n_pref,
  SUM(A1101) AS total_pop,
  SUM(A4200) AS total_deaths
FROM raw_b2026
WHERE A1101 >= 1000000
"""
result = pd.read_sql(sql, con)
print(result)

📤 実行例(実際に出る出力):

n_pref total_pop total_deaths 0 37 116738000 1455035

💬 読み方:2023 年度で人口 100 万人以上の県は 37 件、 合計人口 約 1.17 億人、 死亡数の合計は約 145.5 万人。 同じ SQL の WHERE を書き換えるだけで「200 万人以上」「都市圏のみ」など何度でも切り直せる。 ETL ならその度に再加工コードを書き直す必要があるが、 ELT は DWH の生表に対して SQL を当てるだけで済む。

⚖️ ELT と ETL の比較(実務で迷ったときの判断軸)

どちらを採るべきかは、 (a) ストレージ料金、 (b) 集計軸の変更頻度、 (c) PII(個人情報)保護要件の 3 軸で判断する。 BigQuery や Snowflake のような列指向 DWH を使うのであれば、 ストレージは安く、 集計軸の変更頻度が高い分析業務では ELT が圧倒的に有利。

観点 ELT が有利 ETL が有利
ストレージ単価 GB 単価 < 0.03 USD/月(BigQuery 等) GB 単価 > 0.5 USD/月(高機能 OLAP)
集計軸の変更頻度 月数回〜毎日(BI 試行錯誤) 数ヶ月に 1 回(固定レポート)
PII マスキング要件 DWH 内で SQL 関数によるマスキングが可能 ロード前に物理的に削除する必要がある
監査ログ SQL クエリ履歴で再現可能 ETL ジョブ定義の世代管理が必要
小規模・組み込み用途 向かない(DWH の固定費が高い) 向く(pandas + cron で十分)

実務 Tip:SSDSE-B-2026 程度(数十 MB)であれば pandas + SQLite で ELT を模擬実行できる。 本番運用前に SQL を書き上げておけば、 そのまま BigQuery に移植して即運用に乗せられる。 ELT の真価は 「分析者が SQL だけで完結できる」点にある。

🧠 ELT を理解するための追加観点

ELT は単に「順番が違うだけ」ではなく、 分析の運用思想そのものを変える技術選択である。 ここでは ELT 採用の前後で実務がどう変わるか、 SSDSE-B-2026 を題材に 4 つの観点から踏み込む。

1. データ鮮度(Freshness)の改善

ETL の典型は「夜間バッチでまとめて変換し、 翌朝に集計済みテーブルを提供する」運用だった。 これに対し ELT は「到着次第ロードし、 SQL は分析者が叩く瞬間に実行する」モデル。 SSDSE-B-2026 を例にとると、 政府統計の公開直後にロードすれば、 数時間以内に最新の集計値を BI で確認できる。 ETL のように夜間バッチを待つ必要がない。

ETL モデル: データ公開 → 夜間ジョブ(22:00-6:00)→ 集計表 → 翌朝 BI 総遅延: 平均 18 時間 ELT モデル: データ公開 → 直ちにロード → 分析者が SQL 実行 → 即 BI 総遅延: 平均 30 分

💬 効果:データ鮮度が 36 倍向上。 政策発表直後の都道府県統計や、 災害時の人口移動データなど、 速報性が求められる場面で ELT は決定的な差を生む。

2. 失敗時のリカバリ容易さ

ETL では Transform が失敗するとパイプライン全体が止まり、 中間データも失われる。 ELT は生データを先にロードするため、 Transform で問題があっても SQL を書き直すだけで復旧できる。 SSDSE-B-2026 の例で言えば、 「消費支出(L3221)の単位を円から千円に直し忘れた」場合、 ETL なら全工程を再実行する必要があるが、 ELT なら / 1000 を SQL に追加するだけで済む。

失敗シナリオ ETL の対応 ELT の対応
単位ミス(百万円→億円) 全パイプライン再実行(数時間) SQL に /100 を追加(数秒)
集計軸の追加(地域→市町村) ETL ジョブ書き直し(半日) GROUP BY 市町村 追加(数秒)
欠損補完手法の変更 物理列を作り直す(数時間) VIEW の SQL を書き換え(数秒)
分析者の試行錯誤 エンジニアに依頼(数日) 分析者自身が SQL で実験(分単位)

3. データ系譜(Lineage)の追跡

ELT では生データから集計値までの全工程が SQL クエリ履歴として DWH に残るため、 「この数字はどこから来たのか」を完全に再現できる。 監査対応や論文の再現性確保に直結する重要な特性で、 SSDSE-B-2026 のような公的データを使う分析では特に重視される。

📥 系譜情報の自動取得例(BigQuery INFORMATION_SCHEMA より):

query_id | source_table | dest_table | sql_summary q_2026_001 | raw_b2026 | pref_summary | SELECT 都道府県, SUM(総人口)... q_2026_002 | pref_summary | region_summary | SELECT region, AVG(総人口)... q_2026_003 | region_summary | dashboard_view | CASE WHEN region='中日本'...

💬 効果:「dashboard_view の数字 → region_summary → pref_summary → raw_b2026 → SSDSE-B-2026.csv」の系譜が SQL 履歴だけで復元できる。 ETL では別途メタデータカタログが必要だったが、 ELT では DWH 標準機能で十分。

4. コスト構造の変化

ELT は「ストレージは安く、 計算は使った分だけ」モデルが前提。 SSDSE-B-2026(約 50 MB)を毎日ロードしても年間ストレージ料金は数百円。 計算は分析者が SQL を叩いた瞬間だけ発生し、 月数千円で収まる。 これに対し ETL は ETL サーバーの常時稼働コストが固定で発生するため、 利用頻度が低いと割高になる。

想定: 月 100 クエリ、 SSDSE-B-2026 規模(50 MB × 12 ヶ月分) ELT (BigQuery 想定): ストレージ: 600 MB × 0.02 USD/月 = 0.012 USD/月 計算: 1 GB × 100 クエリ × 5 USD/TB = 0.5 USD/月 合計: 約 0.5 USD/月 (≒ 70 円/月) ETL (専用 ETL サーバ): サーバ常時稼働: 約 50 USD/月 (≒ 7,500 円/月) → 月 100 クエリ未満では ELT が 100 倍以上安い

💬 結論:個人の研究や小規模ベンチャーは ELT で月数百円から始められる。 ETL の固定費が正当化されるのは「大量の定型レポートを毎日大量に出す」企業のみ。 教育・研究用途では ELT 一択と言ってよい。

🏭 実世界の ELT 事例とアーキテクチャ詳細

実際に運用されている ELT 基盤の構成要素を、 SSDSE-B-2026 を題材にしたミニマル構成と、 大企業の本格構成の 2 段階で対比する。 規模が変わっても 「先にロード、 後で SQL」の哲学は一貫している。

ミニマル構成(個人研究・教育用途)

SSDSE-B-2026 を扱う場合、 以下の 3 ツールだけで本格的な ELT を組める。 大学の演習や学会発表の準備にも十分な構成で、 月額コストはほぼゼロ。

ツール 役割 月額目安
Extract curl / requests SSDSE 公開 URL から CSV 取得 0 円
Load DuckDB / SQLite CSV を生のまま列指向で保持 0 円
Transform SQL (DuckDB) 分析時に SELECT で集計 0 円
BI matplotlib / Streamlit SQL 結果を可視化 0 円

💬 学習効果:この構成で SSDSE-B-2026 を 1 週間触れば、 「ELT とはこういう動きか」が体感できる。 後で BigQuery / Snowflake に移行しても、 SQL はほぼそのまま動く。

本格構成(企業の DWH 基盤)

数百 GB〜数十 TB を扱う場合は、 マネージド DWH と SaaS ELT ツールを組み合わせる。 dbt + Fivetran + Snowflake + Looker が現代的な標準構成。

代表ツール 特徴
Extract & Load Fivetran / Airbyte / Stitch 200+ ソース対応、 スキーマ自動追従
DWH BigQuery / Snowflake / Redshift 列指向、 ストレージと計算分離
Transform dbt (data build tool) SQL の Git 管理、 テスト、 ドキュメント自動生成
BI Looker / Tableau / Power BI セマンティックレイヤ、 ダッシュボード共有
監視 Monte Carlo / Datadog データ品質、 SLA、 異常検知

dbt による ELT の現代的な書き方

このコードでやること:dbt のモデル定義ファイルに SQL を書き、 dbt run で DWH に集計テーブルを生成する。 SSDSE-B-2026 の都道府県集計を、 dbt で再現する例。

📥 入力(dbt model: models/marts/pref_summary.sql):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- models/marts/pref_summary.sql
{{ config(materialized='table') }}

SELECT
  Prefecture,
  SUM(A1101) AS total_pop,
  SUM(A4200) AS total_deaths,
  AVG(A1101) AS avg_pop
FROM {{ ref('raw_b2026') }}
WHERE A1101 IS NOT NULL
GROUP BY Prefecture
ORDER BY total_pop DESC

📤 実行例(dbt run コマンドの出力):

$ dbt run --select pref_summary 12:34:56 Running with dbt=1.7.0 12:34:57 Found 1 model, 0 tests, 0 snapshots 12:34:58 1 of 1 START sql table model marts.pref_summary ........... [RUN] 12:35:01 1 of 1 OK created sql table model marts.pref_summary ...... [CREATE TABLE (47 rows) in 2.34s] 12:35:01 Completed successfully 12:35:01 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1

💬 読み方:dbt は SQL を「モデル」と呼ばれる Git 管理されたファイルとして扱う。 {{ ref('raw_b2026') }} で他モデルへの依存を宣言し、 dbt が自動で DAG(依存グラフ)を構築。 ELT の T 工程を コードレビュー可能・テスト可能な形にしたのが dbt の革新性。

⚠️ よくある落とし穴

この用語を使うときに初学者が踏みやすい失敗パターン。 1 度経験してしまえば次から避けられますが、 先に知っておくに越したことはありません。

❌ DWH コスト爆発
生データを保持+頻繁な再変換でクエリ料金が嵩む。 パーティション・クラスタリング設計が必須。
❌ 変換ロジックの散逸
SQL があちこちに散らばると改修困難。 dbt のような変換管理ツールで一元化。
❌ PII を生で保持
個人情報をそのまま入れると GDPR/個人情報保護法に抵触。 ロード前にマスキング。
❌ リネージ不明
どのテーブルがどこから来たか分からなくなる。 メタデータ管理(OpenLineage 等)。
🛡 防御策まとめ:「適用条件を確認する」「結果と前提をセットで記述する」「不確実性を必ず併記する」の 3 点を習慣化すれば、 上記の罠の大半は回避できます。

⚠️ ELT のコスト管理 — 「無限スケール」が「無限請求」になる罠

BigQuery/Snowflake は秒単位課金 + クエリスキャン量課金で「使った分だけ」だが、 SELECT * FROM huge_table を脳死で書くと月数十万円の請求が来る。 ELT を本番運用するならコスト管理が ETL より重要。

コスト要因兆候対策
フルスキャンパーティション/クラスタリング無視PARTITION BY (event_date) + WHERE で枝刈り
SELECT *列指向 DWH で全列スキャン必要列のみ明示、 view で列を絞る
毎日 full rebuilddbt run で全モデル再構築incremental materialization、 差分 merge
CROSS JOIN 事故中間テーブルが爆発EXPLAIN で行数見積、 WHERE/USING で結合キー明示
不要な ORDER BY最終出力以外で並び替えCTAS 中の ORDER BY は外す
放置された開発テーブルストレージコストが線形増TTL 設定、 dataset.expiration_days
並列度の上げすぎ秒単位課金のスポットで暴走同時実行数制限、 quota alert

💬 BigQuery では --dry_run でクエリスキャン量を事前確認可能。 dbt は --target dev で限定スキャン、 --target prod でフル実行、 のように環境を切替えてコスト分離するのが定石。 「クエリレベル課金 alert」(月予算超過時メール) を CFO 説得材料として必ず設定する。

⚠️ ELT 実装での落とし穴 ─ Deep FAQ

落とし穴兆候対処
raw 層に変換を混ぜる「Extract 時に型変換しちゃおう」raw は 絶対に無変換 ── 取り直し可能性を保つ
staging を表 (table) で作成ストレージ二重化staging は view、 mart は table が定石
タイムゾーン無視日次バッチで日付がずれるUTC 統一、 表示時のみ変換
スキーマ変更で全モデル redraw の列名変更で連鎖崩壊on_schema_change で対応、 source schema test
incremental の unique_key 未設定重複行が累積必ず unique_key を指定して MERGE 化
勘で書く dbt SQLテストなし、 ドキュメントなし不変式は 必ず dbt test に書く (not_null, unique, accepted_values)
PII を raw 層に放置GDPR/CCPA 違反リスクraw に入れる前に hash/mask、 政策で「PII 列を raw に入れない」
timezoned column 名衝突created_at が 8 種類あるstaging で _utc 接尾辞統一

🎯 ELT — 5 つの覚えどころ

  1. Extract → Load → Transform: 順序の入れ替えだけだが、 計算リソースが DWH 内に移ったことが本質的変化。
  2. 三層構造: raw (無変換) → staging (型整理) → mart (集約・派生)。 各層の役割を厳密に分ける。
  3. dbt が事実上の標準: SQL + Jinja + ref() による DAG 構築、 schema.yml で契約、 docs で自動ドキュメント。
  4. SCD と Incremental: 履歴管理と差分更新が運用効率の鍵。 dbt snapshot/incremental で対応。
  5. コスト管理が ETL より重要: パーティション・クラスタ・dry_run・予算アラートを必ず設定する。

⚠️ ELT 採用時の落とし穴と対処法(実務で頻発する 6 つ)

ELT は強力だが、 「順序を逆にすればよい」と短絡的に捉えるとハマる。 SSDSE-B-2026 を扱う実演習でもよく遭遇する落とし穴を、 対処法とセットで列挙する。

落とし穴1:生データに PII を含めたまま全社員アクセス可能にしてしまう

SSDSE-B-2026 のような公開統計では問題ないが、 自社の顧客データを ELT で扱う場合、 マスキングを怠ると重大事故になる。 対処:ロード時点で SECURE VIEW を作成し、 PII カラムは SHA256() でハッシュ化、 アクセス権を最小権限で分離する。 BigQuery では Column-level Security、 Snowflake では Dynamic Data Masking を活用。

落とし穴2:SQL クエリ料金が想定外に膨らむ

BigQuery のような従量課金型 DWH では、 毎回 SELECT * すると 1 クエリ数千円になることがある。 対処:必要な列だけ指定、 パーティション(日付分割)を活用、 マテリアライズドビューで頻繁な集計を事前計算。 SSDSE-B-2026 程度では問題にならないが、 数百 GB クラスでは設計の良し悪しで月額数十万円の差が出る。

落とし穴3:dbt モデル間の依存が複雑化し、 失敗時の影響範囲が読めない

モデル数が数百を超えると、 1 つの SQL の変更が下流の数十モデルを破壊することがある。 対処:dbt の --exclude--defer、 lineage graph 可視化を駆使し、 CI で全モデルテストを毎回実行する。 SSDSE-B-2026 規模なら 5 モデル程度で済むが、 本番運用では DAG の設計力が問われる。

落とし穴4:スキーマ変更(カラム追加・削除)の追従が遅れる

SSDSE-B が年度更新で列名や順序が変わると、 既存の SQL が即座に壊れる。 対処:Fivetran や Airbyte のスキーマ自動追従機能を有効化、 dbt の schema.yml でカラムをテスト、 重要列の rename は alias で吸収する。 「ロード層は変えず、 Transform 層で旧名にマッピング」が王道。

落とし穴5:DWH のロックインで他クラウドへ移行困難

BigQuery の独自関数(STRUCTARRAY_AGG)に依存すると Snowflake へ移れない。 対処:標準 SQL の範囲で書く、 dbt の adapter 機能で DWH 非依存にする、 Iceberg や Delta Lake のような open table format を採用してテーブル自体を可搬にする。 SSDSE-B-2026 を扱う教育用途では問題ないが、 企業では長期視点が必要。

落とし穴6:SQL 中心主義による「Python・R で書きたい処理」の困難

機械学習の特徴量生成や時系列の窓関数は SQL では書きにくいことがある。 対処:BigQuery ML、 Snowpark、 dbt-python model を活用し、 SQL では難しい処理だけ Python に任せる。 SSDSE-B-2026 で「都道府県の主成分分析をしたい」なら、 ELT で前処理を SQL、 PCA は sklearn.decomposition.PCA でやる、 という分業が現実的。

総括:ELT は 「SQL を中心に据える分析運用」であり、 SQL の表現力と分析者のスキルが成果を決める。 SSDSE-B-2026 で SQL を 100 本書く演習をこなせば、 これら 6 つの落とし穴は自然に回避できるようになる。 特に、 SSDSE-B-2026 の都道府県 47 行という適度な規模は、 SQL の GROUP BYWINDOWJOIN の練習にちょうど良い。 集計結果が手計算で検算できるため、 SQL の挙動を直感的に確かめながら学習を進められる。 これが大規模ログデータでの学習にはない、 公的統計データを使う独自の強みである。

また、 ELT の運用では「データの正しさ」を保証するテスト文化が重要になる。 dbt の tests 機能を活用すれば、 「total_pop は NULL でないこと」「都道府県は重複しないこと」「死亡数(A4200)は正の値であること」といった制約を SQL で記述・自動検証できる。 SSDSE-B-2026 の演習でこのテスト習慣を身につけておけば、 本番運用に移っても品質を担保し続けられる。 ELT は単なる技術トレンドではなく、 データ品質を継続的に検証する組織文化そのものを変える運動である、 と理解するのが正しい。

📜 ELT が ETL を逆転した歴史的背景(2015 年以降の変化)

1990 年代から 2010 年頃まで、 データウェアハウスの世界は ETL が支配していた。 ストレージが高価で、 CPU/RAM も限られたため、 「変換してから入れる」しか選択肢が無かったのだ。 これを覆したのは、 列指向 DWH の商用化と、 クラウド従量課金モデルの普及である。

時期 出来事 ELT への影響
1990 年代 Informatica、 DataStage 隆盛 ETL が事実上の標準。 ストレージ単価が高く、 生データ保持は非現実的
2010 Hadoop / Hive 普及 「生データを HDFS に貯めて後で MapReduce」モデルが ELT の原型に
2012 BigQuery 一般公開 列指向 + サーバーレスで「全データを SQL で叩く」が現実的に
2014 Snowflake 商用化 ストレージと計算の完全分離。 ELT の経済合理性が決定的に
2016 dbt OSS リリース SQL の Git 管理とテストが普及、 ELT が組織レベルで運用可能に
2020 以降 Fivetran / Airbyte 普及 E と L を SaaS が担い、 分析者は SQL だけに集中できる時代に

結論:ELT は技術的優位の積み重ねで ETL を逆転した。 SSDSE-B-2026 を含む現代のデータ分析は、 ほぼすべて ELT パラダイムの上で動いていると考えてよい。 学生のうちから SQL を中心に学ぶことが、 そのまま実務直結のスキルになる時代になった。

✅ 理解度チェック(ELT の本質を確認)

  1. Q1. ELT で「Load」を「Transform」より先に行う最大の理由はストレージ単価の低下である。 これは 。 列指向 DWH の登場で生データ保持のコストが激減したため、 後から SQL で何度でも切り直す方が合理的になった。 1990 年代のディスク単価は数千円/GB だったが、 2024 年のクラウドストレージは数銭/GB と 1 万倍以上安くなっており、 「とりあえず全部ロード」が現実的になった。
  2. Q2. ELT は ETL より集計軸の変更に 強い。 SQL を書き換えるだけで再集計できるためで、 ETL のように物理テーブル再構築は不要。 SSDSE-B-2026 で「都道府県 → 地域 → 全国」と粒度を変えた集計を比較したい場合、 ELT なら GROUP BY 句を差し替えるだけで完結する。
  3. Q3. PII(個人情報)を含む生データをそのまま DWH にロードする際は、 必ず 列レベルのマスキングアクセス権分離を SQL ロール定義で設定する。 これを怠ると ELT の利便性が即セキュリティ事故に直結する。 BigQuery の Column-level Security や Snowflake の Dynamic Data Masking など、 主要 DWH には標準機能が揃っている。
  4. Q4. SSDSE-B-2026 のような数十 MB スケールでは、 ELT を pandas + SQLite で模擬実行でき、 そのまま BigQuery に移植可能。 これが「先に SQL で書く」習慣を学ぶ良い練習材料となる。 個人 PC で書いた SQL がクラウドでも動くため、 演習から本番へのギャップが小さい。
  5. Q5. BI ツール(Looker、 Tableau、 Power BI)の多くは ELT 前提で設計されている。 ダッシュボードでフィルタを切り替えるたびに裏で SELECT 文が発行され、 これが ELT の T 工程に該当する。 ユーザー操作の速度が、 そのまま DWH の応答性能に直結する設計。
  6. Q6. dbt(data build tool)は ELT の T 工程を Git 管理 + テスト + ドキュメント自動生成の対象にした OSS。 SQL を「読み・書き・レビューできるコード」として扱う発想で、 dbt の登場以後 ELT は組織運用に堪える成熟段階に入った。
  7. Q7. ELT の Extract と Load を SaaS(Fivetran、 Airbyte)が担うようになったことで、 分析者は SQL を書くことだけに集中できる時代になった。 ETL 時代に必須だった「Python でパーサを書く」「シェルスクリプトでバッチを組む」スキルは、 SSDSE-B-2026 のような公的データ分析では不要になりつつある。 代わりに、 SQL の表現力ビジネス問題を SQL に翻訳する力が問われるようになった。
  8. Q8. ELT を学ぶ最短経路は、 DuckDB をローカルにインストールし、 SSDSE-B-2026 を直接 SELECT で叩いてみること。 DuckDB は CSV を SQL でそのまま読めるため、 「Load 不要、 即 Transform」の体験ができる。 30 分の演習で ELT の本質が掴める。
  9. Q9. ELT の文脈で「メダリオン・アーキテクチャ」(Bronze・Silver・Gold の 3 層)と呼ばれる設計パターンがある。 生データを Bronze(生)、 軽い変換後を Silver(標準化)、 BI 利用層を Gold(集計済み)として段階的に層化する手法で、 dbt のディレクトリ構成も staging / intermediate / marts でこれを反映している。 SSDSE-B-2026 を扱う際も、 raw → cleaned → pref_summary という 3 層化を意識すると、 SQL がメンテナンスしやすくなる。 この設計は Databricks が提唱したものだが、 現在は Snowflake や BigQuery でも標準的に採用されている設計思想となった。
  10. Q10. ELT の習得には「SQL を毎日 30 分書く」を 3 ヶ月続けることが最も効果的。 SSDSE-B-2026 のような公的統計データは無料で入手でき、 練習材料として最適。 一度 SQL で書けるようになれば、 BigQuery、 Snowflake、 PostgreSQL、 DuckDB、 SQLite と環境を変えても応用が効く。 これが「ELT 時代の基礎スキル = SQL」と言われる所以である。

🗺 概念マップ

ELT (Extract Load Transform) は「先にロードしてから変換」というパターンで、 ETL の変換タイミングを逆転させた現代的な手法。 上流の生データソース ・下流の DWH (Snowflake・BigQuery 等) と密結合し、 dbt が代表的な Transform 層のツールとして並列する。

ELT BigQuery (GCP) Snowflake ETL(対比) dbt (Transform) データレイク raw/staging/mart

ELT(Extract, Load, Transform)は、 まず生データを DWH/データレイクに格納してから変換する設計で、 クラウド DWH(BigQuery・Snowflake・Redshift)の処理能力を活用した現代的なデータパイプラインの主流。 ETL と比べて柔軟性・再現性で優れる反面、 ストレージコストとガバナンスの設計が重要になる。

🔗 隣接手法への橋渡し

ELT (Extract Load Transform) は単独のパターンではなく、 ETL の変換タイミングを逆転させた現代的データ統合手法である。 Snowflake ・BigQuery ・dbt と組み合わせて DWH 上で SQL ベース変換を実行する。

ELT (Extract Load Transform) は「先にロードしてから変換する」現代的データ統合パターンで、 上流の生データ取り込みを軽量化し、 並列の ETL と「変換タイミング」で対比し、 下流の DWH 上で SQL/dbt による変換を回す構成を取る。

🌳 手法選択フロー

ETL か ELT かは「ターゲット DWH の計算能力・変換ロジックの複雑さ・ガバナンス要件」の 3 軸で決まる。 Snowflake/BigQuery 級なら ELT、 オンプレ DB や厳格管理下では ETL を選ぶ。

  1. 変換をどこで行うか
    ELT は生データをそのまま格納してから、 倉庫側の計算資源で変換する。 変換先の DB が十分速いなら ELT、 取り込み前に軽くしたいなら ETL。
  2. 生データを残す必要があるか
    残すなら ELT。 定義を後から変えても、 生データから作り直せる。 変換してから捨てる ETL では、 定義変更のたびに再取得が要る。
  3. 変換の記述は SQL で足りるか
    集計・結合・型変換なら SQL で書ける。 画像処理や外部 API 呼び出しが要るなら、 その部分は取り込み前に処理する。
  4. 費用はどちらに寄せるか
    ELT は倉庫側の計算課金が増える。 同じ変換を毎回走らせるなら、 結果を保存して再利用する設計にする。

SSDSE-B-2026 のような既に整形された公開データを社内基盤に取り込むなら ELT が向き、 まず生 CSV を S3 や DWH にそのまま置き、 後段で都道府県コード・年次の整合性を SQL で確認する流れが効率的である。

🎮 触って理解する — 「リプレイ容易性」を体感するELTシミュレータ

ELT 最大の実務的メリットは、 生データが DWH の staging 層に残っているため、 変換ロジックにバグが見つかっても「SQL 変換だけを再実行 (リプレイ)」すれば mart が直ること。 ETL 方式では変換後のデータしか残らないので、 ソースからの再抽出が必要になりコストが跳ね上がる。 下のシミュレータで両方式のリプレイコストを比べてみよう。 (ETL/ELT の順序比較そのものは ETL のページ、 DWH・データレイクの位置づけは DWHデータレイク を参照。)

シナリオ: SSDSE-B-2026 の 2023 年度実測値 (総人口 A1101・出生数 A4101) が staging 層にロード済み。 mart 層では「出生率 = 出生数 ÷ 総人口 × 係数」を SQL で計算する。 正しい係数は ×1000 (人口千人あたり) だが、 ある日誰かが係数を間違えてデプロイしてしまった…!

アーキテクチャ:
◀ ここを左右にドラッグ (タッチ対応) しても係数を調整できます ▶
まず「⚠️ 変換ルールにバグ発生!」を押して、 mart が壊れる様子を見てみよう。 その後スライダーを ×1000 に直して「🔄 再実行」。 ETL / ELT 両方式で試すとコスト差が分かる。
📦 Source 業務DB / CSV 🔧 ETLサーバ Transform (中間処理) 🏛 DWH (BigQuery / Snowflake) staging 層 生データ保持 (47都道府県) 総人口・出生数そのまま SQL変換 ×1000 mart 層 (出生率) 北海道 4.80 東京都 6.13 広島県 6.09 沖縄県 8.55 ✅ 正しい (人口千人あたり)
今回のリプレイコスト
0
ELT: Transform 10 のみ
🅰 ETL方式 累計コスト
0
1回のリプレイ = 80 (E40+T10+L30)
🅱 ELT方式 累計コスト
0
1回のリプレイ = 10 (T10のみ)
都道府県staging: 総人口 (人)staging: 出生数 (人)mart 現在値スライダー適用プレビュー正解 (×1000)
北海道5,092,00024,4304.804.804.80
東京都14,086,00086,3486.136.136.13
広島県2,738,00016,6826.096.096.09
沖縄県1,468,00012,5498.558.558.55

数値は SSDSE-B-2026 (cp932 / skiprows=[1]) の 2023 年度実測値 (A1101 総人口・A4101 出生数)。 コスト単位は「本シミュレータ内の仮想コスト」であり架空の相対値 (Extract 40・Transform 10・Load 30) です。

💡 直感 — なぜ「先に貯める」と安心なのか

ELT は「原本 (生データ) を DWH に置いたまま、 変換はいつでも SQL でやり直せる」構造。 上のシミュレータの通り、 バグ修正のリプレイは staging → mart の短い経路で済む (仮想コスト 10)。 一方 ETL 方式は変換後のデータしか残らないため、 ソースへの再アクセス = Extract からの長い経路 (仮想コスト 80、 8 倍) が必要になる。 しかもソース側の業務 DB は過去データを保持していない・夜間しか叩けない等の制約があることも多く、 実務では「8 倍」どころか再現不可能な場合すらある。 「原本さえあれば何度でもやり直せる」— これが ELT の安心感の正体で、 写真の RAW ファイルを残しておけば現像 (レタッチ) を何度でもやり直せるのと同じ理屈。

⚠️ よくある落とし穴 — リプレイ容易性の代償

🚀 発展 — リプレイを支える 3 つの概念

🧮 SSDSE-B-2026 で ELT を体験 — DuckDB を DWH 代替に

BigQuery/Snowflake のような本格 DWH の代わりに、 ローカルで動く DuckDB (列指向・解析特化の埋め込み DB) で ELT パターンを再現する。 これにより「クラウドアカウントなしで ELT 全工程をハンズオン体験」できる。

🎯 このコードでやること: SSDSE-B-2026 を Extract → Load (raw schema) → Transform (staging → mart) という ELT 三層に分け、 DuckDB の SQL で各層を構築する。 最終 mart で「都道府県別・年度別の出生率パネル」を作る。

📥 入力データ:

SSDSE-B-2026.csv (cp932 / skiprows=[1] / 564 行 / 112 列 / 47 都道府県 × 12 年度) 列: A1101 (総人口), A1301 (15歳未満), A1303 (65+), A4101 (出生数), J2503 (保育所等数) ほか
 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
import duckdb, pandas as pd

con = duckdb.connect('dwh.duckdb')             # 永続化 DWH ファイル

# === ① EXTRACT + LOAD (生データを raw スキーマに無変換投入) ===
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
con.execute('CREATE SCHEMA IF NOT EXISTS raw')
con.execute('CREATE OR REPLACE TABLE raw.ssdse_b AS SELECT * FROM df')
print('raw 投入:', con.execute('SELECT count(*) FROM raw.ssdse_b').fetchone())

# === ② TRANSFORM ─ staging 層 (型修正 + 列名整理) ===
con.execute("""
CREATE SCHEMA IF NOT EXISTS staging;
CREATE OR REPLACE TABLE staging.prefecture_year AS
SELECT
  "SSDSE-B-2026"        AS year,
  Code                  AS pref_code,
  Prefecture            AS pref_name,
  A1101                 AS total_pop,
  A1301                 AS pop_under15,
  A1303                 AS pop_over65,
  A4101                 AS births,
  J2503                 AS nurseries
FROM raw.ssdse_b;
""")

# === ③ TRANSFORM ─ mart 層 (派生指標を集約) ===
con.execute("""
CREATE SCHEMA IF NOT EXISTS mart;
CREATE OR REPLACE TABLE mart.fact_birth_rate AS
SELECT year, pref_code, pref_name, total_pop, births,
       births::DOUBLE / total_pop * 1000  AS birth_rate_per_1k,
       pop_over65::DOUBLE / total_pop       AS aging_rate,
       nurseries::DOUBLE / total_pop * 100000 AS nursery_per_100k
FROM staging.prefecture_year
WHERE total_pop IS NOT NULL;
""")

print(con.execute("SELECT * FROM mart.fact_birth_rate WHERE year=2023 ORDER BY birth_rate_per_1k DESC LIMIT 5").df())

📤 実行結果:

raw 投入: (564,) mart.fact_birth_rate (2023 年, top 5): year pref_code pref_name total_pop births birth_rate_per_1k aging_rate nursery_per_100k 0 2023 R47000 沖縄県 1468000 12549 8.548 0.2384 33.17 1 2023 R40000 福岡県 5103000 33942 6.651 0.2845 19.36 2 2023 R25000 滋賀県 1407000 9249 6.574 0.2701 15.35 3 2023 R43000 熊本県 1709000 11189 6.547 0.3230 28.55 4 2023 R23000 愛知県 7477000 48402 6.473 0.2572 17.96 【ELT の三層が完成】 raw.ssdse_b (生データ無変換、 564 行 × 112 列) staging.prefecture_year (型・列名整理、 564 行 × 8 列) mart.fact_birth_rate (派生指標、 564 行 × 8 列、 即分析可能)

💬 読み方: ELT 三層構造のメリットは、 (1) 生データを保持するので「あとから別の集計」が SQL 一つで可能、 (2) 各層が SQL なので分析者にも理解可、 (3) DuckDB/Snowflake/BigQuery で同じ SQL がほぼ動く (移植性)。 沖縄の出生率 8.55 が首位、 福岡 6.65 が 2 位。 東京・愛知など大都市は人口効果で出生数(births)は多いが、 1000 人あたり出生率では沖縄・福岡に劣る ── ELT で派生指標を持つと一目瞭然。

🐍 dbt — ELT の「Transform」を SQL + Jinja で構造化する

dbt (data build tool) は ELT の T 層を専用フレームワークで管理する OSS。 SQL ファイル + Jinja テンプレート + YAML 設定で「モデル」を組み立て、 依存関係 (DAG) を自動解決して順次実行する。 ELT が広まったきっかけの一つ。

🎯 このコードでやること: dbt プロジェクトの典型構造を示し、 SSDSE-B-2026 を ELT 化する 3 つの SQL モデル (raw → staging → mart) を Jinja 付き SQL で書く。

📥 入力データ:

dbt プロジェクト構造: my_dwh/ ├── dbt_project.yml ← プロジェクト設定 ├── profiles.yml ← 接続情報 (DuckDB/Snowflake/BigQuery) └── models/ ├── staging/ │ ├── _sources.yml ← raw.ssdse_b の宣言 │ └── stg_prefecture_year.sql └── mart/ └── fact_birth_rate.sql raw.ssdse_b は ELT 前段 (Fivetran/Airbyte 等) ですでにロード済みと想定
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# models/staging/_sources.yml ─ raw を「source」として宣言
## version: 2
## sources:
##   - name: raw
##     tables:
##       - name: ssdse_b

# models/staging/stg_prefecture_year.sql
## {{ config(materialized='view') }}
## SELECT
##   "SSDSE-B-2026" AS year,
##   Code           AS pref_code,
##   Prefecture     AS pref_name,
##   A1101 AS total_pop, A1303 AS pop_over65, A4101 AS births, J2503 AS nurseries
## FROM {{ source('raw', 'ssdse_b') }}

# models/mart/fact_birth_rate.sql
## {{ config(materialized='table') }}
## SELECT *,
##   births::DOUBLE / total_pop * 1000 AS birth_rate_per_1k
## FROM {{ ref('stg_prefecture_year') }}

# 実行 (CLI)
# $ dbt run --select fact_birth_rate
# $ dbt test                # データ品質テスト (not_null, unique, accepted_values)
# $ dbt docs serve          # 自動ドキュメント + DAG ビジュアライザ

📤 dbt run の出力例:

09:35:21 Running with dbt=1.7.0 09:35:22 Found 2 models, 1 source 09:35:24 1 of 2 START sql view model staging.stg_prefecture_year ... [RUN] 09:35:24 1 of 2 OK created sql view model staging.stg_prefecture_year [OK in 0.18s] 09:35:24 2 of 2 START sql table model mart.fact_birth_rate ........... [RUN] 09:35:25 2 of 2 OK created sql table model mart.fact_birth_rate ...... [OK in 0.42s] 09:35:25 Done. PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2 dbt test: unique_stg_prefecture_year_pref_code_year .... [PASS] not_null_fact_birth_rate_birth_rate_per_1k ... [PASS] accepted_values_year_2012_2023 ............... [PASS]

💬 読み方: dbt の強みは (1) ref() 関数で依存関係を宣言 → DAG を自動構築、 (2) テストが YAML で書ける → 「pref_code は unique」のような契約を CI で検証、 (3) 自動ドキュメント生成 → モデル間のリネージ可視化、 (4) materialization 戦略 (view/table/incremental/ephemeral) を YAML で切替。 これにより「SQL がソフトウェアエンジニアリングできる」ようになった。

🐍 データ品質テスト — Great Expectations と dbt test

ELT の生命線はテスト。 「raw 投入時にスキーマが変わった」「null が突然増えた」「期間外の値が混入」を CI 段階で捕捉できないと本番障害になる。 GE (Great Expectations) と dbt test の二大ツール。

🎯 このコードでやること: dbt のテスト構文 (schema.yml) と、 Python から Great Expectations で SSDSE-B-2026 にテストを書く 2 通りを示す。

📥 入力データ:

mart.fact_birth_rate (564 行 × 8 列) 契約: (year, pref_code) は一意、 birth_rate_per_1k は 0 以上、 aging_rate は 0〜1, year は 2012〜2023
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# === ① dbt 流 (models/mart/schema.yml に契約を書く) ===
## version: 2
## models:
##   - name: fact_birth_rate
##     tests:
##       - dbt_utils.unique_combination_of_columns:
##           combination_of_columns: [year, pref_code]
##     columns:
##       - name: birth_rate_per_1k
##         tests: [not_null, {dbt_utils.expression_is_true: "birth_rate_per_1k >= 0"}]
##       - name: year
##         tests: [{accepted_values: {values: [2012,2013,2014,...,2023]}}]

# === ② Great Expectations 流 (Python から検証) ===
import great_expectations as ge
df = con.execute('SELECT * FROM mart.fact_birth_rate').df()
ged = ge.from_pandas(df)
print(ged.expect_column_values_to_not_be_null('birth_rate_per_1k')['success'])
print(ged.expect_column_values_to_be_between('aging_rate', 0.0, 1.0)['success'])
print(ged.expect_compound_columns_to_be_unique(['year', 'pref_code'])['success'])
print(ged.expect_column_values_to_be_in_set('year', list(range(2012,2024)))['success'])

📤 実行結果:

True ← birth_rate_per_1k に NULL なし True ← aging_rate は 0〜1 に収まる True ← (year, pref_code) のペアが一意 (564 行すべて) True ← year は 2012〜2023 内

💬 読み方: 4 つの契約がすべて成立。 ELT pipeline の CI に組み込めば「データ品質崩壊」を本番投入前に検出可能。 GE は HTML レポートを自動生成して関係者に共有できる点も強み。 dbt test は SQL ベースで CI/CD と相性抜群、 GE は Python ベースで複雑な期待値も書ける ── ハイブリッド運用が現代の定石。

🐍 Incremental Models — 差分更新で月数千ドル節約

fact テーブルが 100 億行になると毎回 full rebuild は非現実的。 dbt の materialized='incremental' で「前回以降の差分だけ」追加できる。 BigQuery では MERGE 文に翻訳され、 イベント時刻ベースで効率的に動く。

🎯 このコードでやること: SSDSE-B-2026 では年度=パーティションキー。 incremental モデルで「最後にロードされた年度より後」だけ追加する dbt SQL を書く。

📥 入力データ:

既存テーブル: mart.fact_birth_rate (2012〜2023 年データが既にあり) 新規追加: 2024 年データが raw に投入された (新スナップショット)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# models/mart/fact_birth_rate.sql (incremental 版)
## {{ config(materialized='incremental',
##           unique_key=['year', 'pref_code'],
##           on_schema_change='sync_all_columns') }}

## SELECT
##   year, pref_code, pref_name, total_pop, births,
##   births::DOUBLE / total_pop * 1000 AS birth_rate_per_1k,
##   pop_over65::DOUBLE / total_pop      AS aging_rate
## FROM {{ ref('stg_prefecture_year') }}

## {% if is_incremental() %}
##   WHERE year > (SELECT MAX(year) FROM {{ this }})  ← 既存より新しい年のみ
## {% endif %}

# 実行: dbt run --select fact_birth_rate

📤 実行結果:

初回 (full rebuild): 564 行 (2012〜2023 × 47 県) ロード、 スキャン 12 MB 2 回目 (incremental): 47 行 (2024 のみ) ロード、 スキャン 1 MB 【コスト削減】 full rebuild: 月 30 回 × 12 MB = 360 MB スキャン incremental: 月 30 回 × 1 MB = 30 MB スキャン → 92% 削減 【BigQuery 換算 ($5/TB)】 仮に元データが 10 TB 規模なら: 月 $1500 → $120 に削減

💬 読み方: 差分更新の鍵は {% if is_incremental() %} Jinja ブロックで「既存 vs 新規」のロジックを切り替えること。 unique_key 指定で重複は MERGE 文で自動マージ。 1 億行のテーブルでは初回構築に 30 分かかっても、 毎日の差分は数秒で終わるようになり、 ETL と比較した ELT 最大のコスト効率源となる。

🐍 Airflow + dbt で ELT パイプラインをオーケストレーション

ELT は (1) Extract+Load (Fivetran/Airbyte), (2) dbt Transform, (3) BI 通知/ML 学習 の連携が必要。 これを束ねるのがオーケストレーター。 Airflow が最古参で標準的。 近年は Dagster / Prefect も台頭。

🎯 このコードでやること: Airflow の DAG として「①CSV ダウンロード → ②DuckDB に Load → ③dbt run → ④品質テスト → ⑤Slack 通知」の一連を記述する。

📥 入力データ:

data/raw/SSDSE-B-2026.csv ← 毎日同じ URL から再ダウンロード想定 DuckDB ファイル: dwh.duckdb dbt プロジェクト: ./my_dwh/
 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
from airflow import DAG
from airflow.operators.bash import BashOperator
from airflow.operators.python import PythonOperator
from datetime import datetime
import duckdb, pandas as pd

def load_to_raw():
    df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
    con = duckdb.connect('dwh.duckdb')
    con.execute('CREATE OR REPLACE TABLE raw.ssdse_b AS SELECT * FROM df')

with DAG('elt_birth_rate',
         start_date=datetime(2026, 5, 24),
         schedule_interval='0 3 * * *',      # 毎朝 3:00 実行
         catchup=False) as dag:

    extract = BashOperator(task_id='extract',
                bash_command='curl -o data/raw/SSDSE-B-2026.csv https://example.com/data.csv')
    load    = PythonOperator(task_id='load_to_raw', python_callable=load_to_raw)
    transform = BashOperator(task_id='dbt_run',
                bash_command='cd my_dwh && dbt run --select fact_birth_rate')
    test    = BashOperator(task_id='dbt_test',
                bash_command='cd my_dwh && dbt test --select fact_birth_rate')
    notify  = BashOperator(task_id='slack_notify',
                bash_command='curl -X POST -d "text=ELT 完了" $SLACK_WEBHOOK')

    extract >> load >> transform >> test >> notify      # DAG の依存定義

📤 Airflow UI の DAG 表示:

[extract] → [load_to_raw] → [dbt_run] → [dbt_test] → [slack_notify] 毎朝 3:00 自動実行 所要時間: 約 2 分 (extract 30s, load 5s, dbt run 45s, dbt test 30s, notify 1s) 失敗時: 自動リトライ 3 回、 失敗時 PagerDuty にアラート

💬 読み方: Airflow の強みは (1) 失敗時の自動リトライ + アラート、 (2) backfill (過去日付の再実行) サポート、 (3) DAG ビジュアライザで依存関係を一覧、 (4) Web UI でログ確認。 dbt と組み合わせると「データ取り込み → 変換 → 検査 → 通知」を 1 つの DAG で表現でき、 失敗箇所の特定も容易。 dbt Cloud の組み込みオーケストレーターを使えば Airflow なしでも完結する。

🐍 CDC (Change Data Capture) — リアルタイム ELT の基礎

夜次バッチ ELT では物足りないリアルタイム需要に応えるのが CDC。 OLTP DB (Postgres/MySQL) の WAL/binlog を tail し、 変更イベントを Kafka 経由で DWH に流す。 Debezium が代表的 OSS。

🎯 このコードでやること: Debezium で Postgres → Kafka → BigQuery の CDC パイプラインを構築する設定例。 SQLAlchemy で行を更新 → 数秒以内に DWH に反映される様子を示す。

📥 構成:

Postgres (source) └─ Debezium connector (logical replication slot) └─ Kafka topic: pgcdc.public.prefecture_population └─ BigQuery Sink connector └─ DWH 表: raw.cdc_prefecture_population (insert/update/delete イベント)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Debezium connector 設定 (JSON, POST to Kafka Connect)
## {
##   "name": "ssdse-pg-connector",
##   "config": {
##     "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
##     "database.hostname": "postgres",
##     "database.dbname": "ssdse",
##     "schema.include.list": "public",
##     "table.include.list": "public.prefecture_population",
##     "plugin.name": "pgoutput",
##     "publication.autocreate.mode": "filtered",
##     "snapshot.mode": "initial"
##   }
## }

# テスト: Postgres 側で 1 行更新すると数秒で DWH に流れる
# UPDATE public.prefecture_population SET births=90000 WHERE pref_code='R13000' AND year=2023;
# → DWH raw.cdc_prefecture_population に op='u', before={...}, after={...} の行が追加

📤 CDC イベント例 (Kafka メッセージ):

{ "op": "u", ← update operation "ts_ms": 1716536400000, ← 変更時刻 (epoch ms) "source": {"db": "ssdse", "table": "prefecture_population", "lsn": 36912}, "before": {"pref_code": "R13000", "year": 2023, "births": 89669}, "after": {"pref_code": "R13000", "year": 2023, "births": 90000} }

💬 読み方: CDC は「ELT のリアルタイム版」── バッチ ELT が 1 日 1 回更新だったのが秒〜分単位の更新になる。 ただし複雑度は跳ね上がる: (1) スキーマ進化、 (2) 順序保証、 (3) exactly-once delivery、 (4) 削除イベントの mart 反映。 リアルタイム要件 (ダッシュボードを 1 時間以内反映、 在庫管理を秒単位反映) がある場合のみ導入し、 そうでなければ夜次バッチで十分。

🧭 直感をもう一段深める — なぜ「順序を逆にする」だけで世界が変わるのか

冒頭の「素材のまま冷蔵庫へ、 食べる時に料理」という比喩を、 もう一歩だけ踏み込んで解剖する。 ELT の本質は Extract → Load → Transform、 つまり生データを一切加工せず先に DWH(データウェアハウス)へ流し込み、 変換は後から DWH の中で行うという順序の逆転にある。 ETL では「Transform(料理)→ Load(冷蔵庫)」の順だったので、 冷蔵庫に入るのは調理済みの一品だけだった。 味付けを間違えたら素材はもう手元にない。

  • 順序逆転の一点突破:ETL = 変換してから積む/ ELT = 積んでから変換する。 違いはこれだけだが、 「生データが DWH に残り続ける」という帰結が全ての利点(再現性・柔軟性・鮮度)と全ての欠点(コスト・ガバナンス)を生む。
  • なぜ今この順序が可能になったか:昔の DWH は「置き場」であって「計算機」ではなかった。 だから重い変換は外部の ETL サーバに任せるしかなかった。 BigQuery・Snowflake などクラウド DWH が安価で事実上無限にスケールする計算力を持った瞬間、 「データを動かして加工する」より「データを動かさず DWH の中で加工する」方が速く・安くなった。 これが ELT が主役に躍り出た唯一にして最大の理由。
  • 「先にロード」が生む安心感:要件は必ず後から変わる。 生データさえ DWH に残っていれば、 「やっぱり市町村単位で集計したい」「単位を千円に直したい」に対して SQL を書き直して再実行するだけで応えられる。 ETL では元の素材(生データ)を捨てているので、 抽出からやり直しになる。
💡 一言で:ELT は「とりあえず生で全部残しておき、 意味づけ(変換)は必要になった瞬間に、 DWH の計算力で何度でもやり直す」思想。 順序を逆にしただけで、 データ基盤が「作り込んだ一品料理」から「素材を蓄えた冷蔵庫」へと役割を変える。 詳しい対比は ETL、 蓄積先の設計は DWH を参照。

⚠️ 落とし穴をもう一段深める — 「生で全部残す」自由の代償

ELT の「とりあえず生で全部ロード」という自由は、 裏を返せば「何でも入る=何が入っているか誰も把握していない」状態を招きやすい。 上の「⚠️ よくある落とし穴」を補完する形で、 ガバナンス・再現性・権限という運用の生命線に絞って深掘りする。

落とし穴何が起きるか具体的な防御策
データスワンプ化
(沼化)
生データを無秩序に投げ込み続けた結果、 命名・説明・所有者が不明なテーブルが数千個に膨張。 「データレイク」が「データ沼(swamp)」に成り下がり、 誰も何を使ってよいか分からなくなる。投入時にメタデータ(source, owner, updated_at, PII 有無)を必須化。 Data Catalog(DataHub/Atlan)で発見可能性を担保。 raw 層にも命名規約と TTL を設ける。
変換ロジックの散在「SQL を書き直すだけ」の手軽さが仇となり、 同じ集計ロジックが BI ツール・アドホック SQL・複数の dbt モデルに重複コピーされる。 定義が食い違い「売上の数字が画面ごとに違う」事故に。変換は dbt など単一フレームワークに集約し、 ref() で依存を宣言。 共通指標は 1 つの mart モデルに定義(Single Source of Truth)。
スキーマオンリード
の副作用
ELT は「読む時に構造を解釈する(schema-on-read)」ので、 ロード時点では型崩れ・列追加・欠損が検知されない。 数か月後に下流クエリが静かに壊れる。raw→staging の境界で明示的に型キャスト+not_null/unique テスト。 schema drift をロード時に検知する仕組み(Great Expectations 等)を挟む。
変換の再現性喪失「今の SQL」で mart は作れても、 3 か月前の数字を再現できない。 変換ロジックがバージョン管理外にあると監査・検証が不能に。変換 SQL を Git 管理(dbt はモデルがそのままコード)。 マスタ履歴は SCD Type 2 で保持。 生データ(raw)は不変(immutable)とし上書き禁止。
計算課金の暴走従量課金 DWH で SELECT * やフルスキャンを放置すると、 想定の数十〜数百倍の請求。 「無限スケール」は「無限請求」と表裏一体。パーティション+クラスタリング、 列指定、 maximum_bytes_billed、 予算アラート。 本ページ「🐍 ELT のコストモデル」の試算も参照。
権限管理の甘さ生データが raw 層に平文で残るため、 PII(個人情報)に全社員がアクセスできてしまう構成事故が起きやすい。ロード前に hash/mask、 列レベルアクセス制御(BigQuery Column-level/Snowflake Dynamic Data Masking)、 監査ログ常時記録。

💬 6 つに共通する教訓は「ELT の手軽さは、 ガバナンスを設計に組み込んで初めて安全になる」という点。 ETL 時代は「変換パイプラインを通らないとデータが入らない」という構造自体が門番だった。 ELT では門番が消える代わりに、 メタデータ・テスト・アクセス制御・バージョン管理を能動的に敷かないと、 半年後にデータ沼と青天井の請求書が待っている。 蓄積先の設計思想は データレイク、 基盤全体の役割分担は データエンジニアリング を参照。

🚀 発展 — ELT を取り巻くモダンデータスタックの地図

ELT は単独の技術ではなく、 モダンデータスタック(Modern Data Stack)という一連の道具立ての中心に座る。 ここでは、 実務で次に学ぶべき発展トピックを、 関連ページへの導線とともに地図化する。

① ELT と ETL の使い分け(どちらか、 ではなく両方)

ELT は ETL を駆逐したのではなく「使い分けるもの」になった。 クラウド主導のアナリティクスや集計軸が頻繁に変わる分析業務は ELT、 レガシー DB 連携・複雑な業務ルール・ロード前に必ず PII を落とす必要がある要件は ETL が向く。 小規模(数十 MB)や組み込み用途では DWH の固定費が重いため、 pandas + cron のミニ ETL で十分なこともある。 詳細な対比表は本ページ上部「📐 ELT と ETL の決定的な違い」と ETLETL ツール を参照。

② モダンデータスタックと dbt(変換の民主化)

典型構成は Fivetran/Airbyte(Extract+Load)→ dbt(Transform)→ Snowflake/BigQuery(DWH)。 中核の dbt は SQL に「モデル間依存(ref())・テスト・ドキュメント・リネージ」を与え、 変換をソフトウェア開発のように扱えるようにした。 これにより、 SQL さえ書ければ分析者が変換パイプラインを保守できる(変換の民主化)。 dbt は本サイトに専用ページが無いため用語のみ記載。

③ データレイク/レイクハウスとスキーマオンリード

ELT の raw 層は データレイク とほぼ同義で、 あらゆる形式の生データを安価に蓄える。 近年は「レイク(安さ・柔軟さ)」と「DWH(信頼性・トランザクション)」を統合したレイクハウス(Iceberg/Delta/Hudi などオープンテーブルフォーマット)が台頭し、 DWH とレイクの境界が再溶解している。 いずれもスキーマオンリード(構造を書き込み時ではなく読み出し時に解釈)が前提で、 これが柔軟性の源泉でもあり、 前節の落とし穴(型崩れの遅延検知)の源泉でもある。

④ 変換のバージョン管理とメダリオンアーキテクチャ

変換ロジックを Git 管理し(dbt モデル=コード)、 PR レビュー・CI テスト・環境分離(dev/prod)を回すのが現代の作法。 データの階層化は メダリオンアーキテクチャBronze(raw/生)→ Silver(staging/整形)→ Gold(mart/集計) の三層が標準(Databricks 由来の呼称で、 ELT の raw/staging/mart と同じ)。 各層を進むごとにデータの品質と信頼性が上がる。

⑤ 増分処理(Incremental)でコストと時間を削る

毎回すべてを作り直す(full refresh)と DWH 課金が線形に膨らむ。 増分処理は「前回以降に増えた・変わった行だけ」を処理する。 updated_at や高水位マーク(high-water mark)を基準に差分を MERGE する。 dbt では materialized='incremental' で宣言的に実現でき、 本ページ「年度パーティション ELT」の枝刈りと組み合わせると効果が最大化する。

🗺 発展の学習順ETL との対比で全体像 → DWH で蓄積先の仕組み → データレイク で raw 層の思想 → データエンジニアリング で基盤運用、 の順に辿ると迷いにくい。 dbt・レイクハウス・データパイプライン(本サイトに専用ページ無し)は用語として押さえ、 手を動かして深めるのが近道。

🧮 実データで体感 — 「先にロード → DWH 内で集計」を SSDSE-B-2026 で実演

ELT の核心「生データを先にロードし、 変換(集計)は後から DWH の中で」を、 実在の公的統計 SSDSE-B-2026 で最小構成で追体験する。 ここでは DWH の代わりに SQLite を使い、 2023 年度の全 47 都道府県を無変換でロード → SQL の集計だけで全国合計を算出する。 Python 側で groupby せず、 集計を SQL(=DWH の役割)に委ねているのが ELT の型。

📥 入力データ(SSDSE-B-2026.csv、 cp932、 skiprows=[1]、 2023 年度 47 行。 実在列 A4101=出生数 / A4200=死亡数 / A1101=総人口):

SSDSE-B-2026,Code,Prefecture,A1101,A4101,A4200,... 2023,R01000,北海道,5092000,24430,75120,... 2023,R13000,東京都,14086000,86348,137241,... 2023,R27000,大阪府,8763000,55292,104964,...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
import sqlite3

# (1) Extract: 公的統計を CSV で取得(無変換)
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]          # 2023 年度だけ

# (2) Load: 生のまま DWH(SQLite で代用)へ raw 層として投入
con = sqlite3.connect('warehouse.db')
df.to_sql('raw_b2026', con, if_exists='replace', index=False)

# (3) Transform: 全国の出生数・死亡数・自然増減を SQL で集計
sql = """
SELECT
  COUNT(*)              AS n_pref,
  SUM(A4101)            AS births,
  SUM(A4200)            AS deaths,
  SUM(A4101) - SUM(A4200) AS natural_change
FROM raw_b2026
"""
print(pd.read_sql(sql, con))

📤 実行結果(実際に出る出力):

n_pref births deaths natural_change 0 47 727269 1575084 -847815

💬 読み方:2023 年度の全国合計は 出生数 727,269 人・死亡数 1,575,084 人で、 自然増減は約 −84.8 万人(出生が死亡を大きく下回る自然減)。 ここで重要なのは、 生データ(raw_b2026)をそのまま残したまま、 集計は SQL 一発で得ている点。 「上位県だけ」「県別に」「200 万人以上だけ」など切り口を変えたくなっても、 GROUP BYWHERE を書き換えて再実行するだけでよい(ETL なら抽出からやり直し)。 参考までに 2023 年度の出生数トップ 3 は 東京都 86,348 / 大阪府 55,292 / 神奈川県 53,991(同 raw 表への SQL で確認可能)。 これが「先にロード → DWH 内で何度でも変換」という ELT の機動力である。