
ローカルでゼロ構築、ファイル1つで完結、サーバ不要。本記事はSQLite×Pythonで“毎日回る”ETL・集計・レポート自動化を最短で作るための完全ガイドです。データ設計→DB作成→ETL(取り込み/整形/検証)→インデックス/PRAGMAによる高速化→pandas/Matplotlib/ExcelWriterでの配布まで、事故らない前処理の型として落とし込みます。最後に増分更新パイプラインと自動実行(タスクスケジューラ/cron)の雛形、そしてレビュー観点チェックリストも配布します。
この記事で身に付く力
- 軽量DWH(SQLite)設計とETL自動化:ローカル完結で安全・再現性の高い基盤づくり
- 高速化の実務勘所:PRAGMA/インデックス/バルク挿入の使いどころ
- 配布までの一気通貫:pandas+ExcelWriter+画像出力の納品パターン
ふみとの現場メモ
大手でマーケ分析を回していた頃、Excel 6ブック×40シートの手作業更新で毎月1時間×複数人が溶けていました。SQLiteに取り込み→SQLで整形→ExcelWriterで配布に切り替えたところ、更新は5分&再現性100%に。小さくてもDWH化は効きます。
SQLite×Pythonで何が解決できる?
CSVのまま複数JOINで破綻、Notebookに前処理コピペが散乱、毎月のExcel納品でコピペ地獄…こうした「あるある」を、SQLiteを軽量DWHとして採用し、PythonでETLと配布を自動化することで一掃します。ファイル1つにクエリと再現性を閉じ込めましょう。
使用環境
Python 3.10+ / pandas 2.x / SQLite3(標準ライブラリ) を想定。Windows/Mac/Linuxいずれでも動作します。
プロジェクト構成(雛形)
retail_etl/
├─ data/
│ ├─ raw/ # 受領データ(CSV等)
│ └─ warehouse/ # SQLite DB配置
├─ src/
│ ├─ etl.py # 取り込み/整形
│ ├─ sql/ # SQLテンプレ
│ │ ├─ create_tables.sql
│ │ └─ queries.sql
│ └─ report.py # 集計→Excel/画像出力
├─ reports/
├─ requirements.txt
└─ README.md
スキーマ設計 → SQLite DBを作る
例として sales(order_date, store, product, qty, price)
と stores(store, area, open_date)
。主キー/UNIQUE制約で重複挿入を物理的に防ぎ、複合インデックスはフィルタ/結合列へ。
PRAGMA foreign_keys=ON;
CREATE TABLE IF NOT EXISTS sales (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_date TEXT NOT NULL,
store TEXT NOT NULL,
product TEXT NOT NULL,
qty INTEGER NOT NULL,
price REAL NOT NULL,
UNIQUE(order_date, store, product, qty, price)
);
CREATE TABLE IF NOT EXISTS stores (
store TEXT PRIMARY KEY,
area TEXT,
open_date TEXT
);
CREATE INDEX IF NOT EXISTS idx_sales_store_date ON sales(store, order_date);
DB初期化スクリプト(Python)
WAL
で同時読取に強くし、foreign_keys=ON
で参照整合性を担保します。
from pathlib import Path
import sqlite3
ROOT = Path(**file**).resolve().parents\[1]
DB = ROOT / "data/warehouse/retail.sqlite"
SQL\_DIR = ROOT / "src/sql"
def init\_db() -> None:
DB.parent.mkdir(parents=True, exist\_ok=True)
con = sqlite3.connect(DB)
try:
con.execute("PRAGMA journal\_mode=WAL;") # 高速/同時読み取り
con.execute("PRAGMA synchronous=NORMAL;")
con.execute("PRAGMA foreign\_keys=ON;")
con.executescript((SQL\_DIR / "create\_tables.sql").read\_text(encoding="utf-8"))
finally:
con.close()
取り込み(CSV→SQLite)とバリデーション
欠損・型・範囲の検査を取り込み前に行い、壊れた行を入れません。
import pandas as pd, numpy as np, sqlite3
from pathlib import Path
ROOT = Path(**file**).resolve().parents\[1]
DB = ROOT / "data/warehouse/retail.sqlite"
def load\_csv\_to\_db(csv\_path: str) -> None:
con = sqlite3.connect(DB)
try:
df = pd.read\_csv(csv\_path)
\# バリデーション(最小限)
req = {"order\_date","store","product","qty","price"}
assert req.issubset(df.columns)
df = df.dropna(subset=\["order\_date","store","product"])
df\["qty"] = pd.to\_numeric(df\["qty"], errors="coerce").fillna(0).astype(int)
df\["price"] = pd.to\_numeric(df\["price"], errors="coerce").astype(float)
df = df\[df\["qty"] > 0]
\# 追記
df.to\_sql("sales", con, if\_exists="append", index=False)
finally:
con.close()
安全なINSERT(DB-APIのプレースホルダ)
import sqlite3
from pathlib import Path
DB = Path(**file**).resolve().parents\[1] / "data/warehouse/retail.sqlite"
def insert\_store(store: str, area: str, open\_date: str) -> None:
con = sqlite3.connect(DB)
try:
con.execute("PRAGMA foreign\_keys=ON;")
with con: # トランザクション
con.execute(
"INSERT OR REPLACE INTO stores(store, area, open\_date) VALUES(?,?,?)",
(store, area, open\_date)
)
finally:
con.close()
集計クエリを関数化(重い整形はSQL、配布はpandas)
import sqlite3, pandas as pd
from pathlib import Path
DB = Path(**file**).resolve().parents\[1] / "data/warehouse/retail.sqlite"
Q\_MONTHLY = """
WITH monthly AS (
SELECT strftime('%Y-%m', order\_date) AS ym, store,
SUM(qty\*price) AS sales, SUM(qty) AS qty
FROM sales
WHERE qty>0 AND price>0
GROUP BY ym, store
)
SELECT \* FROM monthly;
"""
def fetch\_monthly() -> pd.DataFrame:
con = sqlite3.connect(DB)
try:
return pd.read\_sql\_query(Q\_MONTHLY, con)
finally:
con.close()
レポート出力(Excel/画像)
集計→図の保存→Excel出力までをワンショットで。
from pathlib import Path
import matplotlib.pyplot as plt
from queries import fetch_monthly
ROOT = Path(**file**).resolve().parents\[1]
def export\_report() -> None:
df = fetch\_monthly()
\# 可視化(例:月次売上推移)
g = df.groupby("ym", as\_index=False)\["sales"].sum()
plt.figure(); plt.plot(g\["ym"], g\["sales"], marker="o")
plt.title("月次売上の推移"); plt.xticks(rotation=45); plt.tight\_layout()
fig\_path = ROOT / "reports/monthly\_sales.png"
plt.savefig(fig\_path, dpi=200, bbox\_inches="tight")
```
# Excel書き出し
xlsx = ROOT / "reports/report.xlsx"
xlsx.parent.mkdir(parents=True, exist_ok=True)
with pd.ExcelWriter(xlsx) as xw:
df.to_excel(xw, sheet_name="monthly", index=False)
print("Saved:", xlsx, fig_path)
```
Excelには定義シート(SQL/前提)を同梱すると親切です。
増分更新(差分ロード)の型
import sqlite3, pandas as pd
from pathlib import Path
DB = Path(**file**).resolve().parents\[1] / "data/warehouse/retail.sqlite"
def incremental\_load(csv\_path: str) -> None:
con = sqlite3.connect(DB)
try:
cur = con.execute("SELECT IFNULL(MAX(order\_date),'0000-00-00') FROM sales")
last = cur.fetchone()\[0]
df = pd.read\_csv(csv\_path)
df = df\[df\["order\_date"] > last]
if not df.empty:
df.to\_sql("sales", con, if\_exists="append", index=False)
finally:
con.close()
最大日付/シーケンスで差分を挿入。重複はUNIQUE制約で防御します。
性能チューニング:PRAGMA/インデックス/バルク
PRAGMA journal_mode=WAL;
:同時読取/書込の体感向上PRAGMA synchronous=NORMAL;
:書込速度と安全性のバランス- インデックス:
WHERE/JOIN
で使う列(例:store, order_date
)。複合は選択性→範囲の順 - バルク挿入:
executemany
あるいはpandas.to_sql(chunksize=…)
- VACUUM:削除後にファイル縮小
品質保証:簡易データチェック&テスト
import pandas as pd, sqlite3
from pathlib import Path
DB = Path(**file**).resolve().parents\[1] / "data/warehouse/retail.sqlite"
def check\_sales() -> None:
con = sqlite3.connect(DB)
try:
df = pd.read\_sql\_query("SELECT qty, price FROM sales", con)
assert (df\["qty"] >= 0).all(), "qty に負値あり"
assert (df\["price"] >= 0).all(), "price に負値あり"
finally:
con.close()
スケジューリング(自動実行)の最短メモ
- Windows:タスクスケジューラで
python src/etl.py
を毎朝7:00 - macOS/Linux:
crontab -e
→0 7 * * * /usr/bin/python3 /path/src/etl.py
- 失敗時はログ/通知(メール/Slack)で安心運用
エラーハンドリング&ロールバック
import logging, sqlite3
from pathlib import Path
logging.basicConfig(level=logging.INFO)
DB = Path(**file**).resolve().parents\[1] / "data/warehouse/retail.sqlite"
def safe\_insert(rows: list\[tuple]) -> None:
con = sqlite3.connect(DB)
try:
with con:
con.executemany(
"INSERT INTO sales(order\_date,store,product,qty,price) VALUES(?,?,?,?,?)",
rows
)
except Exception as e:
logging.exception("insert failed: %s", e) # 自動でROLLBACK
finally:
con.close()
小さく始めて広げる(スケールの道筋)
- ファイル1つ→部門共有ならOneDrive/Driveに置いて共有(ロック注意)
- 行数が増えたら抽出→Parquetを併用 [内部リンク:pandas実践]
- 同じSQLをPostgreSQLへ移植(方言差:日付関数/IFNULL→COALESCEなど)
次の一歩:無料カウンセリングで設計レビュー
独学でも到達できますが、設計/クエリ/増分更新/配布の仕上げはレビューが最短です。6ヶ月ロードマップでポートフォリオまで作るなら、下記2校の無料カウンセリングから設計レビューを受けましょう。
TechAcademy データサイエンスコース(受講料:174,600円~ ※更に割引あり)

