論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
DML(データ操作言語)
Data Manipulation Language
データエンジニアリング

🔖 キーワード索引

DML(データ操作言語)Data Manipulation Languageデータエンジニアリング

本ページは DML(データ操作言語)(Data Manipulation Language)を多角的に解説します。 上のチップは、 検索・関連語の手がかりです。

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

DMLSELECTINSERTUPDATEDELETEMERGESQLトランザクションRDBMS

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

💡 30秒で分かる結論

🍰 まずはやさしく

データの中身を操作する道具です。

データの追加や変更をするために使います。

スマホアプリの情報を書き換えるような操作です。

安全に使うためのコツを学びましょう。

💡 DML 実践 Tips 20

  1. UPDATE/DELETE 前に必ず SELECT で確認
  2. BEGIN; ... ROLLBACK; の儀式を習慣化
  3. WHERE で複数条件は AND/OR の優先順位に注意 (括弧で明示)
  4. LIKE のワイルドカードは右側のみが高速
  5. JOIN は ON 句で条件指定 (WHERE は使わない)
  6. サブクエリより JOIN の方が高速なことが多い
  7. SELECT * は避け、 必要列を明示
  8. 大量データの INSERT はバルク (1000 行ずつ)
  9. EXPLAIN で実行計画を確認
  10. インデックスは過剰だと INSERT/UPDATE が遅くなる
  11. NULL は計算で消える (NULL + 1 = NULL)
  12. COUNT(*) と COUNT(列) は意味が違う (NULL の扱い)
  13. DISTINCT は遅い、 GROUP BY の方が速いことも
  14. HAVING は GROUP BY 後の WHERE
  15. UNION ALL は UNION より速い (重複排除なし)
  16. EXISTS は IN より速いことが多い
  17. VIEW で複雑クエリを抽象化
  18. マテリアライズドビューで集計を事前計算
  19. パーティションで巨大テーブルを分割
  20. 定期的に VACUUM / ANALYZE で統計情報更新

❓ よくある質問 (FAQ)

Q. SQL は時代遅れ?
A. 全く逆。 50 年経っても代替されず、 むしろ NoSQL ブームの後に再評価。 BigQuery など現代 DWH も SQL ベース。
Q. ORM と raw SQL どちらを使う?
A. Web アプリは ORM、 分析・バッチは raw SQL が多い。 パフォーマンスは raw SQL が有利。
Q. SQLite と PostgreSQL どちらを学ぶ?
A. 初心者は SQLite (設定不要)、 実務では PostgreSQL。 文法は 95% 共通。
Q. NoSQL を使うべき場面は?
A. スキーマが頻繁に変わる、 ペタバイト級、 グラフ構造、 全文検索など。 通常は RDB で十分。
Q. SELECT が遅いときの対処は?
A. ① EXPLAIN で実行計画確認 ② インデックス追加 ③ クエリ書き換え ④ パーティション

📍 文脈 — どこで使う概念か

🍰 まずはやさしく

データベースの中核となる言葉です。

データの検索や削除を行うために使います。

部活の名簿を管理する仕組みのようなものです。

データエンジニアリングの基本を学びます。

DML(Data Manipulation Language)は、 リレーショナルデータベース(RDB)でデータの追加・更新・削除・検索を行う SQL の中核です。 業務アプリケーションのバックエンド、 データウェアハウス、 BI ツールでも基盤として使われます。 SQL を知らずしてデータエンジニアリングは語れないと言える基本技術です。

🎨 直感で掴む — 具体例で理解する

🍰 まずはやさしく

データの読み書きをするための命令です。

テーブルの中身を自由に変えるために使います。

買い物リストに品物を書き足す感覚です。

他の命令との違いについて学びましょう。

SQL の命令は大きく 3 つに分類されます:

分類用途命令例
DML(操作)データの中身を扱うSELECT, INSERT, UPDATE, DELETE
DDL(定義)テーブル構造を作る・変えるCREATE, ALTER, DROP
DCL(制御)権限・トランザクションGRANT, REVOKE, COMMIT

本ページで扱う DML は 「データの読み書き」そのもの。 業務アプリで一番使う命令群です。

📐 定義

🍰 まずはやさしく

データを操作するための専用の言語です。

データの集合を計算して書き換えるために使います。

テストの点数一覧から特定の人の点数を探す操作です。

それぞれの命令の意味を詳しく学びます。

SELECT/INSERT/UPDATE/DELETE 等の操作言語。 関係代数の視点では、 各命令は集合演算として形式化される:

$$\text{SELECT}: \quad \pi_{\text{cols}}\!\left(\sigma_{\text{pred}}(R)\right)$$ $$\text{INSERT}: \quad R \leftarrow R \cup \{t\}$$ $$\text{UPDATE}: \quad R \leftarrow \bigl(R \setminus \sigma_p R\bigr) \cup f\!\left(\sigma_p R\right)$$ $$\text{DELETE}: \quad R \leftarrow R \setminus \sigma_p R$$

ここで $R$ はリレーション(テーブル)、 $\sigma_p$ は条件 $p$ による選択、 $\pi$ は射影、 $f$ は更新写像。 英語名 Data Manipulation Language、 カテゴリ:データエンジニアリング。

📐 関係代数の全演算 — 数式を言葉で読み解く

記号名称意味SQL 例
σ選択条件に合う行を抽出WHERE
π射影指定列を取り出すSELECT col
×直積全組み合わせCROSS JOIN
∪和2 つの結果を合体 (重複除去)UNION
−差片方にあって他方にないEXCEPT
∩共通両方にあるINTERSECT
⋈自然結合同名列で結合NATURAL JOIN
⋈θ条件結合条件 θ で結合JOIN ON
÷商「全てを満たす」を抽出複雑 (相関サブクエリ)
γ集約グループごとに集計GROUP BY
τ整列並び替えORDER BY

SQL は関係代数の 実用版。 純粋な集合論的扱いから、 NULL や重複の扱いが現実的な妥協を含む。

🔬 数式を言葉で読み解く

SELECT
データの取得(読み取り)。 「どの列を、 どの行から、 どんな条件で」を指定
INSERT
新しい行を追加
UPDATE
既存の行の値を変更。 WHERE を忘れると全行更新の事故
DELETE
行を削除。 同じく WHERE 必須
JOIN
複数テーブルを連結。 INNER, LEFT, RIGHT, FULL の 4 種
WHERE
絞り込み条件。 DML の安全装置

🔬 EXPLAIN で実行計画を読む

SQL の高速化は EXPLAIN で内部を見ることから。 PostgreSQL の例:

EXPLAIN ANALYZE SELECT * FROM ssdse_b WHERE A1101 > 5000000; Seq Scan on ssdse_b (cost=0.00..50.00 rows=20 width=100) Filter: (A1101 > 5000000) Rows Removed by Filter: 544 Planning Time: 0.05 ms Execution Time: 0.5 ms

「Seq Scan」は全行スキャン。 「Index Scan」「Bitmap Heap Scan」が出ればインデックスが効いている。 大規模データでは Seq Scan を Index Scan に変えるだけで 100 倍速くなることも。

👁 ビューとマテリアライズドビュー

種類動作用途
VIEW (通常)SELECT のエイリアス、 毎回実行クエリの再利用、 セキュリティ (列制限)
Materialized View結果を物理保存、 REFRESH で更新重い集計を事前計算
Temporary Tableセッション中だけ存在中間結果保存
CTE (WITH)クエリ内だけ有効段階的なクエリ構築

💾 ストレージエンジンと内部構造

エンジンDBMS特徴
InnoDBMySQL/MariaDBトランザクション対応、 標準
MyISAMMySQL (旧)速いがトランザクション無し
RocksDBMySQL/CockroachDBLSM-Tree、 書き込み高速
WiredTigerMongoDB圧縮、 トランザクション
SQLite ファイルベースSQLiteB-Tree、 シンプル
PostgreSQL HeapPostgreSQLMVCC、 高機能
カラム指向 (Parquet, ORC)BigQuery 等分析クエリで高速

☁ クラウド DB と DWH

サービス提供元特徴
Amazon RDSAWSMySQL/PostgreSQL マネージド
Amazon AuroraAWSRDS の高速版
Amazon RedshiftAWSカラム指向 DWH
Google Cloud SQLGCPOLTP マネージド
BigQueryGCPサーバーレス DWH
Azure SQL DatabaseAzureSQL Server マネージド
Snowflake独立マルチクラウド DWH
Databricks独立Lakehouse
PlanetScale独立サーバーレス MySQL
Supabase独立PostgreSQL ベース

📊 主要データ型

型用途注意
INTEGER / INT整数サイズ別: SMALLINT, BIGINT
REAL / FLOAT / DOUBLE浮動小数精度に注意 (金額には NUMERIC)
NUMERIC / DECIMAL固定小数金融計算に必須
VARCHAR(n)可変長文字列n は最大長
TEXT長文制限なし
BOOLEAN真偽値SQLite は 0/1 で代用
DATE日付YYYY-MM-DD
TIMESTAMP日時タイムゾーン考慮
JSON / JSONB半構造化PostgreSQL/MySQL 対応
UUID分散 ID分散システム用
BYTEA / BLOBバイナリ画像等
ARRAY配列PostgreSQL
GEOMETRY地理空間PostGIS 拡張

✅ DML ベストプラクティス 20

  1. UPDATE/DELETE 前に SELECT で確認
  2. BEGIN; ROLLBACK; の儀式を習慣化
  3. プレースホルダで SQL インジェクション防止
  4. SELECT * を避ける、 必要列を明示
  5. JOIN は ON で条件指定
  6. WHERE で関数を使わない (インデックス無効化)
  7. LIKE のワイルドカードは右側のみ
  8. NULL は IS NULL で比較
  9. 大量 INSERT はバルクで
  10. EXPLAIN で実行計画確認
  11. 適切なインデックス追加
  12. 過剰なインデックスは INSERT を遅くする
  13. 定期的な VACUUM/ANALYZE
  14. ストアドプロシージャより アプリ側でロジック
  15. マテリアライズドビューで重い集計を高速化
  16. パーティションで大規模テーブル分割
  17. ORM 使用時は N+1 問題に注意
  18. マイグレーション (Flyway, Alembic) で変更管理
  19. バックアップを定期的に
  20. 本番では権限を最小化 (DCL)

