この用語と一緒に検索・参照されやすいタグ。 関連ページに飛ぶときの手がかりにも使えます。
「elt」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「elt」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「elt の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
データを先に保存して後で加工する方法です。
効率よくデータを分析するために使います。
スマホの写真を全部保存して後で分ける感じです。
ここではELTの結論を短くまとめます。
ELT(Extract-Load-Transform)は、 データを先にロードしてから DWH 内で変換する近代的データ基盤パターン。 ETL の進化形。
ここまでが要点です。 ただし実際に使う前に、 このページの「⚠️ よくある落とし穴」で挙げた DWH コスト爆発/変換ロジックの散逸/PII を生で保持 には必ず目を通してください。 つまずくのは知識が無いときより、 知ってはいたが確認を飛ばしたときです。
🍰 まずはやさしく
大きなデータを扱うときの基本ルールです。
仕事で分析基盤(データの土台)を作る時に使います。
部活の大量の記録を整理する場面に似ています。
どのような場面でこの考え方が役立つか読みます。
SSDSE のような単一 CSV はETL/ELT 不要ですが、 企業の分析基盤では避けて通れない概念。 「データレイクハウス」「Modern Data Stack」の中核を成します。
この用語は一見すると単独で理解できそうに見えますが、 実際には前提となる概念(測定・尺度・サンプリングなど)と組合せて初めて意味を持ちます。 「定義を覚える」より「どんな問いに答える道具なのか」を捉えるのが効率的です。
🍰 まずはやさしく
素材のまま冷蔵庫に入れて後で料理するイメージです。
状況に合わせて柔軟にデータを変えるために使います。
買い物した食材をまず全部棚に入れる感覚です。
直感的にELTがどんなものか理解しましょう。
「ELT」を最初に学ぶときは、 厳密な定義よりイメージを優先しましょう。 以下は具体例・比喩を用いた直感的理解の入口です。
🍰 まずはやさしく
データの流れを順番に決めた仕組みのことです。
間違いのない正確な処理を行うために使います。
テストの点数を集めてから平均を出す流れに似ています。
ELTの正確な定義と仕組みについて読みます。
直感の次は、 厳密な定義を確認します。 数式は言語の一種で、 一度書き慣れれば「言葉より速く伝えられる」便利な道具。 慣れていない方は、 各記号が何を表すかを「🔬 数式を言葉で読み解く」で 1 つずつ確認してください。
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, SSIS | Fivetran/Airbyte + dbt + Snowflake/BigQuery |
💬 「クラウド DWH 革命」(2010s 後半) が ELT 流行の前提。 Snowflake/BigQuery が秒単位課金 + 並列クエリで 計算リソースが事実上無限になり、 「データを動かさずに加工する方が速くて安い」になった。 ETL 時代の「データ移動が高コスト・DB は計算しない」前提が逆転した。
マスター系テーブル (Dimension) のレコードが時間とともに変わるとき、 どう履歴を残すか。 これが SCD 問題。 4 種類の戦略があり、 dbt の snapshot 機能や Type 2 が最も普及している。
| タイプ | 挙動 | 用途 |
|---|---|---|
| SCD Type 0 | 変更を無視 (初回値を固定) | 生年月日など本来不変の属性 |
| SCD Type 1 | UPDATE で上書き、 履歴消失 | スペルミス修正など履歴不要 |
| SCD Type 2 | 変更ごとに新行追加、 valid_from/valid_to で期間付与 | 最も普及、 過去時点の状態を再現可能 |
| SCD Type 3 | 「現在の値」と「前の値」を列で持つ | 変化が稀で 1 つ前だけ必要 |
| SCD Type 4 | 履歴を別テーブルに分離 | 本表は軽量にしたい時 |
| SCD Type 6 | Type 1 + 2 + 3 のハイブリッド | 最大柔軟、 複雑 |
💬 ELT の Transform 層で SCD Type 2 を実装するなら dbt snapshot が便利。 updated_at 列 (もしくはハッシュ) を基準に変更検知し、 dbt_valid_from / dbt_valid_to 列を自動付与してくれる。 都道府県マスタが滅多に変わらなくても、 「2020 年に静岡県の県庁所在地コードが変わった」のような史実は SCD2 で残せる。
ELT で 100+ モデルを積み上げると、 「raw.users テーブルのスキーマが変わったら何が壊れる?」という影響解析が必要になる。 dbt は dbt docs generate で全モデルのリネージ DAG を HTML で自動生成。 OpenLineage / Marquez など別エコシステムもある。
| ツール | 特徴 | 使いどころ |
|---|---|---|
| dbt docs | SQL ref() から自動推論 | dbt 内のモデル間リネージ |
| OpenLineage + Marquez | Airflow/Spark/dbt 横断的に収集 | マルチエンジン環境 |
| Atlan / DataHub | エンタープライズカタログ + リネージ | 数百テーブル規模 |
| Monte Carlo | データオブザーバビリティ + 異常検知 | SLA 必須の本番 DWH |
| SqlLineage (OSS) | SQL 文を解析して列レベル lineage | CI でリネージ自動検証 |
| 要件 | バッチ ELT | マイクロバッチ | ストリーミング |
|---|---|---|---|
| 遅延要件 | 数時間 OK | 5〜15 分 | 数秒 |
| 主要ツール | Airflow + dbt | dbt + scheduler 短間隔 | Kafka + Flink/Spark Streaming |
| 運用コスト | 低 (1 人月) | 中 | 高 (運用専任必要) |
| DWH コスト | 低 | 中 | 高 (常時 warehouse 稼働) |
| 複雑度 | 低 | 中 | 高 (順序・重複・遅延データ) |
| 典型ユースケース | 経営 BI、 月次レポート | マーケ ダッシュボード | 不正検知、 アラート、 在庫 |
💬 「ストリーミング ELT」は技術的に華やかだが、 99% の業務要件はバッチで十分。 「リアルタイムが必要」と言い出した時、 (a) ビジネス価値が運用コスト + DWH コスト増を上回るか、 (b) マイクロバッチ (15 分間隔) で要件を満たせないか、 を必ず確認すべき。
| 疑問 | 答え |
|---|---|
| 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 倍ほど上がります。
数式だけでは「実感」が湧きにくいので、 具体的な数値で 1 度手計算してみると理解が定着します。 以下の例は、 本サイトで扱う SSDSE-B-2026 や公開教材に近い形式で用意しました。
典型的な構成と役割:
| 層 | 役割 | 更新頻度 | 例 |
|---|---|---|---|
| Raw | 生データ保管(変更しない) | 到着次第 | JSON / CSV そのまま |
| Staging | 型・命名統一 | 日次 | SQL で軽い整形 |
| Mart | BI 用集計 | 日次/時次 | KPI・ダッシュボード用 |
手計算で得た値と、 後述の Python 実装で算出した値が一致することを確認すると、 「数式とコードの対応関係」がクリアに見えるようになります。
大規模 fact テーブルのパーティション設計を SSDSE-B-2026 で疑似体験する。 年度をパーティションキーにして「2023 年だけスキャン」を実現すると、 12 年分すべてスキャンする場合と比べてコストが約 1/12 になる。
🎯 このコードでやること: DuckDB で年度パーティション風に PARTITIONED BY 句相当の方法 (= PARQUET 出力 + Hive スタイルパーティション) でデータを書き出し、 「2023 年のみ」を読み込む SELECT がフルスキャンよりどれだけ速いか測る。
📥 入力データ:
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} 秒') |
📤 実行結果:
💬 読み方: パーティション枝刈りは 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={...}) で同等を実現。
| フェーズ | チェック項目 | ツール例 |
|---|---|---|
| Extract | ソースに対する API/JDBC コネクタ、 schema drift 対応 | Fivetran, Airbyte, Stitch, custom Python |
| Load | raw 層に無変換投入、 ロードバッチ ID 付与 | DWH の COPY INTO / bq load / Snowpipe |
| Transform | staging → mart の dbt モデル、 ref() で依存 | dbt, Dataform, SQLMesh |
| Test | not_null, unique, accepted_values, relationships | dbt test, Great Expectations |
| Document | model description, column description, lineage | dbt docs, DataHub, Atlan |
| Orchestrate | スケジュール、 リトライ、 アラート | Airflow, Dagster, Prefect, dbt Cloud |
| Monitor | freshness, 行数異常、 SLA | Monte Carlo, Datafold, elementary |
| Govern | アクセス制御、 PII マスキング、 監査ログ | Immuta, BigQuery IAM, Snowflake roles |
| Cost Control | パーティション、 incremental、 quota | DWH 標準機能 + 内製モニタリング |
| CI/CD | PR で dbt build state:modified+、 マージで本番デプロイ | GitHub Actions, CircleCI |
💬 このチェックリストの 10 項目すべてに「誰が・どのツールで・どう実装しているか」答えられれば、 ELT 基盤として一級品。 4〜6 項目しか埋まっていない場合は「未成熟」とみなし、 残りを順次整備していく。 特に Test と Monitor が抜けていると、 数か月後にデータ品質崩壊が発覚する典型パターンに陥る。
| 年 | 出来事 | 意義 |
|---|---|---|
| 1990s | Informatica, Oracle Warehouse Builder | ETL ツール商用時代、 GUI 中心 |
| 2006 | Hadoop / MapReduce | 分散処理で TB 級データ可 |
| 2010 | Hive | Hadoop 上で SQL、 ELT 萌芽 |
| 2011 | Google BigQuery GA | サーバレス DWH、 ペタバイトを秒で |
| 2014 | Snowflake 1.0 | 計算/ストレージ分離、 マルチクラウド |
| 2016 | dbt v0.1 (Fishtown Analytics) | SQL ベース ELT フレームワーク誕生 |
| 2018 | Fivetran / Airbyte 隆盛 | Extract+Load の SaaS 化 |
| 2020+ | Modern Data Stack 確立 | Fivetran + dbt + Snowflake/BQ が標準 |
| 2022+ | Data Mesh、 Lakehouse、 OpenTable Format | Iceberg/Delta/Hudi で DWH 境界が再溶解 |
💬 ELT は「クラウド DWH の出現 + dbt の登場」という 2 つの偶然が重なって 2018 年頃から急速に普及。 ETL を駆逐したのではなく「使い分けるもの」になった ── レガシー DB 連携や複雑な業務ルールは ETL、 クラウド主導のアナリティクスは ELT、 ストリーミングは Flink + dbt のような融合形態が現代の選択肢。
💬 多くの組織は Stage 1〜2 でしばらく止まる。 Stage 3 以降に進むには「分析者だけでなくデータエンジニア」がチームに必要。 ELT は「SQL だけで完結する分」プログラミング寄りのスキルセットを軽視しがちだが、 Stage 4〜5 ではむしろ本格的な SRE / DevOps スキルが要求される ── 「ELT が簡単に見えるのは Stage 1 だけ」というのが現場の真実。
ELT は「DWH (BigQuery/Snowflake) の従量課金 = スキャン GB あたりコスト」が中心。 ETL は「専用 ETL サーバの維持コスト」が中心。 ここでは SSDSE-B-2026 の都道府県データをロードし、 行数とスキャン量から想定コストを試算する。 数式を言葉で読み解く工程を入れつつ、 ELT が「クエリ最適化と相性が良い」理由を明確にする。
数式を言葉で読み解く: c_store はストレージ単価 (GB/月)、 V_store は保管総量。 c_scan はスキャン単価 (BigQuery で約 6.25 USD/TB)、 V_q^scan はクエリ q が舐めるバイト数。 ELT の運用コストは「クエリの設計」が支配的で、 パーティション・カラムナ・SELECT * 回避で大きく下げられる。
実値: SSDSE-B-2026 は 47 都道府県 × 約 100 列 × 7 年 ≈ 33,000 行。 1 行平均 1.2 KB として保管量は 40 MB、 1 日 100 クエリ × 平均 5 MB スキャンと仮定。
| 項目 | 単価 | 月量 | 月コスト |
|---|---|---|---|
| ストレージ | 0.02 USD/GB | 0.04 GB | 0.0008 USD |
| スキャン | 6.25 USD/TB | 15 GB (100q × 30day × 5MB) | 0.094 USD |
| 合計 | — | — | ≈ 0.095 USD/月 |
🎯 このコードでやること:SSDSE-B-2026 をロード後、 想定スキャン量と単価から月額コストを試算し、 さらに SELECT * を SELECT col1, col2 に絞った場合の削減効果を比較する。
📥 入力例 (SSDSE-B-2026 の主要カラム):
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}% 削減)") |
📤 実行すると次の出力が得られる:
💬 列指定で 95% 以上 スキャン量が削減される。 これが BigQuery / Snowflake などカラムナ DWH のメリットであり、 ELT パターンで「SELECT * 禁止」が現場ルールになる理由でもある。
maximum_bytes_billed でガード。partition_by 設定必須。合成データで ELT (Load 先行) と ETL (Transform 先行) の総時間を比較する。
| パターン | Extract | Load | Transform | 合計 |
|---|---|---|---|---|
| ETL | 10 | 20 | 30 (CPU) | 60 |
| ELT | 10 | 20 | 10 (DB 内) | 40 |
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}%)") |
💬 手計算 (Step 2) 33% 削減と Python 出力が完全一致。
公的統計(SSDSE-B-2026)を題材に、 最小限の Python コードで動作させます。 ファイルパス(data/raw/SSDSE-B-2026.csv)は自分の環境に合わせて変更してください。 まずはこのまま動かすことが理解の最短ルートです。
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()) |
▶ 実行 を押せばこのページの中でそのまま動きます(ライブラリもデータも同梱済みで、 準備は要りません)。 手元の Python に移して動かすときは pip install pandas が必要です。 読んでいるデータは data/raw/SSDSE-B-2026.csv。 日本語を含むので encoding='cp932' の指定を落とさないでください。
本サイトの全コードは 論文一覧ページ から実例として確認できます。 自分のデータで試したい場合は、 列名・欠損記号・単位の違いだけ調整すれば、 ほぼそのまま流用できます。
「ELT」を初めて使う方向けに、 ハンズオン的な実行手順を整理します。 上の Python 実装と組み合わせて、 1 度自分の手でなぞってみることを強く推奨します。
data/raw/ に配置(または自分のデータを用意)。 列名と単位を確認。df.head()、 df.describe()、 df.isna().sum() で全体像を把握。 ここで欠損や外れ値の見当を付ける。この 8 ステップを 1 度回すと、 「用語を読んで分かった気になる」段階から「実際に使える」段階に進めます。 知識は身体で覚えるのが結局のところ最速です。
ELT は 抽出(Extract)→ ロード(Load)→ 変換(Transform)の順で動くため、 ロード直後の生データと、 SQL 変換後の集計データを並べて見ると、 パイプラインのどこで欠損や偏りが入り込むかを直感的に把握できる。 ここでは SSDSE-B-2026 都道府県データ(df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932'))を BigQuery 風データウェアハウスにロードした想定で、 3 種類の標準図を提示する。
このコードでやること:ELT パイプラインでロード済みの raw 表(raw_b2026)から、 2023 年度の都道府県 47 行を SQL で SELECT し、 そのまま matplotlib に渡して散布図を描く。 これは Transform レイヤの代表例で、 BI ツールが裏で実行している処理に等しい。
📥 入力データ(SSDSE-B-2026 の 2023 年度抜粋、 A1101=総人口 / A4200=死亡数):
💬 読み方:人口が多い都道府県ほど死亡数も多く、 右上に東京・大阪など大都市が並ぶ強い正の相関が見える(高齢化率の違いで直線からのズレも生じる)。 ELT で そのまま全行ロードしている証拠で、 ETL のように事前集約していたらこの分布は見えない。 ELT の利点は「後から SQL で何度でも切り直せる」点にあり、 BI ダッシュボードの大半はこの段階で十分実用になる。
このコードでやること:ELT の T 工程に入る前の ロード直後の生データに対して、 分布形状を確認する。 ヒストグラムは「ロードに失敗した行は無いか」「単位ミス(千人と人の混在)が無いか」の最初のスモークテストになる。
📥 入力データ(SSDSE-B-2026 の総人口列 A1101、 2023 年度・47 都道府県):
💬 読み方:右に長い裾を持つ強い右裾分布。 平均 265 万人に対し中央値はもっと小さく、 ロード自体は正常(明らかな異常値・単位ミスは無い)と判断できる。 ELT であれば、 この確認結果を基に T 工程で LOG10(population) 列を追加するだけで対数化が完結する。 ETL のように事前に物理列を作り直さなくて済むのが ELT の機動力だ。
このコードでやること:T 工程で SQL の CASE WHEN によって 47 都道府県を 4 地域(北海道・東日本・中日本・西日本)に再分類し、 地域ごとの人口分布を箱ひげ図で比較する。 ELT 上で「集計軸を変えたい」と言われた瞬間に SQL 一発で対応できる例。
📥 入力データ(2023 年度の総人口を 4 地域に再分類した概算、 SSDSE-B-2026 から派生):
💬 読み方:中日本(東京・名古屋圏)は中央値と上ひげが他地域を上回り、 西日本は大阪・福岡の外れ値が目立つ。 ELT であれば、 別途「人口密度ベースで地域を再定義したい」とリクエストが来ても、 SQL 上で CASE WHEN を書き換えるだけで即対応できる。 これが ETL(事前に物理テーブルを作るアプローチ)に対する最大の差別化点。
現代の 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=死亡数):
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) |
📤 実行例(実際に出る出力):
💬 読み方:2023 年度で人口 100 万人以上の県は 37 件、 合計人口 約 1.17 億人、 死亡数の合計は約 145.5 万人。 同じ SQL の WHERE を書き換えるだけで「200 万人以上」「都市圏のみ」など何度でも切り直せる。 ETL ならその度に再加工コードを書き直す必要があるが、 ELT は DWH の生表に対して SQL を当てるだけで済む。
どちらを採るべきかは、 (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 採用の前後で実務がどう変わるか、 SSDSE-B-2026 を題材に 4 つの観点から踏み込む。
ETL の典型は「夜間バッチでまとめて変換し、 翌朝に集計済みテーブルを提供する」運用だった。 これに対し ELT は「到着次第ロードし、 SQL は分析者が叩く瞬間に実行する」モデル。 SSDSE-B-2026 を例にとると、 政府統計の公開直後にロードすれば、 数時間以内に最新の集計値を BI で確認できる。 ETL のように夜間バッチを待つ必要がない。
💬 効果:データ鮮度が 36 倍向上。 政策発表直後の都道府県統計や、 災害時の人口移動データなど、 速報性が求められる場面で ELT は決定的な差を生む。
ETL では Transform が失敗するとパイプライン全体が止まり、 中間データも失われる。 ELT は生データを先にロードするため、 Transform で問題があっても SQL を書き直すだけで復旧できる。 SSDSE-B-2026 の例で言えば、 「消費支出(L3221)の単位を円から千円に直し忘れた」場合、 ETL なら全工程を再実行する必要があるが、 ELT なら / 1000 を SQL に追加するだけで済む。
| 失敗シナリオ | ETL の対応 | ELT の対応 |
|---|---|---|
| 単位ミス(百万円→億円) | 全パイプライン再実行(数時間) | SQL に /100 を追加(数秒) |
| 集計軸の追加(地域→市町村) | ETL ジョブ書き直し(半日) | GROUP BY 市町村 追加(数秒) |
| 欠損補完手法の変更 | 物理列を作り直す(数時間) | VIEW の SQL を書き換え(数秒) |
| 分析者の試行錯誤 | エンジニアに依頼(数日) | 分析者自身が SQL で実験(分単位) |
ELT では生データから集計値までの全工程が SQL クエリ履歴として DWH に残るため、 「この数字はどこから来たのか」を完全に再現できる。 監査対応や論文の再現性確保に直結する重要な特性で、 SSDSE-B-2026 のような公的データを使う分析では特に重視される。
📥 系譜情報の自動取得例(BigQuery INFORMATION_SCHEMA より):
💬 効果:「dashboard_view の数字 → region_summary → pref_summary → raw_b2026 → SSDSE-B-2026.csv」の系譜が SQL 履歴だけで復元できる。 ETL では別途メタデータカタログが必要だったが、 ELT では DWH 標準機能で十分。
ELT は「ストレージは安く、 計算は使った分だけ」モデルが前提。 SSDSE-B-2026(約 50 MB)を毎日ロードしても年間ストレージ料金は数百円。 計算は分析者が SQL を叩いた瞬間だけ発生し、 月数千円で収まる。 これに対し ETL は ETL サーバーの常時稼働コストが固定で発生するため、 利用頻度が低いと割高になる。
💬 結論:個人の研究や小規模ベンチャーは ELT で月数百円から始められる。 ETL の固定費が正当化されるのは「大量の定型レポートを毎日大量に出す」企業のみ。 教育・研究用途では 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 はほぼそのまま動く。
数百 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 のモデル定義ファイルに 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 は SQL を「モデル」と呼ばれる Git 管理されたファイルとして扱う。 {{ ref('raw_b2026') }} で他モデルへの依存を宣言し、 dbt が自動で DAG(依存グラフ)を構築。 ELT の T 工程を コードレビュー可能・テスト可能な形にしたのが dbt の革新性。
この用語を使うときに初学者が踏みやすい失敗パターン。 1 度経験してしまえば次から避けられますが、 先に知っておくに越したことはありません。
BigQuery/Snowflake は秒単位課金 + クエリスキャン量課金で「使った分だけ」だが、 SELECT * FROM huge_table を脳死で書くと月数十万円の請求が来る。 ELT を本番運用するならコスト管理が ETL より重要。
| コスト要因 | 兆候 | 対策 |
|---|---|---|
| フルスキャン | パーティション/クラスタリング無視 | PARTITION BY (event_date) + WHERE で枝刈り |
| SELECT * | 列指向 DWH で全列スキャン | 必要列のみ明示、 view で列を絞る |
| 毎日 full rebuild | dbt 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 説得材料として必ず設定する。
| 落とし穴 | 兆候 | 対処 |
|---|---|---|
| raw 層に変換を混ぜる | 「Extract 時に型変換しちゃおう」 | raw は 絶対に無変換 ── 取り直し可能性を保つ |
| staging を表 (table) で作成 | ストレージ二重化 | staging は view、 mart は table が定石 |
| タイムゾーン無視 | 日次バッチで日付がずれる | UTC 統一、 表示時のみ変換 |
| スキーマ変更で全モデル red | raw の列名変更で連鎖崩壊 | 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 は強力だが、 「順序を逆にすればよい」と短絡的に捉えるとハマる。 SSDSE-B-2026 を扱う実演習でもよく遭遇する落とし穴を、 対処法とセットで列挙する。
SSDSE-B-2026 のような公開統計では問題ないが、 自社の顧客データを ELT で扱う場合、 マスキングを怠ると重大事故になる。 対処:ロード時点で SECURE VIEW を作成し、 PII カラムは SHA256() でハッシュ化、 アクセス権を最小権限で分離する。 BigQuery では Column-level Security、 Snowflake では Dynamic Data Masking を活用。
BigQuery のような従量課金型 DWH では、 毎回 SELECT * すると 1 クエリ数千円になることがある。 対処:必要な列だけ指定、 パーティション(日付分割)を活用、 マテリアライズドビューで頻繁な集計を事前計算。 SSDSE-B-2026 程度では問題にならないが、 数百 GB クラスでは設計の良し悪しで月額数十万円の差が出る。
モデル数が数百を超えると、 1 つの SQL の変更が下流の数十モデルを破壊することがある。 対処:dbt の --exclude、 --defer、 lineage graph 可視化を駆使し、 CI で全モデルテストを毎回実行する。 SSDSE-B-2026 規模なら 5 モデル程度で済むが、 本番運用では DAG の設計力が問われる。
SSDSE-B が年度更新で列名や順序が変わると、 既存の SQL が即座に壊れる。 対処:Fivetran や Airbyte のスキーマ自動追従機能を有効化、 dbt の schema.yml でカラムをテスト、 重要列の rename は alias で吸収する。 「ロード層は変えず、 Transform 層で旧名にマッピング」が王道。
BigQuery の独自関数(STRUCT、 ARRAY_AGG)に依存すると Snowflake へ移れない。 対処:標準 SQL の範囲で書く、 dbt の adapter 機能で DWH 非依存にする、 Iceberg や Delta Lake のような open table format を採用してテーブル自体を可搬にする。 SSDSE-B-2026 を扱う教育用途では問題ないが、 企業では長期視点が必要。
機械学習の特徴量生成や時系列の窓関数は 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 BY、 WINDOW、 JOIN の練習にちょうど良い。 集計結果が手計算で検算できるため、 SQL の挙動を直感的に確かめながら学習を進められる。 これが大規模ログデータでの学習にはない、 公的統計データを使う独自の強みである。
また、 ELT の運用では「データの正しさ」を保証するテスト文化が重要になる。 dbt の tests 機能を活用すれば、 「total_pop は NULL でないこと」「都道府県は重複しないこと」「死亡数(A4200)は正の値であること」といった制約を SQL で記述・自動検証できる。 SSDSE-B-2026 の演習でこのテスト習慣を身につけておけば、 本番運用に移っても品質を担保し続けられる。 ELT は単なる技術トレンドではなく、 データ品質を継続的に検証する組織文化そのものを変える運動である、 と理解するのが正しい。
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 を中心に学ぶことが、 そのまま実務直結のスキルになる時代になった。
GROUP BY 句を差し替えるだけで完結する。SELECT で叩いてみること。 DuckDB は CSV を SQL でそのまま読めるため、 「Load 不要、 即 Transform」の体験ができる。 30 分の演習で ELT の本質が掴める。staging / intermediate / marts でこれを反映している。 SSDSE-B-2026 を扱う際も、 raw → cleaned → pref_summary という 3 層化を意識すると、 SQL がメンテナンスしやすくなる。 この設計は Databricks が提唱したものだが、 現在は Snowflake や BigQuery でも標準的に採用されている設計思想となった。ELT (Extract Load Transform) は「先にロードしてから変換」というパターンで、 ETL の変換タイミングを逆転させた現代的な手法。 上流の生データソース ・下流の DWH (Snowflake・BigQuery 等) と密結合し、 dbt が代表的な Transform 層のツールとして並列する。
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 を選ぶ。
SSDSE-B-2026 のような既に整形された公開データを社内基盤に取り込むなら ELT が向き、 まず生 CSV を S3 や DWH にそのまま置き、 後段で都道府県コード・年次の整合性を SQL で確認する流れが効率的である。
ELT 最大の実務的メリットは、 生データが DWH の staging 層に残っているため、 変換ロジックにバグが見つかっても「SQL 変換だけを再実行 (リプレイ)」すれば mart が直ること。 ETL 方式では変換後のデータしか残らないので、 ソースからの再抽出が必要になりコストが跳ね上がる。 下のシミュレータで両方式のリプレイコストを比べてみよう。 (ETL/ELT の順序比較そのものは ETL のページ、 DWH・データレイクの位置づけは DWH・データレイク を参照。)
シナリオ: SSDSE-B-2026 の 2023 年度実測値 (総人口 A1101・出生数 A4101) が staging 層にロード済み。 mart 層では「出生率 = 出生数 ÷ 総人口 × 係数」を SQL で計算する。 正しい係数は ×1000 (人口千人あたり) だが、 ある日誰かが係数を間違えてデプロイしてしまった…!
| 都道府県 | staging: 総人口 (人) | staging: 出生数 (人) | mart 現在値 | スライダー適用プレビュー | 正解 (×1000) |
|---|---|---|---|---|---|
| 北海道 | 5,092,000 | 24,430 | 4.80 | 4.80 | 4.80 |
| 東京都 | 14,086,000 | 86,348 | 6.13 | 6.13 | 6.13 |
| 広島県 | 2,738,000 | 16,682 | 6.09 | 6.09 | 6.09 |
| 沖縄県 | 1,468,000 | 12,549 | 8.55 | 8.55 | 8.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 ファイルを残しておけば現像 (レタッチ) を何度でもやり直せるのと同じ理屈。
dbt run 一発で依存順にリプレイしてくれるフレームワーク。 「変換の乱立」対策の事実上の標準。CREATE OR REPLACE TABLE や MERGE で実装する。 べき等でない変換 (INSERT 追記のみ等) はリプレイで行が二重化するため、 リプレイ容易性の前提条件と言える。BigQuery/Snowflake のような本格 DWH の代わりに、 ローカルで動く DuckDB (列指向・解析特化の埋め込み DB) で ELT パターンを再現する。 これにより「クラウドアカウントなしで ELT 全工程をハンズオン体験」できる。
🎯 このコードでやること: SSDSE-B-2026 を Extract → Load (raw schema) → Transform (staging → mart) という ELT 三層に分け、 DuckDB の SQL で各層を構築する。 最終 mart で「都道府県別・年度別の出生率パネル」を作る。
📥 入力データ:
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()) |
📤 実行結果:
💬 読み方: ELT 三層構造のメリットは、 (1) 生データを保持するので「あとから別の集計」が SQL 一つで可能、 (2) 各層が SQL なので分析者にも理解可、 (3) DuckDB/Snowflake/BigQuery で同じ SQL がほぼ動く (移植性)。 沖縄の出生率 8.55 が首位、 福岡 6.65 が 2 位。 東京・愛知など大都市は人口効果で出生数(births)は多いが、 1000 人あたり出生率では沖縄・福岡に劣る ── ELT で派生指標を持つと一目瞭然。
dbt (data build tool) は ELT の T 層を専用フレームワークで管理する OSS。 SQL ファイル + Jinja テンプレート + YAML 設定で「モデル」を組み立て、 依存関係 (DAG) を自動解決して順次実行する。 ELT が広まったきっかけの一つ。
🎯 このコードでやること: dbt プロジェクトの典型構造を示し、 SSDSE-B-2026 を ELT 化する 3 つの SQL モデル (raw → staging → mart) を Jinja 付き SQL で書く。
📥 入力データ:
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 の出力例:
💬 読み方: dbt の強みは (1) ref() 関数で依存関係を宣言 → DAG を自動構築、 (2) テストが YAML で書ける → 「pref_code は unique」のような契約を CI で検証、 (3) 自動ドキュメント生成 → モデル間のリネージ可視化、 (4) materialization 戦略 (view/table/incremental/ephemeral) を YAML で切替。 これにより「SQL がソフトウェアエンジニアリングできる」ようになった。
ELT の生命線はテスト。 「raw 投入時にスキーマが変わった」「null が突然増えた」「期間外の値が混入」を CI 段階で捕捉できないと本番障害になる。 GE (Great Expectations) と dbt test の二大ツール。
🎯 このコードでやること: dbt のテスト構文 (schema.yml) と、 Python から Great Expectations で SSDSE-B-2026 にテストを書く 2 通りを示す。
📥 入力データ:
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']) |
📤 実行結果:
💬 読み方: 4 つの契約がすべて成立。 ELT pipeline の CI に組み込めば「データ品質崩壊」を本番投入前に検出可能。 GE は HTML レポートを自動生成して関係者に共有できる点も強み。 dbt test は SQL ベースで CI/CD と相性抜群、 GE は Python ベースで複雑な期待値も書ける ── ハイブリッド運用が現代の定石。
fact テーブルが 100 億行になると毎回 full rebuild は非現実的。 dbt の materialized='incremental' で「前回以降の差分だけ」追加できる。 BigQuery では MERGE 文に翻訳され、 イベント時刻ベースで効率的に動く。
🎯 このコードでやること: SSDSE-B-2026 では年度=パーティションキー。 incremental モデルで「最後にロードされた年度より後」だけ追加する dbt SQL を書く。
📥 入力データ:
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 |
📤 実行結果:
💬 読み方: 差分更新の鍵は {% if is_incremental() %} Jinja ブロックで「既存 vs 新規」のロジックを切り替えること。 unique_key 指定で重複は MERGE 文で自動マージ。 1 億行のテーブルでは初回構築に 30 分かかっても、 毎日の差分は数秒で終わるようになり、 ETL と比較した ELT 最大のコスト効率源となる。
ELT は (1) Extract+Load (Fivetran/Airbyte), (2) dbt Transform, (3) BI 通知/ML 学習 の連携が必要。 これを束ねるのがオーケストレーター。 Airflow が最古参で標準的。 近年は Dagster / Prefect も台頭。
🎯 このコードでやること: Airflow の DAG として「①CSV ダウンロード → ②DuckDB に Load → ③dbt run → ④品質テスト → ⑤Slack 通知」の一連を記述する。
📥 入力データ:
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 表示:
💬 読み方: Airflow の強みは (1) 失敗時の自動リトライ + アラート、 (2) backfill (過去日付の再実行) サポート、 (3) DAG ビジュアライザで依存関係を一覧、 (4) Web UI でログ確認。 dbt と組み合わせると「データ取り込み → 変換 → 検査 → 通知」を 1 つの DAG で表現でき、 失敗箇所の特定も容易。 dbt Cloud の組み込みオーケストレーターを使えば Airflow なしでも完結する。
夜次バッチ ELT では物足りないリアルタイム需要に応えるのが CDC。 OLTP DB (Postgres/MySQL) の WAL/binlog を tail し、 変更イベントを Kafka 経由で DWH に流す。 Debezium が代表的 OSS。
🎯 このコードでやること: Debezium で Postgres → Kafka → BigQuery の CDC パイプラインを構築する設定例。 SQLAlchemy で行を更新 → 数秒以内に DWH に反映される様子を示す。
📥 構成:
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 メッセージ):
💬 読み方: CDC は「ELT のリアルタイム版」── バッチ ELT が 1 日 1 回更新だったのが秒〜分単位の更新になる。 ただし複雑度は跳ね上がる: (1) スキーマ進化、 (2) 順序保証、 (3) exactly-once delivery、 (4) 削除イベントの mart 反映。 リアルタイム要件 (ダッシュボードを 1 時間以内反映、 在庫管理を秒単位反映) がある場合のみ導入し、 そうでなければ夜次バッチで十分。
冒頭の「素材のまま冷蔵庫へ、 食べる時に料理」という比喩を、 もう一歩だけ踏み込んで解剖する。 ELT の本質は Extract → Load → Transform、 つまり生データを一切加工せず先に DWH(データウェアハウス)へ流し込み、 変換は後から DWH の中で行うという順序の逆転にある。 ETL では「Transform(料理)→ Load(冷蔵庫)」の順だったので、 冷蔵庫に入るのは調理済みの一品だけだった。 味付けを間違えたら素材はもう手元にない。
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 は単独の技術ではなく、 モダンデータスタック(Modern Data Stack)という一連の道具立ての中心に座る。 ここでは、 実務で次に学ぶべき発展トピックを、 関連ページへの導線とともに地図化する。
ELT は ETL を駆逐したのではなく「使い分けるもの」になった。 クラウド主導のアナリティクスや集計軸が頻繁に変わる分析業務は ELT、 レガシー DB 連携・複雑な業務ルール・ロード前に必ず PII を落とす必要がある要件は ETL が向く。 小規模(数十 MB)や組み込み用途では DWH の固定費が重いため、 pandas + cron のミニ ETL で十分なこともある。 詳細な対比表は本ページ上部「📐 ELT と ETL の決定的な違い」と ETL/ETL ツール を参照。
典型構成は 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 と同じ)。 各層を進むごとにデータの品質と信頼性が上がる。
毎回すべてを作り直す(full refresh)と DWH 課金が線形に膨らむ。 増分処理は「前回以降に増えた・変わった行だけ」を処理する。 updated_at や高水位マーク(high-water mark)を基準に差分を MERGE する。 dbt では materialized='incremental' で宣言的に実現でき、 本ページ「年度パーティション ELT」の枝刈りと組み合わせると効果が最大化する。
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=総人口):
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)) |
📤 実行結果(実際に出る出力):
💬 読み方:2023 年度の全国合計は 出生数 727,269 人・死亡数 1,575,084 人で、 自然増減は約 −84.8 万人(出生が死亡を大きく下回る自然減)。 ここで重要なのは、 生データ(raw_b2026)をそのまま残したまま、 集計は SQL 一発で得ている点。 「上位県だけ」「県別に」「200 万人以上だけ」など切り口を変えたくなっても、 GROUP BY や WHERE を書き換えて再実行するだけでよい(ETL なら抽出からやり直し)。 参考までに 2023 年度の出生数トップ 3 は 東京都 86,348 / 大阪府 55,292 / 神奈川県 53,991(同 raw 表への SQL で確認可能)。 これが「先にロード → DWH 内で何度でも変換」という ELT の機動力である。