株式会社キカガク AI人材長期育成コース(受講料:237,600円~)

読者タイプ別の運用プラン
- 社会人(転職狙い):増分ETL→ウィンドウ→Excel配布の一連をポートフォリオ化。READMEに再現手順を明記。→ [内部リンク:ポートフォリオ完全ガイド]
- 副業(レポート納品):テンプレSQL+ExcelWriterで毎月自動。差分ロードで納期短縮。→ [内部リンク:データレポート納品の型]
- 主婦/夫(在宅):短時間×高頻度でCTE分割→小関数化→スケジューラ導入の3段階で拡張。
行動課題:ミニプロジェクト(提出推奨)
課題:「sales.csv
をSQLiteに取り込み、月次×店舗の売上/数量と前年比を report.xlsx
に出力。画像1枚(折れ線+注釈)を添付」
init_db()
でDB作成→load_csv_to_db()
で取り込みfetch_monthly()
で集計、Matplotlibで折れ線+注釈(キャンペーン開始等)- ExcelWriterで
monthly
タブを書き出し - READMEに再現手順/依存を書き、
reports/
をGitにコミット
レビュー観点チェックリスト(コピペ可)
- [ ] 主キー/UNIQUEで重複を防いでいる
- [ ] PRAGMA/INDEXで速度を確保している
- [ ] 取り込み前に型/欠損/範囲を検査している
- [ ] CTEでクエリを段階化し、可読性を確保
- [ ] 再現手順をREADMEに明記
付録A:よく使うSQLスニペット(SQLite)
-- Top-N per group
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY store ORDER BY sales DESC) rn
FROM monthly
) WHERE rn <= 3;
\-- IFNULL/COALESCE
SELECT COALESCE(area, 'Unknown') FROM stores;
\-- 日付操作(文字列→月)
SELECT strftime('%Y-%m', order\_date) AS ym FROM sales;
付録B:in-memory DBでのテスト
import sqlite3, pandas as pd
con = sqlite3.connect(":memory:")
con.executescript("CREATE TABLE t(a INT); INSERT INTO t VALUES(1),(2);")
print(pd.read_sql_query("SELECT COUNT(*) AS n FROM t", con))
con.close()
この記事から次に読むべきもの(内部リンク)
-
-
はじめてのSQL:SELECT/WHERE/GROUP BYを最短で理解【コピペOK】
データ分析・自動レポート・簡易アプリの土台はSQLです。Pythonだけで押し切るより、前処理の7割をDB側で完結させる方が速く・安定します。本記事は、未経験〜初学者が週10時間×2〜3週で、SELE ...
-
-
【実務で差がつく】pandas実践:欠損処理・結合・ウィンドウ関数・時系列・品質保証まで“読みやすく速い”型を習得
リード(結論)基礎を終えたら次は実務の現場で頻出する処理を“型”で覚える段階です。本記事は、pandas 2.x を前提に、欠損・外れ値・結合・ウィンドウ関数・時系列・カテゴリ処理・集計の自動化・大規 ...
-
-
【保存版】可視化入門:Matplotlib/Plotlyの使い分けと“伝わるグラフ設計”10ステップ
結論:可視化は「きれいに描く」ことではなく、意思決定を動かすための設計です。本稿では、未経験〜初学者が 週10時間×1〜2週 で、Matplotlib/Plotlyを軸に “伝わるグラフ”の設計と実装 ...
-
-
【保存版】データレポート納品の型:要件定義→ETL→検証→可視化→Excel/PDF→引き継ぎまで、失注しないワークフロー完全版
“いい分析”より“伝わる納品”。副業や実務で評価されるのは、意思決定に効く1枚と再現できるパッケージを期限通り出せること。 本記事は、未経験〜初学者が週10時間×2〜3週で、要件定義→データ受領→ET ...
-
-
【保存版】データ職のポートフォリオ完全ガイド|再現性・評価・LTまで
ポートフォリオって「作ったものの置き場」でしょ? いいえ。採用側が見たいのは「意思決定に効いた証拠」と「再現性」です。 本ガイドは、未経験〜初学者が週10時間×4〜6週で、テーマ選定→要件定義→データ ...
最近のコメント