💭 考察問題

  1. 歴史的考察: なぜ SQL は 50 年経っても代替されないのか? Codd の関係モデルの何が革新的だったか?
  2. 技術的考察: NoSQL ブームが過ぎ、 NewSQL (SQL 互換の分散 DB) が台頭する理由
  3. 設計考察: 完全正規化 (3NF) と非正規化、 どちらが正しいか? シチュエーション別に
  4. パフォーマンス考察: SSDSE-B-2026 (564 行) を SQLite/pandas/PostgreSQL で処理する場合の速度比較
  5. 運用考察: 「マイグレーション失敗で本番停止」を防ぐためのプロセスは?
  6. セキュリティ考察: SQL インジェクション以外の DB セキュリティリスクは何があるか?
  7. 未来予測: 生成 AI と DML の関係 — 自然言語から SQL を生成する Text-to-SQL の未来
  8. 教育考察: 高校生にプログラミング教育する際、 SQL は何番目に教えるべきか?

🤖 LLM 時代の DML — Text-to-SQL

「日本語で質問するだけで SQL が自動生成される」時代が到来しています。 これは DML の未来を変える可能性があります。

サービス提供元特徴
ChatGPT Code InterpreterOpenAICSV を直接分析、 SQL も生成
Claude SonnetAnthropicSQL の高品質生成
Gemini CodeGoogleBigQuery 統合
Vanna.ai独立Text-to-SQL 特化 OSS
DataLine独立BI ツール統合
Defog SQLCoder独立SQL 専用 LLM

「東京都の 2023 年人口を教えて」と聞けば SQL が生成され、 DB から答えが返る。 ただし生成された SQL の正しさ検証は人間の責任。

🇯🇵 日本での DML 活用

日本の社会インフラの大半が SQL + DML で動いている。 「目に見えないが社会を支える技術」の代表例。

🎓 学習パス

  1. Week 1: SQLite インストール、 SSDSE-B-2026 を投入
  2. Week 2: SELECT の基本 (WHERE, ORDER BY, LIMIT)
  3. Week 3: GROUP BY と集計関数
  4. Week 4: JOIN を理解
  5. Week 5: UPDATE / DELETE / INSERT (トランザクションも)
  6. Week 6: サブクエリと CTE
  7. Week 7: Window 関数
  8. Week 8: インデックスと EXPLAIN
  9. Week 9: PostgreSQL に移行、 本番機能を学ぶ
  10. Week 10: ORM (SQLAlchemy) を学ぶ
  11. Week 11: パフォーマンスチューニング
  12. Week 12: 実プロジェクトで運用

🔗 学習リソース

🐼 pandas 操作と SQL 完全対応表

pandasSQL説明
df.head(10)SELECT * FROM t LIMIT 10先頭 10 行
df.tail(5)SELECT * FROM t ORDER BY id DESC LIMIT 5末尾
df.shapeSELECT COUNT(*) FROM t行数
df.columnsPRAGMA table_info(t)列名
df['col'].unique()SELECT DISTINCT col FROM tユニーク値
df['col'].value_counts()SELECT col, COUNT(*) GROUP BY col ORDER BY 2 DESC頻度集計
df.describe()SELECT MIN, MAX, AVG, ...基本統計
df.isnull().sum()SELECT SUM(CASE WHEN col IS NULL THEN 1 ELSE 0 END)欠損数
df.dropna()WHERE col IS NOT NULL欠損除外
df.fillna(0)COALESCE(col, 0)欠損補完
df.sort_values('x')ORDER BY x並び替え
df.groupby('y').agg(...)GROUP BY y集計
pd.merge(df1, df2)INNER JOIN結合
pd.concat([df1, df2])UNION ALL縦結合
df.pivot_table(...)条件付き集計 + PIVOTピボット
df.duplicated()GROUP BY col HAVING COUNT(*) > 1重複検出
df.apply(func)UDF (ユーザー定義関数)カスタム処理

🚫 アンチパターン集

📂 ケーススタディ集

ケース ① 銀行送金

A → B に 1 万円送金:

BEGIN; UPDATE account SET balance = balance - 10000 WHERE id = 'A'; UPDATE account SET balance = balance + 10000 WHERE id = 'B'; -- 両方成功なら COMMIT、 片方失敗なら ROLLBACK COMMIT;

ACID の Atomicity が必須。 片方だけ成功して片方失敗は許されない。

ケース ② 在庫管理

商品の在庫が複数あるとき、 同時に複数の注文が来たら:

BEGIN; SELECT stock FROM products WHERE id = 1 FOR UPDATE; -- 行ロック -- ロジック (注文可能か判定) UPDATE products SET stock = stock - 1 WHERE id = 1; COMMIT;

ケース ③ ユーザー集計

「最近 30 日でアクティブな県別ユーザー数」:

SELECT prefecture, COUNT(DISTINCT user_id) AS active_users FROM access_log WHERE accessed_at >= DATE('now', '-30 days') GROUP BY prefecture ORDER BY active_users DESC;

ケース ④ SSDSE × 補助データの分析

SSDSE-B-2026 と外部の経済指標を JOIN して相関分析:

SELECT s.Prefecture, s.A1101 AS 人口, e.GDP_per_capita FROM ssdse_b s JOIN economic e ON s.Prefecture = e.prefecture AND s.year = e.year WHERE s.year = 2023 ORDER BY e.GDP_per_capita DESC;

📦 マイグレーション — DB スキーマ変更管理

本番 DB のスキーマ変更を安全に行うのが マイグレーション。 ツール一覧:

ツール言語特徴
FlywayJavaシンプル、 SQL ベース
LiquibaseJavaXML/YAML、 高機能
AlembicPythonSQLAlchemy 統合
Django MigrationsPythonDjango 標準
Rails MigrationsRubyRails 標準
SequelizeNode.jsJavaScript
PrismaNode.jsモダン、 型安全
golang-migrateGoGo 製、 高速

マイグレーションは「up (適用)」と「down (取り消し)」のペア。 失敗時に即ロールバックできる。

💾 バックアップとリストア

方式特徴用途
論理バックアップSQL ダンプ (pg_dump, mysqldump)小〜中規模
物理バックアップデータファイル直接コピー大規模、 速い
増分バックアップ差分のみ保存毎日実行
ストリーミング (PostgreSQL)リアルタイムレプリケーションHA 構成
スナップショットクラウド機能AWS RDS, GCP Cloud SQL
WAL アーカイブトランザクションログ保存Point-in-Time Recovery

「バックアップを取っただけでリストア練習をしていない = バックアップなし」というのが業界の格言。 必ず復元テストを。

🔬 集合代数とトランザクション意味論 — 深掘り

DML (SELECT / INSERT / UPDATE / DELETE) は数学的には「関係代数」+「トランザクション意味論」で形式化される。 関係 R を集合と見なすと、 各演算は集合演算に対応する:

DML関係代数数式SSDSE-B での意味
SELECT射影 π + 選択 σπ_{cols}(σ_{pred}(R))「人口 100万以上の県の県名と人口を抽出」
INSERT和集合R ← R ∪ {t}「新しい年度の SSDSE 行を追加」
UPDATE差 + 和R ← (R \ σ_p R) ∪ f(σ_p R)「人口データを最新値に上書き」
DELETE差集合R ← R \ σ_p R「廃止された旧コード行を削除」

このコードでやること: SSDSE-B-2026 を SQLite に投入し、 SELECT で集約 → UPDATE で派生列を一括計算 → トランザクション内で BEGIN/COMMIT を使う一連の DML フローを示す。

📥 入力データ:

SSDSE-B-2026,Code,Prefecture,A1101(総人口・人),A1303(65歳以上人口・人)
2023,R01000,北海道,5092000,1681000
2023,R13000,東京都,14086000,3205000
2023,R47000,沖縄県,1468000,350000
... (47 行)
 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
import pandas as pd
import sqlite3

# 英字の項目コードを使うので、2 行目の日本語名を読み飛ばす
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()].copy()   # 最新年 (2023) の 47 県

conn = sqlite3.connect(':memory:')
# 必要列だけ投入
# 注意: SSDSE-B に面積 (B1101) の列は無いので、人口密度ではなく
#       収録されている 65 歳以上人口 (A1303) から高齢化率を計算する
df[['Prefecture', 'A1101', 'A1303']].to_sql('pref', conn, index=False)

cur = conn.cursor()
cur.execute('BEGIN')
# ALTER で派生列を追加し UPDATE で高齢化率を計算
cur.execute('ALTER TABLE pref ADD COLUMN aging_rate REAL')
cur.execute('UPDATE pref SET aging_rate = A1303 * 100.0 / A1101')  # %
cur.execute('COMMIT')

# 高齢化率 32% 以上の上位 5 件
rows = cur.execute(
    'SELECT Prefecture, ROUND(aging_rate, 1) FROM pref '
    'WHERE aging_rate > 32 ORDER BY aging_rate DESC LIMIT 5'
).fetchall()
for r in rows:
    print(r)

📤 実行例:

('秋田県', 39.1)
('高知県', 36.3)
('徳島県', 35.4)
('山口県', 35.4)
('青森県', 35.2)

💬 結果の読み方: BEGIN/COMMIT で囲んだ UPDATE は ACID の「Atomicity」を担保し、 失敗時は ROLLBACK で全て巻き戻る。 1 文の UPDATE で 47 行すべてに派生列 aging_rate (%) を一括計算でき、 続く SELECT (WHERE aging_rate > 32) で秋田 39.1%・高知 36.3%・徳島 35.4%・山口 35.4%・青森 35.2% と上位 5 県が即座に得られる。 徳島と山口は小数 1 桁に丸めると同じ 35.4 だが、 並べ替えは丸める前の値で行われている。 pandas でも df['aging_rate'] = df['A1303'] * 100 / df['A1101'] の 1 行で書けるが、 SQL の UPDATE はデータをデータベースに置いたまま宣言的に書き換えられ、 トランザクションで守られる点が DML の威力。

🖼 視覚で確認する:DML 結果を集計・可視化する

DML(INSERT/UPDATE/DELETE/SELECT)の結果は、 多くの場合「集計→分布→ランキング」という3段階で可視化されます。 SSDSE-B-2026 を例にした典型的な可視化を3点示します。

UPDATE で計算した 2023 年度 47 都道府県の高齢化率 aging_rate を SELECT したヒストグラム
図1: 上のコードと同じく UPDATE pref SET aging_rate = A1303 * 100.0 / A1101 で計算した 2023 年度 47 都道府県の高齢化率を SELECT aging_rate FROM pref で取り出したヒストグラム(ビン幅 1%)。 AVG は 31.6%、 最小は東京 22.8%、 最大は秋田 39.1%。 WHERE aging_rate > 32 に当たるのは青の 23 県で、 LIMIT 5 で見た上位 5 県はその右端にすぎない。 SELECT の結果を集計・ランキングする前に、 連続値カラムの分布を最初に把握する基本ツール。
UPDATE で計算した高齢化率を 7 地方別に並べた箱ひげ図。左端は全 47 件
図2: 同じ pref 表に ALTER TABLE pref ADD COLUMN region TEXT で地方の列を足し、 UPDATE ... SET region = CASE ... で 7 地方を入れてから SELECT region, aging_rate FROM pref で取り出した箱ひげ図。 全 47 件では箱が 30.0〜34.0% で、 1.5×IQR の外側の外れ値は東京都 22.8% と沖縄県 23.8%。 GROUP BY region の平均は四国 34.6% が最も高く関東 28.0% が最も低い。 地方ごとに見ると秋田県(39.1%)・宮城県(29.2%)・愛知県(25.7%)も外れ値になる。 UPDATE 結果や WHERE 条件で抽出したサブセットの代表値・散らばりを一目で確認できる。
ORDER BY aging_rate DESC LIMIT 10 で得た高齢化率上位 10 県の横棒グラフ
図3: SELECT Prefecture, aging_rate FROM pref ORDER BY aging_rate DESC LIMIT 10 の結果を横棒グラフにしたもの。 青の 5 県(秋田 39.06・高知 36.34・徳島 35.40・山口 35.36・青森 35.22)が上のコードの LIMIT 5 で返る行で、 6 位の山形 35.19 は青森と 0.03 ポイントしか違わない。 LIMIT の切れ目の前後の差が小さいかどうかも確かめてから順位を読む。

DML は数値を返すだけで完結せず、 続く可視化と組み合わせることで意思決定に直結する情報になります。

🧮 実値で計算してみる

具体的な DML 操作の例(都道府県データ):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
-- 1. SELECT(読み取り)
SELECT 都道府県名, 高齢化率, 死亡率
  FROM 統計表
 WHERE 高齢化率 > 30
 ORDER BY 高齢化率 DESC
 LIMIT 10;

-- 2. INSERT(追加)
INSERT INTO 統計表 (都道府県名, 高齢化率, 死亡率)
VALUES ('東京都', 23.4, 9.8);

-- 3. UPDATE(更新)— WHERE 必須
UPDATE 統計表
   SET 死亡率 = 10.5
 WHERE 都道府県名 = '東京都';

-- 4. DELETE(削除)— WHERE 必須
DELETE FROM 統計表
 WHERE 年 < 2010;

赤線:UPDATE と DELETE で WHERE を忘れると全行が一発で書き換えられる。 必ず先に SELECT で確認すること。

DML 操作後の行数変化を数値で確認する

初期テーブルに 5 都道府県が登録されているとする。高齢化率 (%) のベクトルは(SSDSE-B-2026 の 2023 年、65 歳以上人口 A1303 ÷ 総人口 A1101 × 100):

高齢化率 データ: [33.0, 22.8, 39.1, 35.0, 34.9](北海道・東京・秋田・岩手・島根 の順)

初期行数 $n_0 = 5$。 以下の DML を順に実行した場合の行数変化を手計算する。

Step 1 — INSERT 2 件: 神奈川・大阪を追加
ΔINSERT = [2] → 行数 = 5 + 2 = 7

Step 2 — DELETE WHERE 高齢化率 < 25: [22.8](東京都)→ 1 件削除
ΔDELETE = [1] → 行数 = 7 − 1 = 6

Step 3 — UPDATE 1 件: 秋田の死亡率を更新 (行数変化なし, Δ = 0)
行数 = 6 + 0 = 6

Step 4 — SELECT COUNT(*): 読み取り専用、 行数変化なし
最終行数 $n_{\text{final}} = n_0 + \sum\Delta_{\text{INSERT}} - \sum\Delta_{\text{DELETE}} = 5 + 2 - 1 = \mathbf{6}$

同じ計算を Python で再現する:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np

# 初期テーブル: 5 都道府県の高齢化率 (SSDSE-B-2026, 2023 年, A1303/A1101)
aging_rates = [33.0, 22.8, 39.1, 35.0, 34.9]
n0 = len(aging_rates)  # 5

# Step 1: INSERT 2 件
delta_insert = [2]
n1 = n0 + sum(delta_insert)  # 7

# Step 2: DELETE WHERE 高齢化率 < 25 → 東京都の 22.8 が 1 件マッチ
delta_delete = [1]
n2 = n1 - sum(delta_delete)  # 6

# Step 3: UPDATE は行数変化なし
n_final = n2  # 6

# 数式で直接計算
n_formula = n0 + sum(delta_insert) - sum(delta_delete)
print(f"手計算: {n_final}, 数式: {n_formula}, 一致: {np.allclose(n_final, n_formula)}")

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

手計算: 6, 数式: 6, 一致: True

💬 手計算 6 と Python 出力 6 で一致。 np.allclose が True を返したことで、 $n_{\text{final}} = 5 + 2 - 1 = 6$ が数式・手計算・実装の三者で同じ値であることが確認できる。

🧮 SSDSE-B-2026 を SQLite に投入 — 数式を言葉で読み解く

DML の真価は 実データで体感するのが一番。 SSDSE-B-2026 (47 都道府県 × 12 年 = 564 行) を SQLite に投入し、 様々な SELECT/UPDATE を試してみましょう。

関係代数の 3 つの基本演算 — 数式を言葉で読み解く

関係代数SQL での実現意味
選択 (Selection) σWHERE 句条件に合う「行」を選ぶ
射影 (Projection) πSELECT 列名必要な「列」だけ取り出す
結合 (Join) ⋈JOIN ON複数テーブルを連結
和 (Union) ∪UNION2 つのクエリ結果を合体
差 (Difference) −EXCEPT片方にあって他方にない
積 (Cartesian Product) ×CROSS JOIN全組み合わせ

例えば「東京都の 2023 年人口」を取るクエリ:

【関係代数表現】
$$\pi_{A1101}(\sigma_{Prefecture=東京都 \land 年=2023}(SSDSE))$$
SSDSE 表から「東京都の 2023 年」を選択 (σ) し、 人口列 (π) を射影
【SQL 表現】
SELECT A1101 FROM ssdse WHERE Prefecture = '東京都' AND year = 2023;
結果: 14086000

🐍 Python 実装

このコードでやること: SSDSE-B-2026.csv を SQLite に読み込み、 pd.read_sql で SELECT 文を実行して結果を DataFrame として取得する最小構成。 DML の基本フローをそのまま Python で確認できる。

📥 入力データ (SSDSE-B-2026 抜粋):

年度 地域コード 都道府県 総人口 65歳以上人口 ... 2023 R01000 北海道 5092000 1681000 ... 2023 R13000 東京都 14086000 3205000 ... 2023 R47000 沖縄県 1468000 350000 ... (最新年度 2023 年の 47 都道府県に絞り、高齢化率 = 65歳以上人口 ÷ 総人口 × 100 を列として追加)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# ── この抜粋で使うデータを用意します(SSDSE-B の 47 都道府県・最新年度)──
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1)
df = df[df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
df['年度'] = pd.to_numeric(df['年度'], errors='coerce')
df = df[df['年度'] == df['年度'].max()]
for _c in df.columns[3:]:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')
df['高齢化率'] = df['65歳以上人口'] / df['総人口'] * 100

# 見本でよく使われる仮の列名を、実データから作っておく
df['income'] = df['消費支出(二人以上の世帯)']
df['population'] = df['総人口']
_region = {'北海道': '北海道', '青森県': '東北', '岩手県': '東北', '宮城県': '東北',
           '秋田県': '東北', '山形県': '東北', '福島県': '東北', '茨城県': '関東',
           '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東',
           '東京都': '関東', '神奈川県': '関東'}
df['region'] = df['都道府県'].map(_region).fillna('その他')
df['地域'] = df['region']

import pandas as pd
import sqlite3

# SQLite で DML を実行
conn = sqlite3.connect('mydata.db')
df.to_sql('stat_table', conn, if_exists='replace', index=False)

# SELECT を pandas で
result = pd.read_sql('SELECT * FROM stat_table WHERE 高齢化率 > 30', conn)
print(f'{len(result)} 件')
print(result[['都道府県', '総人口', '高齢化率']].head())

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

35 件 都道府県 総人口 高齢化率 0 北海道 5092000 33.012569 1 青森県 1184000 35.219595 2 岩手県 1163000 34.995701 3 秋田県 914000 39.059081 4 山形県 1026000 35.185185

💬 結果の読み方: WHERE 高齢化率 > 30 に当てはまるのは 47 都道府県中 35 件で、 30% 以下は東京 (22.8%)・沖縄 (23.8%) など 12 都府県しかない。 ORDER BY を付けていないので先頭 5 行は北海道・青森・岩手・秋田・山形と投入順 (地域コード順) のままで、 秋田 39.1% が 4 行目に来る。 高い順に見たいなら ORDER BY 高齢化率 DESC を書く必要がある。

🐍 Python 実装 — pandas + SQLite で DML を体感

① SSDSE-B-2026 を SQLite に投入 (INSERT)

🎯 このコードでやること: SSDSE-B-2026.csv (564 行) を SQLite データベースに投入し、 SQL でクエリ可能な状態にする。 pandas の to_sql は内部で INSERT を実行。

📥 入力データ: SSDSE-B-2026.csv (47 都道府県 × 12 年 = 564 行 × 112 列)

 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

# CSV 読み込み (cp932)
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])

# 年列の名前を分かりやすく
df = df.rename(columns={'SSDSE-B-2026': 'year'})

# SQLite に投入 (INSERT を内部で実行)
conn = sqlite3.connect('ssdse.db')
df.to_sql('ssdse_b', conn, if_exists='replace', index=False)

# 確認
count = pd.read_sql('SELECT COUNT(*) AS n FROM ssdse_b', conn).iloc[0,0]
print(f'投入レコード数: {count}')

# テーブル構造確認
schema = pd.read_sql('PRAGMA table_info(ssdse_b)', conn)
print(f'カラム数: {len(schema)}')
print('先頭 5 カラム:', schema['name'].head().tolist())

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

投入レコード数: 564 カラム数: 112 先頭 5 カラム: ['year', 'Code', 'Prefecture', 'A1101', 'A110101']

💬 結果の読み方: 47 都道府県 × 12 年 = 564 行が正しく投入。 pandas の to_sql は INSERT 文を 1 行ずつ発行するのではなく、 内部でバルクインサート (executemany) を使い高速。

② SELECT で人口上位 10 都道府県を取得

🎯 このコードでやること: 2023 年の人口上位 10 都道府県を SELECT で取得。 WHERE + ORDER BY + LIMIT の基本パターン。

📥 入力データ: ssdse.db の ssdse_b テーブル (564 行)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import pandas as pd
import sqlite3

conn = sqlite3.connect('ssdse.db')
query = '''
SELECT Prefecture, A1101 AS 人口
  FROM ssdse_b
 WHERE year = 2023
 ORDER BY A1101 DESC
 LIMIT 10;
'''
result = pd.read_sql(query, conn)
print(result.to_string(index=False))

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

Prefecture 人口 東京都 14086000 神奈川県 9229000 大阪府 8763000 愛知県 7477000 埼玉県 7331000 千葉県 6257000 兵庫県 5370000 福岡県 5103000 北海道 5092000 静岡県 3555000

💬 結果の読み方: 上位 10 都道府県は東京・神奈川・大阪を含む大都市圏 + 北海道・福岡などの地方中核。 SELECT は DML 中で最も使われる命令で、 全 DML 命令の 95% 以上を占める (実務感覚)。

③ GROUP BY で年代別集計

🎯 このコードでやること: 47 都道府県の合計人口を年代ごとに集計し、 日本の人口推移を確認する。 GROUP BY + SUM の基本パターン。

📥 入力データ: ssdse.db の ssdse_b テーブル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd
import sqlite3

conn = sqlite3.connect('ssdse.db')
query = '''
SELECT year,
       SUM(A1101) AS 全国人口合計,
       AVG(A1101) AS 平均都道府県人口,
       COUNT(*) AS 都道府県数
  FROM ssdse_b
 GROUP BY year
 ORDER BY year;
'''
result = pd.read_sql(query, conn)
print(result.to_string(index=False))

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

year 全国人口合計 平均都道府県人口 都道府県数 2012 127589000 2.714660e+06 47 2013 127414000 2.710936e+06 47 ... 2022 124946000 2.658426e+06 47 2023 124353000 2.645809e+06 47 (2014〜2021 年の 8 行は省略)

💬 結果の読み方: 全 47 都道府県を合計すると 2012 年 1 億 2,758 万 9 千人 → 2023 年 1 億 2,435 万 3 千人で、 11 年で約 324 万人 (2.5%) 減った。 AVG は SUM を 47 で割っただけなので、 平均都道府県人口 264 万人は東京 1,409 万人に引っ張られた値で、 中央値 155 万人よりずっと大きい。 GROUP BY は実務で最も使われる DML 構造の 1 つ。 「集計の単位 + 集計関数 (SUM/AVG/COUNT/MAX/MIN)」がセットで覚えるのがコツ。

④ UPDATE と トランザクション

🎯 このコードでやること: 東京都の 2023 年人口を仮に更新する例。 UPDATE 前に SELECT で確認 → BEGIN TRANSACTION → 確認 → COMMIT or ROLLBACK の安全な手順を体験。

📥 入力データ: ssdse.db

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import sqlite3

conn = sqlite3.connect('ssdse.db')
cur = conn.cursor()

# 1. まず確認 (絶対重要)
cur.execute("SELECT A1101 FROM ssdse_b WHERE Prefecture='東京都' AND year=2023")
before = cur.fetchone()[0]
print(f'更新前: 東京都 2023 年人口 = {before}')

# 2. トランザクション開始
cur.execute('BEGIN TRANSACTION')

# 3. UPDATE
new_value = 14_100_000  # 仮の更新値
cur.execute(
    "UPDATE ssdse_b SET A1101 = ? WHERE Prefecture='東京都' AND year=2023",
    (new_value,)
)
print(f'影響行数: {cur.rowcount}')  # 必ず 1 行のはず

# 4. 確認
cur.execute("SELECT A1101 FROM ssdse_b WHERE Prefecture='東京都' AND year=2023")
print(f'(トランザクション中) 値 = {cur.fetchone()[0]}')

# 5. 元に戻す (実データを壊さないため ROLLBACK)
conn.rollback()
print('ROLLBACK 実行')

cur.execute("SELECT A1101 FROM ssdse_b WHERE Prefecture='東京都' AND year=2023")
print(f'最終値: {cur.fetchone()[0]} (元に戻った)')

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

更新前: 東京都 2023 年人口 = 14086000 影響行数: 1 (トランザクション中) 値 = 14100000 ROLLBACK 実行 最終値: 14086000 (元に戻った)

💬 結果の読み方: 影響行数が想定 (1) と一致することを確認してから COMMIT、 違えば ROLLBACK。 この「BEGIN → 確認 → COMMIT/ROLLBACK」の儀式が本番運用での事故防止に必須。 UPDATE WHERE 条件ミスで全行更新する事故は実務で頻発。

⑤ JOIN で複数テーブル連結

🎯 このコードでやること: SSDSE-B-2026 と都道府県マスタを JOIN して、 地方区分 (東北、 関東、 ...) 別に人口を集計する。 INNER JOIN の典型例。

📥 入力データ: ssdse.db + 自作の都道府県地方マスタ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import pandas as pd
import sqlite3

conn = sqlite3.connect('ssdse.db')

# 都道府県と地方 (8 区分) の対応表を 47 都道府県ぶん作成
regions = {
    '北海道': ['北海道'],
    '東北': ['青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県'],
    '関東': ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県'],
    '中部': ['新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県',
             '岐阜県', '静岡県', '愛知県'],
    '近畿': ['三重県', '滋賀県', '京都府', '大阪府', '兵庫県', '奈良県', '和歌山県'],
    '中国': ['鳥取県', '島根県', '岡山県', '広島県', '山口県'],
    '四国': ['徳島県', '香川県', '愛媛県', '高知県'],
    '九州・沖縄': ['福岡県', '佐賀県', '長崎県', '熊本県', '大分県', '宮崎県',
                 '鹿児島県', '沖縄県'],
}
region_map = pd.DataFrame([{'Prefecture': p, 'Region': r}
                           for r, prefs in regions.items() for p in prefs])
print(f'対応表: {len(region_map)} 都道府県')
region_map.to_sql('prefecture_region', conn, if_exists='replace', index=False)

# JOIN
query = '''
SELECT r.Region AS 地方,
       COUNT(*) AS 県数,
       SUM(s.A1101) AS 人口合計
  FROM ssdse_b s
  INNER JOIN prefecture_region r ON s.Prefecture = r.Prefecture
 WHERE s.year = 2023
 GROUP BY r.Region
 ORDER BY 人口合計 DESC;
'''
result = pd.read_sql(query, conn)
print(result.to_string(index=False))
print(f'合計: {result["人口合計"].sum()}')

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

対応表: 47 都道府県 地方 県数 人口合計 関東 7 43527000 近畿 7 21990000 中部 9 20749000 九州・沖縄 8 14029000 東北 6 8318000 中国 5 7070000 北海道 1 5092000 四国 4 3578000 合計: 124353000

💬 結果の読み方: 8 地方の人口合計を足すと 1 億 2,435 万 3 千人で、 2023 年の 47 都道府県の合計と一致するので、 対応表に漏れも重複もない。 関東 7 都県だけで 4,352 万 7 千人と全国の 35% を占め、 最少の四国 4 県 (357 万 8 千人) の 12 倍になる。 INNER JOIN は両テーブルにある行だけを残すので、 対応表から県が 1 つ抜けているとその県の人口は黙って合計から消える。 合計が全国値と合うかを必ず確かめる。

🔒 トランザクションと ACID

DML を本番運用するうえで欠かせないのが トランザクションと ACID 特性。 数式を言葉で読み解くと、 これは「データベース操作の安全保証ルール」です。

特性意味例
Atomicity (原子性)全て成功か全て失敗銀行送金: 引き出し成功・入金失敗は許されない
Consistency (一貫性)整合性ルールを常に満たす合計残高がトランザクション前後で同じ
Isolation (分離性)並行実行でも独立2 人同時に更新しても結果は順次実行と同じ
Durability (永続性)コミット後は障害でも残る停電してもデータが消えない

SQL の BEGIN; ...; COMMIT; でトランザクション。 失敗したら ROLLBACK;。 PostgreSQL や Oracle は ACID を厳密に守るが、 NoSQL では緩い (BASE 原則) ことが多い。

🔐 分離レベル (Isolation Level)

並行トランザクションでどの程度厳密に独立を保つか。 厳しいほど安全だが遅い。

レベル許される現象使い所
READ UNCOMMITTEDDirty Read, Non-repeatable, Phantomほぼ使わない
READ COMMITTEDNon-repeatable, PhantomPostgreSQL デフォルト
REPEATABLE READPhantom のみMySQL デフォルト
SERIALIZABLEなし (最も厳密)金融など高信頼が必要な場面

SSDSE のような分析用途では READ COMMITTED で十分。 在庫管理や金融はより厳格な REPEATABLE READ や SERIALIZABLE が必要。

🔓 ロックの種類

種類取得契機影響
共有ロック (S)SELECT (一部 DBMS)他者の SELECT は OK、 UPDATE 不可
排他ロック (X)UPDATE, DELETE, INSERT他者は何もできない
行ロックWHERE で 1 行特定他行は無影響
テーブルロックALTER TABLE 等テーブル全体停止
デッドロック循環的ロックDBMS が片方を強制終了

ロック競合は実務で頻発するパフォーマンス問題。 「短いトランザクション」「同じ順序で複数行ロック」が回避のコツ。

🚨 SQL インジェクション — 必ず防ぐ

DML を実行するアプリで最も多い脆弱性が SQL インジェクション。 ユーザー入力を文字列連結で SQL に埋め込むと、 攻撃者に任意の SQL を実行されます。

[NG] 危険な例: query = f"SELECT * FROM users WHERE name = '{user_input}'" → user_input が "'; DROP TABLE users; --" だと全テーブル削除! [OK] プレースホルダ使用: cursor.execute("SELECT * FROM users WHERE name = ?", (user_input,)) → 値は別途バインドされ、 安全

Python の sqlite3, psycopg2, mysql.connector はすべてプレースホルダ対応。 ? や %s で書き、 値は別引数で渡す。 ORM (SQLAlchemy 等) を使えば自動的に安全。

🐍 追加 Python 実装

⑥ DELETE のサンプル (BEGIN/ROLLBACK で安全に)

🎯 このコードでやること: 2012 年のレコード (47 件) を DELETE する例を、 必ず ROLLBACK して実データを保護しながら体験する。 影響行数の確認が安全運用の鍵。

📥 入力データ: ssdse.db (564 行)

 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 sqlite3

conn = sqlite3.connect('ssdse.db')
cur = conn.cursor()

# 削除前の件数確認
cur.execute('SELECT COUNT(*) FROM ssdse_b')
before = cur.fetchone()[0]
print(f'削除前: {before} 行')

# トランザクション開始
cur.execute('BEGIN')

# DELETE
cur.execute('DELETE FROM ssdse_b WHERE year = 2012')
print(f'影響行数: {cur.rowcount}')

# 削除後の件数
cur.execute('SELECT COUNT(*) FROM ssdse_b')
print(f'(トランザクション中) {cur.fetchone()[0]} 行')

# 実データを壊さないため ROLLBACK
conn.rollback()
print('ROLLBACK 実行')

# 元に戻ったか確認
cur.execute('SELECT COUNT(*) FROM ssdse_b')
print(f'最終: {cur.fetchone()[0]} 行 (元通り)')

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

削除前: 564 行 影響行数: 47 (トランザクション中) 517 行 ROLLBACK 実行 最終: 564 行 (元通り)

💬 結果の読み方: 影響行数が想定 (47 = 都道府県数 × 1 年) と一致することを確認。 もし 517 とか想定外なら ROLLBACK で守る。 これが本番運用の鉄則。

⑦ Window 関数で前年比を計算

🎯 このコードでやること: 各都道府県の人口について、 LAG 関数で前年の値を取得し、 増減率を計算する。 SQL の Window 関数はピボットなしで時系列比較ができる強力機能。

📥 入力データ: ssdse.db

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import pandas as pd
import sqlite3

conn = sqlite3.connect('ssdse.db')
query = '''
SELECT Prefecture, year, A1101 AS pop,
       LAG(A1101) OVER (PARTITION BY Prefecture ORDER BY year) AS prev_pop,
       A1101 - LAG(A1101) OVER (PARTITION BY Prefecture ORDER BY year) AS diff
  FROM ssdse_b
 WHERE Prefecture IN ('東京都', '鳥取県', '沖縄県')
 ORDER BY Prefecture, year;
'''
result = pd.read_sql(query, conn)
print(result.to_string(index=False))

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

Prefecture year pop prev_pop diff 東京都 2012 13234000 NaN NaN 東京都 2013 13307000 13234000.0 73000.0 東京都 2014 13399000 13307000.0 92000.0 東京都 2015 13515271 13399000.0 116271.0 東京都 2016 13646000 13515271.0 130729.0 東京都 2017 13768000 13646000.0 122000.0 東京都 2018 13887000 13768000.0 119000.0 東京都 2019 14007000 13887000.0 120000.0 東京都 2020 14047594 14007000.0 40594.0 東京都 2021 14010000 14047594.0 -37594.0 東京都 2022 14038000 14010000.0 28000.0 東京都 2023 14086000 14038000.0 48000.0 沖縄県 2012 1411000 NaN NaN 沖縄県 2013 1419000 1411000.0 8000.0 沖縄県 2014 1426000 1419000.0 7000.0 沖縄県 2015 1433566 1426000.0 7566.0 沖縄県 2016 1442000 1433566.0 8434.0 沖縄県 2017 1448000 1442000.0 6000.0 沖縄県 2018 1454000 1448000.0 6000.0 沖縄県 2019 1462000 1454000.0 8000.0 沖縄県 2020 1467480 1462000.0 5480.0 沖縄県 2021 1468000 1467480.0 520.0 沖縄県 2022 1468000 1468000.0 0.0 沖縄県 2023 1468000 1468000.0 0.0 鳥取県 2012 583000 NaN NaN 鳥取県 2013 580000 583000.0 -3000.0 鳥取県 2014 577000 580000.0 -3000.0 鳥取県 2015 573441 577000.0 -3559.0 鳥取県 2016 570000 573441.0 -3441.0 鳥取県 2017 566000 570000.0 -4000.0 鳥取県 2018 562000 566000.0 -4000.0 鳥取県 2019 557000 562000.0 -5000.0 鳥取県 2020 553407 557000.0 -3593.0 鳥取県 2021 549000 553407.0 -4407.0 鳥取県 2022 544000 549000.0 -5000.0 鳥取県 2023 537000 544000.0 -7000.0

💬 結果の読み方: 2012→2023 年で東京は 1,323 万 4 千人 → 1,408 万 6 千人と約 85 万人増、 鳥取は 58 万 3 千人 → 53 万 7 千人と約 4 万 6 千人減、 沖縄は約 5 万 7 千人増だが 2022・2023 年の diff は 0 で増加が止まっている。 東京も 2021 年だけ −37,594 と減っている。 各県の初年度 2012 は前の行が無いので prev_pop・diff が NaN になり、 PARTITION BY があるので沖縄の 2012 年が東京の 2023 年と比べられることはない。

🐍 追加 Python — 47 都道府県の人口ランキング

⑧ Window 関数で順位付け

🎯 このコードでやること: 2023 年の 47 都道府県人口に ROW_NUMBER と RANK でランキングを付け、 同順位の有無を確認する。 数式を言葉で読み解くと「順序統計量を SQL で計算」する例。

📥 入力データ: ssdse.db (47 件 × 2023 年)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import pandas as pd
import sqlite3

conn = sqlite3.connect('ssdse.db')
query = '''
SELECT Prefecture, A1101 AS 人口,
       ROW_NUMBER() OVER (ORDER BY A1101 DESC) AS 行番号,
       RANK()       OVER (ORDER BY A1101 DESC) AS 順位,
       DENSE_RANK() OVER (ORDER BY A1101 DESC) AS 密順位,
       NTILE(4)     OVER (ORDER BY A1101)      AS 四分位
  FROM ssdse_b
 WHERE year = 2023
 ORDER BY A1101 DESC
 LIMIT 10;
'''
result = pd.read_sql(query, conn)
print(result.to_string(index=False))

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

Prefecture 人口 行番号 順位 密順位 四分位 東京都 14086000 1 1 1 4 神奈川県 9229000 2 2 2 4 大阪府 8763000 3 3 3 4 愛知県 7477000 4 4 4 4 埼玉県 7331000 5 5 5 4 千葉県 6257000 6 6 6 4 兵庫県 5370000 7 7 7 4 福岡県 5103000 8 8 8 4 北海道 5092000 9 9 9 4 静岡県 3555000 10 10 10 4

💬 結果の読み方: 47 都道府県の人口は全て異なるので、 ROW_NUMBER / RANK / DENSE_RANK が一致。 四分位 (NTILE(4)) は 47 行を 12・12・12・11 行に分け、 ORDER BY A1101 の昇順で数えるので最後の Q4 は 11 行で、 人口上位 11 都道府県 (11 位は茨城県) が Q4 になる。 Window 関数は SQL:2003 で標準化された比較的新しい機能だが、 現代分析には必須。

⑨ UPSERT パターン (INSERT or UPDATE)

🎯 このコードでやること: 「都道府県データが既存ならUPDATE、 なければINSERT」という UPSERT 操作を SQLite で実装する。 実務でよく使われるパターン。

📥 入力データ: 新規追加したい都道府県データのサンプル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import sqlite3

conn = sqlite3.connect('ssdse.db')
cur = conn.cursor()

# 仮想的な「県人口集計」テーブルを作成 (主キー: Prefecture)
cur.execute('''
CREATE TABLE IF NOT EXISTS pref_summary (
  Prefecture TEXT PRIMARY KEY,
  total_pop INTEGER,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')

# UPSERT (SQLite 3.24+ 構文)
data = [
    ('東京都', 14086000),
    ('鳥取県', 537000),
    ('沖縄県', 1468000),
]
cur.executemany('''
INSERT INTO pref_summary (Prefecture, total_pop) VALUES (?, ?)
ON CONFLICT(Prefecture) DO UPDATE SET
  total_pop = excluded.total_pop,
  updated_at = CURRENT_TIMESTAMP
''', data)
conn.commit()

# 確認
cur.execute('SELECT * FROM pref_summary')
for row in cur.fetchall():
    print(row)

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

('東京都', 14086000, '2026-05-24 02:30:00') ('鳥取県', 537000, '2026-05-24 02:30:00') ('沖縄県', 1468000, '2026-05-24 02:30:00') → 2 回実行すると、 updated_at だけ新しくなる (UPDATE が走った証拠)

💬 結果の読み方: UPSERT は「データの取り込みを冪等にする」便利パターン。 同じデータを何度実行しても結果が同じになるので、 ETL バッチで多用される。 PostgreSQL は ON CONFLICT、 MySQL は ON DUPLICATE KEY UPDATE。

⚠️ よくある落とし穴

❌ WHERE 句忘れ
UPDATE 表 SET 列 = 値 だけだと全行が書き換わる。 本番では BEGIN; UPDATE …; -- 確認 ROLLBACK; or COMMIT; の儀式を。
❌ LIKE のワイルドカード
LIKE '%abc%' は両端ワイルドカードで遅い。 インデックスが効かない。
❌ NULL の扱い
= NULL は常に偽。 必ず IS NULL を使う。
❌ 暗黙の型変換
WHERE 文字列カラム = 123 のような比較は予期せぬ結果を生む。
❌ SQL インジェクション
ユーザー入力を文字列連結で SQL に埋め込むと、 任意 SQL を実行されかねない。 必ず プレースホルダを使う。

⚠️ WHERE 句の落とし穴

状況NG 例正しい書き方
NULL 比較WHERE x = NULLWHERE x IS NULL
大小文字WHERE name = 'Tokyo'WHERE LOWER(name) = 'tokyo'
暗黙の型変換WHERE id = '123' (id が int)WHERE id = 123
否定の NOT INNULL を含むサブクエリで予期せぬ結果NOT EXISTS に書き換え
LIKE のワイルドカードLIKE '%abc%' (両端) でインデックス無効全文検索エンジン (FTS) 検討
関数適用WHERE YEAR(date) = 2023WHERE date BETWEEN '2023-01-01' AND '2023-12-31'

⚡ DML パフォーマンス・チューニング

📇 インデックスの種類

種類仕組み用途
B-Tree (バランス木)標準的なソート済み木等価・範囲検索の万能型
Hashハッシュ値で位置特定等価検索のみ、 範囲不可
GIN (Generalized Inverted)転置インデックス全文検索、 JSONB
GIST幾何データ向け地理座標、 範囲型
BRIN (Block Range)ブロックレベル巨大時系列データ
部分インデックスWHERE 条件付き使う部分だけ
関数インデックス関数結果にインデックスLOWER(name) 等
複合インデックス複数列でソートWHERE で複数条件

🏢 主要 RDBMS 比較

DBMS長所短所用途
SQLiteファイル 1 個、 設定不要同時書き込み弱い個人開発、 教育
PostgreSQL機能豊富、 SQL 標準準拠設定やや複雑本格運用、 分析
MySQL速い、 Web で定番SQL 標準準拠ゆるいWeb アプリ
MariaDBMySQL 互換、 オープン同上MySQL 代替
Oracleエンタープライズ機能高額大企業
SQL ServerWindows 統合非 Windows で不便Microsoft 系
DuckDB分析特化、 高速OLTP 不向きデータ分析
BigQueryクラウド、 ペタバイト級有料、 ベンダーロック大規模分析
Snowflakeマルチクラウド DWH有料企業分析基盤
RedshiftAWS 統合、 DWH運用コストAWS ユーザー

🪟 Window 関数 — 現代 DML の核心

「グループごとに集計しつつ、 行も個別に保持したい」というニーズに応えるのが Window 関数。 SQL:2003 以降標準。

関数用途例
ROW_NUMBER()連番都道府県人口ランキング
RANK()同順位許容ランク同人口は同順位、 次は飛ぶ
DENSE_RANK()同順位許容、 連続同上だが順位は連続
LAG(x, n)n 行前の値前年比計算
LEAD(x, n)n 行後の値次年予測
SUM() OVER (...)累積和累積人口
AVG() OVER (...)移動平均5 年移動平均
NTILE(n)n 分割四分位

例: 「都道府県別の前年比人口増減」:

SELECT Prefecture, year, A1101, A1101 - LAG(A1101) OVER (PARTITION BY Prefecture ORDER BY year) AS 前年差 FROM ssdse_b WHERE Prefecture = '東京都' ORDER BY year;

⚠️ DML 落とし穴コレクション

🔄 ORM — Python から DML を扱う高レベル方法

ORM特徴用途
SQLAlchemyPython 標準、 柔軟幅広く
Django ORMDjango 統合Web アプリ
Peewee軽量小規模
SQLModelFastAPI 統合、 型ヒントモダン API
Tortoise ORM非同期対応async/await
raw SQLORM 不使用パフォーマンス重視

ORM は SQL を書かなくて済むが、 N+1 問題 (1 件取得ごとに 1 クエリ発行) などの落とし穴あり。 SSDSE 程度の分析なら pandas + raw SQL の方が直感的。

🐼 pandas vs SQL — どちらを使うか

操作pandasSQL
SELECTdf[['col1', 'col2']]SELECT col1, col2 FROM t
WHEREdf[df.x > 100]WHERE x > 100
ORDER BYdf.sort_values('x')ORDER BY x
GROUP BYdf.groupby('y').sum()GROUP BY y
JOINpd.merge(df1, df2, on='id')JOIN ON id
UPDATEdf.loc[df.x==1, 'y'] = 2UPDATE t SET y=2 WHERE x=1
INSERTdf.append(new_row)INSERT INTO t VALUES
DELETEdf.drop(idx)DELETE FROM t WHERE

使い分け: メモリに収まる (< 数 GB) なら pandas、 大規模なら SQL。 SSDSE-B-2026 (564 行) は完全に pandas 範囲。

⚠️ 条件・限界・誤解回避(DML: データ操作言語)

DML (Data Manipulation Language) は SELECT/INSERT/UPDATE/DELETE/MERGE など、 リレーショナル DB のデータ自体を操作する SQL のサブセットです。 強力で生産性が高い反面、 トランザクション管理・性能設計・データ整合性の理解が不十分だと「数百万行を誤って更新」「本番環境のロックで業務停止」といった致命的事故を起こします。 SSDSE-B-2026 を題材に、 適用条件・限界・典型的な誤解を整理します。

適用条件

  1. スキーマと制約の理解: DML は DDL で定義された型・制約 (NOT NULL, UNIQUE, FOREIGN KEY, CHECK) を遵守する形でしか操作できません。 SSDSE-B-2026 を都道府県マスタと結合するなら、 prefecture_code を主キーに正規化して FOREIGN KEY を張るのが王道。 制約に違反する INSERT/UPDATE は失敗し、 トランザクション全体がロールバックされます。
  2. トランザクション境界の明示: 複数行を更新するときは BEGIN; ... COMMIT; で境界を明示。 暗黙コミット (autocommit) のままだと、 途中で失敗しても部分反映されて整合性が壊れます。 ACID 特性 (Atomicity, Consistency, Isolation, Durability) を意識した運用が前提です。
  3. WHERE 句の必須性: UPDATE/DELETE は WHERE を省略すると全行が対象になります。 SSDSE のように 47 行しかないテーブルでも、 本番では数億行になることを想定し、 必ず WHERE + LIMIT + 事前 SELECT 確認の三点セットを徹底。
  4. 適切なインデックス: WHERE 列・JOIN 列にインデックスがないとフルテーブルスキャンになり、 ロック範囲も広がります。 EXPLAIN ANALYZE で実行計画を確認し、 想定通りにインデックスが使われているかを検証。
  5. 分離レベルの選択: READ COMMITTED (デフォルト多数)・REPEATABLE READ・SERIALIZABLE の違いを理解し、 業務要件に合わせて設定。 SSDSE のような分析専用 DB なら READ COMMITTED で十分ですが、 在庫・金融なら SERIALIZABLE 相当が必要なことも。
  6. 権限管理 (GRANT/REVOKE): アプリ用ユーザーには SELECT/INSERT/UPDATE/DELETE のみ付与し、 DDL や TRUNCATE は禁止。 分析者には読み取り専用ユーザーを用意して本番改変を防ぐ。

限界

  1. 大量更新のロックと性能: 1 トランザクションで数千万行を UPDATE するとロックエスカレーション・WAL 肥大・レプリカ遅延を引き起こします。 バッチ更新は LIMIT 10000 単位で分割し、 適度にコミットするのが定石。
  2. RDBMS 方言の違い: 標準 SQL を中心にしても、 PostgreSQL の RETURNING、 MySQL の INSERT ... ON DUPLICATE KEY UPDATE、 SQL Server の MERGE、 BigQuery の MERGE INTO など差異があります。 移植性を考えるなら共通部分にとどめる工夫が必要。
  3. NULL 比較の罠: WHERE x = NULL は常に NULL (=偽) になり、 期待した行が返りません。 WHERE x IS NULL を使う必要があります。 集計関数も NULL の扱いが異なる (COUNT(*) vs COUNT(col))。
  4. 監査・履歴の欠如: DML は「現状」を更新するだけで、 過去の状態を保存しません。 監査要件があるなら、 トリガで履歴テーブルにコピーする・CDC (Change Data Capture) を導入する・SCD type 2 で設計する等の対策が必要。
  5. 並行更新の競合: 同時に複数セッションが同じ行を更新するとデッドロックが発生。 アプリ側でリトライ機構を実装し、 再試行可能なトランザクション設計にする。

誤解回避

  1. 「TRUNCATE は DELETE の高速版」は誤り: TRUNCATE は DDL に分類され、 ロールバックできない実装が多く、 トリガが発火しません。 監査要件のある環境では使えない場合があります。
  2. 「DELETE すれば容量が空く」は誤り: PostgreSQL は DELETE 直後は物理空間を解放せず、 VACUUM (またはオートバキューム) が必要です。 InnoDB も同様で OPTIMIZE TABLE が要ります。
  3. 「サブクエリより JOIN が常に速い」は誤り: 最近のオプティマイザはサブクエリを JOIN に書き換えます。 むしろ EXISTS や IN の方が意図が明確で読みやすいケースが多い。
  4. 「ORDER BY を入れれば順序が保証される」は誤り: INSERT INTO ... SELECT ... ORDER BY は宛先テーブルの並び順を保証しません。 ORDER BY は SELECT 結果の順序付けにのみ有効。
  5. 「インデックスを増やせば速くなる」は誤り: SELECT は速くなりますが、 INSERT/UPDATE/DELETE は遅くなります。 書き込み頻度の高いテーブルではインデックスを最小限に。
  6. 「LIMIT で安全」は誤り: UPDATE ... LIMIT 100 は対象がランダム抽出される可能性があり、 確定性がありません。 ORDER BY と組み合わせ、 主キーを基準に処理する。

典型ワークフロー

  1. 要件整理: SSDSE-B-2026 を題材に「東京を除く 46 道府県で 65 歳以上人口を集計」のような要件を SQL に落とす前に、 自然言語で書き出す。
  2. SELECT で結果確認: SELECT prefecture, elderly_pop FROM ssdse WHERE prefecture <> '東京'; で結果セットを確認。 想定行数・想定値域を目視。
  3. トランザクション開始: BEGIN; でトランザクション開始。
  4. 更新実行: UPDATE ssdse SET flagged = TRUE WHERE prefecture <> '東京' AND elderly_pop > 1000000;。 影響行数を EXPLAIN や RETURNING で確認。
  5. 検証: SELECT COUNT(*) FROM ssdse WHERE flagged = TRUE; で期待数と一致するかチェック。
  6. コミットまたはロールバック: 期待通りなら COMMIT;、 想定外なら ROLLBACK;。
  7. 監査ログ確認: pg_stat_statements / slow query log / 履歴テーブルで実行履歴を残す。
  8. 性能分析: EXPLAIN (ANALYZE, BUFFERS) で実行計画とコストを確認し、 必要ならインデックス追加。

ケーススタディ

DML は「データを動かす最後のキー操作」であると同時に「事故が一番起きる場所」です。 SSDSE-B-2026 のような小規模データで「SELECT で確認 → BEGIN → 更新 → 検証 → COMMIT」の規律を体に叩き込んでから本番に臨みましょう。

運用 Tips: 安全に DML を回すための実務知見

  1. 本番更新前のドライラン: 更新前に SELECT ... FOR UPDATE + ROLLBACK で「影響範囲だけ事前確認」する手法。 PostgreSQL なら EXPLAIN UPDATE ... や BEGIN; UPDATE ...; SELECT * FROM updated_table; ROLLBACK; を組み合わせて、 副作用なしで挙動を観察できます。
  2. WHERE 句のセーフガード: 重要テーブルの UPDATE/DELETE 文には必ず主キー条件か日付範囲を含め、 単独の UPDATE t SET col=val は禁止する社内ルールを敷きます。 ORM (Django, SQLAlchemy) でも .all().delete() のような書き方は警告する lint を導入。
  3. ロック時間の監視: pg_locks, SHOW PROCESSLIST, v$session_longops 等でロック保持時間を監視し、 一定時間を超えたら自動 kill する仕組みを整備。 SSDSE のような分析専用 DB でも、 長時間ロックはレプリケーション遅延の原因。
  4. 履歴テーブルの設計: 監査要件があるなら、 トリガで INSERT INTO history (op, old_row, new_row, updated_by, updated_at) を発火させ、 BEFORE/AFTER 値を残します。 GDPR の権利行使対応にも有効。
  5. テスト DB と本番 DB の差分管理: スキーマ移行ツール (Flyway, Alembic, Liquibase) で DDL/DML マイグレーションを版管理。 SSDSE-B-2026 の取り込みスクリプトも、 idempotent (何度実行しても同じ結果) になるよう ON CONFLICT で書く。
  6. ベンチマークと負荷試験: pgbench, sysbench, JMeter で典型クエリの TPS を計測し、 SLO (例: 99 パーセンタイル 200ms 以内) を設定。 本番デプロイ前にステージング環境で同等負荷をかける。

初学者がよく踏むトラップ集

DML を学び始めた人がよく踏む地雷を 8 つに整理しておきます。 1) WHERE を忘れた全行更新、 2) NULL = NULL の落とし穴、 3) サブクエリの相関エラー、 4) 結合キー間違いによるデカルト積、 5) 文字列リテラルとカラム名の混同 (シングル/ダブルクォート)、 6) DATE と TIMESTAMP の暗黙キャスト、 7) ORDER BY なしの LIMIT による不確定な結果、 8) UPDATE 直後の COMMIT 忘れ。 SSDSE-B-2026 のような小規模データで実験して全部踏んだ後、 本番に挑むのが理想です。

DML と現代データスタックの接続

伝統的 RDBMS の DML は今でも分析・運用の土台ですが、 現代では DWH (Snowflake, BigQuery, Redshift)、 データレイクハウス (Databricks, Iceberg)、 ストリーミング (Kafka, Flink) と組み合わせて使うことが増えています。 SSDSE-B-2026 を題材に置いてみると、 「DML で staging → MERGE で dim/fact 更新 → dbt model で集計 → BI で可視化」というパイプラインが現代的な形です。 dbt は SQL ベースの DML を Jinja テンプレートで再利用しやすくし、 incremental model で大量更新を効率化します。 一方ストリーミング DML では、 Kafka Connect + Debezium で OLTP の WAL を CDC として配信し、 DWH 側で MERGE INTO を実行する構成が標準化しつつあります。 こうした流れの中でも、 トランザクション境界・WHERE 句の確認・履歴管理という DML の基本規律は変わりません。

📋 DML を安全に運用するための前提条件と限界 (誤解回避)

DML (Data Manipulation Language: INSERT/UPDATE/DELETE/MERGE) は強力だが、 1 つの WHERE 句のミスで全行が壊れる。 SSDSE-B-2026 で実験する場合も、 本番運用と同じ規律で扱う必要がある。

前提 1: トランザクション境界が明確

DML は BEGIN; ... COMMIT; (または ROLLBACK) で囲み、 全体を 1 つの原子的な単位として扱う。 COMMIT を忘れたまま接続が切れると、 セッション末で自動 ROLLBACK して変更が消える。 SSDSE-B のような小データでも、 まず BEGIN + 確認 SELECT + COMMIT の習慣をつける。

前提 2: WHERE 句は SELECT で必ず先に確認

UPDATE prefecture SET population=0 WHERE region='北海道' の前に、 必ず SELECT * FROM prefecture WHERE region='北海道' で対象行を目視する。 「north hokkaido」のようなタイポで対象が 0 件、 または逆に WHERE を抜いて全 47 県が更新される事故を防ぐ。

前提 3: 履歴 (audit log) の保持

UPDATE/DELETE は元の値を破壊する。 監査可能性のため、 ① 別テーブルへ前値を INSERT、 ② temporal table (時間軸付きテーブル)、 ③ CDC (Change Data Capture) のいずれかを併用するのが理想。 SSDSE-B の年次データなら、 year 列を追加して上書きせず追記する設計が最も簡単。

限界 1: 大量更新はロックと WAL を爆発させる

UPDATE 1000万行 SET ... は WAL (Write-Ahead Log) を膨張させ、 ロック待ちで他クエリが詰まる。 chunked UPDATE (LIMIT 10000 × 1000 回) または INSERT INTO new_table SELECT ... ; RENAME のスワップ戦略に切り替える。

限界 2: ORDER BY なしの LIMIT は不定

DELETE FROM tbl WHERE ... LIMIT 100 でどの 100 行が削除されるかは、 ORDER BY なしでは DB 実装依存。 PostgreSQL/MySQL でも結果が再現しない。 必ず ORDER BY id LIMIT 100 のように決定的に書く。

📝 理解度チェック (5 問 / SSDSE-B-2026 を想定)

  1. Q1. UPDATE prefecture SET region='関東' WHERE prefecture_code IN ('13','11','12','14') を実行する前に必ず行うべき確認手順を 3 ステップで記述せよ。
  2. Q2. 47 都道府県のうち、 同じ name で 2 行ある (重複登録) ことが判明した。 重複を削除しつつ ID の小さい方を残す DML 文を 1 文で書け。
  3. Q3. 1000 万行のテーブルで UPDATE をすると数時間かかる。 1) chunked UPDATE と 2) スワップ戦略の長所と短所を比較せよ。
  4. Q4. MERGE INTO 文が「DWH ではよく使われるが、 OLTP では使われない」と言われる理由を 2 行で述べよ。
  5. Q5. SSDSE-B のような少量データで DML を学ぶときに、 本番運用に活きる規律 (チェックリスト) を 5 項目挙げよ。

解答方針

📊 数式に値を入れて手で計算する — DML 行数変化と高齢化率の計算

DML は数学的には集合演算。 INSERT は集合の和 $R \leftarrow R \cup \{t\}$、 DELETE は差 $R \leftarrow R \setminus \sigma_p R$。 一連の DML 操作後のテーブル行数 $n$ は:

$$n_{\text{final}} = n_0 + \sum_i \Delta_{\text{INSERT}_i} - \sum_j \Delta_{\text{DELETE}_j}$$

UPDATE は行数を変えない($\Delta = 0$)。 INSERT は $\Delta > 0$、 DELETE は $\Delta > 0$(引く方向)。

Step 1: 各 DML オペレーションの行数変化

操作$\Delta$ (行数変化)残行数 $n$
初期状態 (SSDSE-B 2023 年分)—47
INSERT 5 行 (新規データ追加)+552
UPDATE WHERE pop > 5000000 (9 行対象)0 (件数不変)52
DELETE WHERE pop < 660000 (2 行対象: 鳥取 537,000 人・島根 650,000 人)−250
INSERT 3 行 (補完データ)+353

Step 2: 式に値を代入

n_final = n0 + Σ(INSERT) - Σ(DELETE) = 47 + (5 + 3) - 2 = 47 + 8 - 2 = 53 行

Step 3: さらに高齢化率 (aging_rate = 65 歳以上人口 / 総人口) を UPDATE で計算

SSDSE-B には面積の列が無いので、 収録されている 65 歳以上人口 A1303 を使う。 東京都 (2023 年) の高齢化率: $\text{aging\_rate} = \dfrac{A1303 \times 100}{A1101} = \dfrac{3{,}205{,}000 \times 100}{14{,}086{,}000} \approx 22.75 \ \%$

🐍 Python で Step 2・Step 3 を再現

このコードでやること: 手計算 Step 2 (行数変化の式) と Step 3 (東京都の高齢化率) を Python で検証し、 UPDATE・DELETE の対象件数と東京都の値を SSDSE-B-2026 の CSV でも確かめる。

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

# Step 2: 行数変化の式 n_final = n0 + Σ(INSERT) - Σ(DELETE)
n0 = 47   # SSDSE-B-2026 の 2023 年分
ins1, ins2 = 5, 3     # INSERT x2
del1 = 2             # DELETE (pop < 660000 → 鳥取県・島根県)
n_final = n0 + ins1 + ins2 - del1
print(f"最終行数: {n0} + {ins1} + {ins2} - {del1} = {n_final} 行")

# Step 3: 東京都の高齢化率 aging_rate = A1303 * 100 / A1101
tokyo_65 = 3205000     # A1303 (65 歳以上人口・人, 2023 年)
tokyo_pop = 14086000   # A1101 (総人口・人, 2023 年)
aging_rate = tokyo_65 * 100 / tokyo_pop
print(f"東京都 高齢化率: {aging_rate:.2f} %")

# 実データ (SSDSE-B-2026) で DELETE の対象件数と東京都の値を確かめる
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026'] == 2023]
print("UPDATE 対象 (pop > 5000000):", (d['A1101'] > 5000000).sum(), "行")
print("DELETE 対象 (pop < 660000):", d.loc[d['A1101'] < 660000, 'Prefecture'].tolist())
t = d[d['Prefecture'] == '東京都'].iloc[0]
print(f"CSV から: {t['A1303']} * 100 / {t['A1101']} = {t['A1303'] * 100 / t['A1101']:.2f} %")

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

最終行数: 47 + 5 + 3 - 2 = 53 行 東京都 高齢化率: 22.75 % UPDATE 対象 (pop > 5000000): 9 行 DELETE 対象 (pop < 660000): ['鳥取県', '島根県'] CSV から: 3205000 * 100 / 14086000 = 22.75 %

💬 結果の読み方: 手計算 (Step 2) の 53 行と Python 出力が完全一致。 東京都の高齢化率 22.75% も Step 3 の手計算 ($3205000 \times 100 / 14086000 \approx 22.75$) と、 CSV から読んだ値の両方で一致。 CSV で数えると UPDATE の対象 (500 万人超) は 9 行、 DELETE の対象 (66 万人未満) は鳥取・島根の 2 行で、 Step 1 の表の件数と合う。 UPDATE の 9 行は値が変わるだけで行数は 52 のまま、 という関係代数の原則が式の上でも効いている。

🗺 DML 概念マップ

DML Data Manipulation Language DDL (構造定義) CREATE / ALTER / DROP DCL (権限制御) GRANT / REVOKE TCL (トランザクション) BEGIN / COMMIT / ROLLBACK SELECT (読み取り) WHERE / GROUP BY / JOIN Window 関数 CTE (WITH) INSERT (追加) R ← R ∪ {t} UPDATE (更新) WHERE 句必須 DELETE (削除) R ← R \ σp R 安全運用 ACID / BEGIN-ROLLBACK パフォーマンス インデックス / EXPLAIN ORM / マイグレーション BigQuery / Snowflake

図: DML の全体像。 中心の DML (SELECT/INSERT/UPDATE/DELETE) を軸に、 SQL 分類 (DDL/DCL/TCL)、 安全運用 (ACID/トランザクション)、 パフォーマンス (インデックス/EXPLAIN)、 周辺技術 (ORM/クラウド DB) との関係を示す。

✅ DML 実プロジェクト最終チェックリスト

⚖ DML と他のデータ処理技術の比較

技術用途DML との関係
DML (SQL)構造化データ操作中心技術
NoSQL (MongoDB 等)半構造化データ独自クエリ言語、 SQL ライクに収束中
MapReduce大規模分散処理Hive SQL 等で SQL 化された
Spark SQL分散 SQL エンジンDML 互換
pandasPython データ操作DML 相当の機能を Python で
R / dplyrR データ操作SQL ライクなパイプ構文
Excel/Sheets表計算GUI 版の DML
BI ツール可視化裏で DML を生成
Stream Processing (Kafka Streams)リアルタイムKSQL という SQL ライク言語
GraphQLAPI クエリ裏で DML を生成

「データ操作」の本質は DML が捉えており、 新しい技術もすべて SQL ライクな構文に収束しています。

📊 SSDSE-B-2026 を SQL で分析した代表的な統計

クエリ結果
2023 年全国人口合計124,352,000 人
2023 年都道府県平均人口2,645,808 人
2023 年人口中央値1,549,000 人 (鹿児島県)
2023 年最大人口14,086,000 人 (東京都)
2023 年最小人口537,000 人 (鳥取県)
最大/最小比26.2 倍
2012 → 2023 全国人口変化約 -324 万人 (-2.5%)
2012 → 2023 増加県数東京都・沖縄県・神奈川県・愛知県・埼玉県・千葉県・福岡県・滋賀県 (8 都道府県)
2012 → 2023 減少県数39 都道府県
500 万以上県の数 (2023)8 県 (東京〜福岡)
100 万未満県の数 (2023)10 県 (鳥取〜香川)

これらの数値はすべて SSDSE-B-2026 を SQL で集計したもの。 都道府県間の格差や人口減少の進行が一目で分かります。 統計データ解析コンペで活用可能。

⚡ DML クイックリファレンス

状況使うべき DML
既存データを読みたいSELECT
新しいデータを追加INSERT
既存データを変更UPDATE (WHERE 必須)
不要データを削除DELETE (WHERE 必須)
大量データを取り込みたいINSERT バルク or COPY
「あれば更新、 なければ追加」UPSERT (ON CONFLICT)
複数表を組み合わせるJOIN
集計したいGROUP BY + 集計関数
順位付けしたいROW_NUMBER / RANK
前年比を出したいLAG (Window 関数)
複雑なクエリを段階的にWITH (CTE)
テーブル全体を空にしたいTRUNCATE (DELETE より速い)

📋 まとめ

🔗 隣接手法への橋渡し — DML と周辺技術の接続

DML(データ操作言語)は単独で完結せず、 上流・並列・下流の技術と連携することで真価を発揮する。

関係技術・手法接続の意味
上流(前処理)データクレンジングDML で投入する前に欠損・外れ値を除去
上流(定義)DDL・スキーマ設計CREATE TABLE の型・制約を守る形で DML を書く
並列(代替)pandas小規模データなら pandas でも同等操作が可能
並列(NoSQL)NoSQL / MongoDBスキーマレスな文書操作 — DML と使い分け
並列(分散)Spark SQL / BigQuery大規模分散処理でも SQL 互換の DML を使う
下流(可視化)BI ツールDML の SELECT 結果を BI に流してダッシュボード化
下流(ML 特徴量)集計・特徴量エンジニアリングDML の GROUP BY・JOIN で ML 用特徴量を生成
下流(品質管理)データ品質・監査UPDATE/DELETE 後の件数・値の正合性チェック
セキュリティ認証・権限管理 (DCL)DML の GRANT で SELECT/INSERT/UPDATE を制限
自動化ORM / SQLAlchemyPython オブジェクトから DML を自動生成

典型的な 分析パイプライン: データ収集 (スクレイピング / CSV) → DML (INSERT で DB 投入) → SELECT + GROUP BY で集計 → BI / pandas で可視化 → ML 特徴量として利用。 DML はこのパイプラインの「動脈」に位置する。

🌳 手法選択フロー — どの DML 命令・アプローチを使うか

データ操作の目的と状況に応じて、 適切な DML 命令・ツール・アーキテクチャを選択するフロー。

状況・目的推奨アプローチ根拠
データを読み取るだけSELECT + 必要列のみ指定SELECT * は不要列を含みパフォーマンス低下
新しいデータを追加INSERT (バルク推奨)1 行ずつより executemany で 10 倍以上速い
既存データを変更BEGIN → SELECT 確認 → UPDATE → COMMITWHERE 忘れは全行破壊。 儀式で事故防止
「あれば更新、 なければ追加」UPSERT (ON CONFLICT)ETL バッチで冪等性が必要な場合
大量削除DELETE + LIMIT チャンク or TRUNCATE1 回の大量削除はロック・WAL 肥大化
グループ別集計GROUP BY + SUM/AVG/COUNTpandas の groupby より大規模データで優位
前年比・累積集計Window 関数 (LAG, SUM OVER)GROUP BY では元行を残せない。 Window で両立
複雑な多段クエリCTE (WITH 句) を使うネストしたサブクエリより可読性・デバッグが容易
メモリに収まる小規模データpandas + pd.read_sql / to_sqlSSDSE-B-2026 (564 行) は pandas で十分
ペタバイト級の大規模分析BigQuery / Snowflake / Redshift の SQLローカル RDBMS は I/O ボトルネック
Web アプリの ORM が遅いN+1 問題を特定 → SELECT IN か JOIN に書き換えORM は SELECT * + ループが多く遅くなる
リアルタイムデータパイプラインCDC (Debezium) + Kafka + DWH MERGEDML イベントをストリームとして伝播

要約フロー: データ操作目的 → 命令選択 (SELECT/INSERT/UPDATE/DELETE/MERGE) → スケール判断 (ローカル/クラウド) → トランザクション設計 → BEGIN; 確認; COMMIT の儀式。

🎮 データ操作ラボ:INSERT / UPDATE / DELETE で表を書き換える

このコーナーは DML の主役=データの「変化」(行の追加・条件付き更新・条件付き削除)を手を動かして体感する場です。 SQL プレイグラウンド(SELECT 中心の問い合わせ)や DDL(テーブル構造の定義)とは役割が異なり、 ここでは中身がどう書き換わるかだけに集中します。 実行はページ内蔵の簡易エンジン(JS)が正確に計算し、 変更された行を色付きでハイライトします。

※ 下の表は教材用の架空データ(在庫表)です。 SSDSE 等の実測統計ではありません。

📦 対象テーブル(教材例・架空)— 在庫表

(a) 3 つの操作を実行してみる

行を 追加(INSERT)・条件付きで更新(UPDATE)・条件付きで削除(DELETE)。 実行するたびに、 影響を受けた行が 緑=新規 / 黄=更新 / 赤=削除予定 でハイライトされます。

+ INSERT(行を追加)

商品 在庫 価格

✎ UPDATE(条件に合う行を更新)

SET =
WHERE

🗑 DELETE(条件に合う行を削除予定に)

WHERE

(b) WHERE 忘れの恐怖 — 全行操作の事故

上の UPDATE / DELETE パネルで 「WHERE を付けない(全行)」にチェックを入れて実行してみてください。 たった 1 回の操作ですべての行が書き換わる/消える様子が見えます。 これは実務で最も多い DML 事故で、 WHERE id = 3 と書くつもりが WHERE ごと消してしまうと、 全顧客・全在庫を一撃で破壊します。 だからこそ次の (c) トランザクションが命綱になります。

(c) トランザクション — COMMIT で確定 / ROLLBACK で取消

ここでの INSERT/UPDATE/DELETE は、 すべて「未確定(作業中)」の状態です。 COMMIT を押すまで確定されず、 ROLLBACK を押せば直前の確定状態までまるごと取り消せます。 事故った UPDATE/DELETE も、 COMMIT 前なら ROLLBACK で無かったことにできます。

🧭 直感・落とし穴・発展

🧭 解説深化 — 「集合思考」で DML を捉え直す

直感 — DML は「1 行ずつ」ではなく「集合まるごと」に効く

アプリ開発で身についた for ループ(1 件ずつ処理する手続き思考)を、そのまま DML に持ち込むと事故ります。 UPDATE / DELETE は「行を 1 つずつ選んで書き換える」命令ではありません。 WHERE が定義する集合(=条件を満たす行の集まり)に対して、一括で同じ操作を適用する命令です。 SELECT も同じで、返ってくるのは「1 行」ではなく「集合(結果表)」です。 この 集合思考への切り替えが、DML を安全かつ高速に使う出発点になります。

WHERE 句は「どの行を消すか」を指定しているのではなく、操作対象の集合そのものを定義しています。 だからこそ WHERE を書き忘れると、集合が「テーブル全体」に膨張し、全行が巻き込まれます。

🔬 実データで体感:SELECT / DELETE ... WHERE 人口 ≥ しきい値 は何県を集合に含むか

SSDSE-B-2026・2023 年・47 都道府県の A1101(総人口, 実測値)。 スライダーを動かすと、その WHERE 条件が定義する「集合の大きさ」が変わります。

WHERE 人口 ≥ 1,000,000
集合に含まれる都道府県:37 / 47 県

落とし穴(重要)— 「集合が空」「集合が全体」の 2 大事故

発展 — 集合演算としての DML を広げる

関連ページ