本ページは DML(データ操作言語)(Data Manipulation Language)を多角的に解説します。 上のチップは、 検索・関連語の手がかりです。
「dml」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「dml」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「dml の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
データの中身を操作する道具です。
データの追加や変更をするために使います。
スマホアプリの情報を書き換えるような操作です。
安全に使うためのコツを学びましょう。
🍰 まずはやさしく
データベースの中核となる言葉です。
データの検索や削除を行うために使います。
部活の名簿を管理する仕組みのようなものです。
データエンジニアリングの基本を学びます。
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 等の操作言語。 関係代数の視点では、 各命令は集合演算として形式化される:
ここで $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 や重複の扱いが現実的な妥協を含む。
SQL の高速化は EXPLAIN で内部を見ることから。 PostgreSQL の例:
「Seq Scan」は全行スキャン。 「Index Scan」「Bitmap Heap Scan」が出ればインデックスが効いている。 大規模データでは Seq Scan を Index Scan に変えるだけで 100 倍速くなることも。
| 種類 | 動作 | 用途 |
|---|---|---|
| VIEW (通常) | SELECT のエイリアス、 毎回実行 | クエリの再利用、 セキュリティ (列制限) |
| Materialized View | 結果を物理保存、 REFRESH で更新 | 重い集計を事前計算 |
| Temporary Table | セッション中だけ存在 | 中間結果保存 |
| CTE (WITH) | クエリ内だけ有効 | 段階的なクエリ構築 |
| エンジン | DBMS | 特徴 |
|---|---|---|
| InnoDB | MySQL/MariaDB | トランザクション対応、 標準 |
| MyISAM | MySQL (旧) | 速いがトランザクション無し |
| RocksDB | MySQL/CockroachDB | LSM-Tree、 書き込み高速 |
| WiredTiger | MongoDB | 圧縮、 トランザクション |
| SQLite ファイルベース | SQLite | B-Tree、 シンプル |
| PostgreSQL Heap | PostgreSQL | MVCC、 高機能 |
| カラム指向 (Parquet, ORC) | BigQuery 等 | 分析クエリで高速 |
| サービス | 提供元 | 特徴 |
|---|---|---|
| Amazon RDS | AWS | MySQL/PostgreSQL マネージド |
| Amazon Aurora | AWS | RDS の高速版 |
| Amazon Redshift | AWS | カラム指向 DWH |
| Google Cloud SQL | GCP | OLTP マネージド |
| BigQuery | GCP | サーバーレス DWH |
| Azure SQL Database | Azure | SQL 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 拡張 |
「日本語で質問するだけで SQL が自動生成される」時代が到来しています。 これは DML の未来を変える可能性があります。
| サービス | 提供元 | 特徴 |
|---|---|---|
| ChatGPT Code Interpreter | OpenAI | CSV を直接分析、 SQL も生成 |
| Claude Sonnet | Anthropic | SQL の高品質生成 |
| Gemini Code | BigQuery 統合 | |
| Vanna.ai | 独立 | Text-to-SQL 特化 OSS |
| DataLine | 独立 | BI ツール統合 |
| Defog SQLCoder | 独立 | SQL 専用 LLM |
「東京都の 2023 年人口を教えて」と聞けば SQL が生成され、 DB から答えが返る。 ただし生成された SQL の正しさ検証は人間の責任。
日本の社会インフラの大半が SQL + DML で動いている。 「目に見えないが社会を支える技術」の代表例。
| pandas | SQL | 説明 |
|---|---|---|
df.head(10) | SELECT * FROM t LIMIT 10 | 先頭 10 行 |
df.tail(5) | SELECT * FROM t ORDER BY id DESC LIMIT 5 | 末尾 |
df.shape | SELECT COUNT(*) FROM t | 行数 |
df.columns | PRAGMA 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 (ユーザー定義関数) | カスタム処理 |
WHERE LOWER(name)=... はインデックス無効WHERE t1.id = t2.id はやめ、 JOIN ON にA → B に 1 万円送金:
ACID の Atomicity が必須。 片方だけ成功して片方失敗は許されない。
商品の在庫が複数あるとき、 同時に複数の注文が来たら:
「最近 30 日でアクティブな県別ユーザー数」:
SSDSE-B-2026 と外部の経済指標を JOIN して相関分析:
本番 DB のスキーマ変更を安全に行うのが マイグレーション。 ツール一覧:
| ツール | 言語 | 特徴 |
|---|---|---|
| Flyway | Java | シンプル、 SQL ベース |
| Liquibase | Java | XML/YAML、 高機能 |
| Alembic | Python | SQLAlchemy 統合 |
| Django Migrations | Python | Django 標準 |
| Rails Migrations | Ruby | Rails 標準 |
| Sequelize | Node.js | JavaScript |
| Prisma | Node.js | モダン、 型安全 |
| golang-migrate | Go | Go 製、 高速 |
マイグレーションは「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 フローを示す。
📥 入力データ:
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) |
📤 実行例:
💬 結果の読み方: BEGIN/COMMIT で囲んだ UPDATE は ACID の「Atomicity」を担保し、 失敗時は ROLLBACK で全て巻き戻る。 1 文の UPDATE で 47 行すべてに派生列 density (人/km²) を一括計算でき、 続く SELECT で東京 6403, 大阪 4632 と上位 5 県が即座に得られる。 pandas で同じことをやると loop が必要だが、 SQL なら宣言的に 1 行で書ける点が DML の威力。
DML(INSERT/UPDATE/DELETE/SELECT)の結果は、 多くの場合「集計→分布→ランキング」という3段階で可視化されます。 SSDSE-B-2026 を例にした典型的な可視化を3点示します。
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 で確認すること。
初期テーブルに 5 都道府県が登録されているとする。高齢化率 (%) のベクトルは:
高齢化率 データ: [29.8, 23.4, 31.2, 27.6, 33.5](北海道・東京・秋田・岩手・島根 の順)
初期行数 $n_0 = 5$。 以下の DML を順に実行した場合の行数変化を手計算する。
Step 1 — INSERT 2 件: 神奈川・大阪を追加
ΔINSERT = [2] → 行数 = 5 + 2 = 7
Step 2 — DELETE WHERE 高齢化率 < 25: [23.4] → 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 都道府県の高齢化率
aging_rates = [29.8, 23.4, 31.2, 27.6, 33.5]
n0 = len(aging_rates) # 5
# Step 1: INSERT 2 件
delta_insert = [2]
n1 = n0 + sum(delta_insert) # 7
# Step 2: DELETE WHERE 高齢化率 < 25 → [23.4] が 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 と Python 出力 6 で一致。 np.allclose が True を返したことで、 $n_{\text{final}} = 5 + 2 - 1 = 6$ が数式・手計算・実装の三者で同じ値であることが確認できる。
DML の真価は 実データで体感するのが一番。 SSDSE-B-2026 (47 都道府県 × 12 年 = 564 行) を SQLite に投入し、 様々な SELECT/UPDATE を試してみましょう。
| 関係代数 | SQL での実現 | 意味 |
|---|---|---|
| 選択 (Selection) σ | WHERE 句 | 条件に合う「行」を選ぶ |
| 射影 (Projection) π | SELECT 列名 | 必要な「列」だけ取り出す |
| 結合 (Join) ⋈ | JOIN ON | 複数テーブルを連結 |
| 和 (Union) ∪ | UNION | 2 つのクエリ結果を合体 |
| 差 (Difference) − | EXCEPT | 片方にあって他方にない |
| 積 (Cartesian Product) × | CROSS JOIN | 全組み合わせ |
例えば「東京都の 2023 年人口」を取るクエリ:
SELECT A1101 FROM ssdse WHERE Prefecture = '東京都' AND year = 2023;このコードでやること: SSDSE-B-2026.csv を SQLite に読み込み、 pd.read_sql で SELECT 文を実行して結果を DataFrame として取得する最小構成。 DML の基本フローをそのまま Python で確認できる。
📥 入力データ (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 30 31 | # ── この抜粋で使うデータを用意します(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(result.head()) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 高齢化率 30% 超の都道府県が SELECT で抽出できた。 pd.read_sql は DML の SELECT 結果を DataFrame として返すので、 以降は pandas の集計・可視化と組み合わせて使える。
🎯 このコードでやること: 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()) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 47 都道府県 × 12 年 = 564 行が正しく投入。 pandas の to_sql は INSERT 文を 1 行ずつ発行するのではなく、 内部でバルクインサート (executemany) を使い高速。
🎯 このコードでやること: 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)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 上位 10 都道府県は東京・神奈川・大阪を含む大都市圏 + 北海道・福岡などの地方中核。 SELECT は DML 中で最も使われる命令で、 全 DML 命令の 95% 以上を占める (実務感覚)。
🎯 このコードでやること: 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)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 全 47 都道府県を集計すると、 日本の人口は 11 年で約 2.5% 減少。 GROUP BY は実務で最も使われる DML 構造の 1 つ。 「集計の単位 + 集計関数 (SUM/AVG/COUNT/MAX/MIN)」がセットで覚えるのがコツ。
🎯 このコードでやること: 東京都の 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]} (元に戻った)') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 影響行数が想定 (1) と一致することを確認してから COMMIT、 違えば ROLLBACK。 この「BEGIN → 確認 → COMMIT/ROLLBACK」の儀式が本番運用での事故防止に必須。 UPDATE WHERE 条件ミスで全行更新する事故は実務で頻発。
🎯 このコードでやること: 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 | import pandas as pd import sqlite3 conn = sqlite3.connect('ssdse.db') # 都道府県と地方の対応表を作成 region_map = pd.DataFrame([ {'Prefecture': '北海道', 'Region': '北海道'}, {'Prefecture': '青森県', 'Region': '東北'}, {'Prefecture': '東京都', 'Region': '関東'}, {'Prefecture': '神奈川県', 'Region': '関東'}, {'Prefecture': '大阪府', 'Region': '近畿'}, {'Prefecture': '愛知県', 'Region': '中部'}, # ... 47 都道府県分 ]) region_map.to_sql('prefecture_region', conn, if_exists='replace', index=False) # JOIN query = ''' SELECT r.Region 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)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: JOIN で別テーブルの情報 (地方区分) を結合して集計可能に。 INNER JOIN は両テーブルにある行のみ、 LEFT JOIN は左テーブル全行 (右がない場合は NULL)。
DML を本番運用するうえで欠かせないのが トランザクションと ACID 特性。 数式を言葉で読み解くと、 これは「データベース操作の安全保証ルール」です。
| 特性 | 意味 | 例 |
|---|---|---|
| Atomicity (原子性) | 全て成功か全て失敗 | 銀行送金: 引き出し成功・入金失敗は許されない |
| Consistency (一貫性) | 整合性ルールを常に満たす | 合計残高がトランザクション前後で同じ |
| Isolation (分離性) | 並行実行でも独立 | 2 人同時に更新しても結果は順次実行と同じ |
| Durability (永続性) | コミット後は障害でも残る | 停電してもデータが消えない |
SQL の BEGIN; ...; COMMIT; でトランザクション。 失敗したら ROLLBACK;。 PostgreSQL や Oracle は ACID を厳密に守るが、 NoSQL では緩い (BASE 原則) ことが多い。
並行トランザクションでどの程度厳密に独立を保つか。 厳しいほど安全だが遅い。
| レベル | 許される現象 | 使い所 |
|---|---|---|
| READ UNCOMMITTED | Dirty Read, Non-repeatable, Phantom | ほぼ使わない |
| READ COMMITTED | Non-repeatable, Phantom | PostgreSQL デフォルト |
| REPEATABLE READ | Phantom のみ | MySQL デフォルト |
| SERIALIZABLE | なし (最も厳密) | 金融など高信頼が必要な場面 |
SSDSE のような分析用途では READ COMMITTED で十分。 在庫管理や金融はより厳格な REPEATABLE READ や SERIALIZABLE が必要。
| 種類 | 取得契機 | 影響 |
|---|---|---|
| 共有ロック (S) | SELECT (一部 DBMS) | 他者の SELECT は OK、 UPDATE 不可 |
| 排他ロック (X) | UPDATE, DELETE, INSERT | 他者は何もできない |
| 行ロック | WHERE で 1 行特定 | 他行は無影響 |
| テーブルロック | ALTER TABLE 等 | テーブル全体停止 |
| デッドロック | 循環的ロック | DBMS が片方を強制終了 |
ロック競合は実務で頻発するパフォーマンス問題。 「短いトランザクション」「同じ順序で複数行ロック」が回避のコツ。
DML を実行するアプリで最も多い脆弱性が SQL インジェクション。 ユーザー入力を文字列連結で SQL に埋め込むと、 攻撃者に任意の SQL を実行されます。
Python の sqlite3, psycopg2, mysql.connector はすべてプレースホルダ対応。 ? や %s で書き、 値は別引数で渡す。 ORM (SQLAlchemy 等) を使えば自動的に安全。
🎯 このコードでやること: 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]} 行 (元通り)') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 影響行数が想定 (47 = 都道府県数 × 1 年) と一致することを確認。 もし 517 とか想定外なら ROLLBACK で守る。 これが本番運用の鉄則。
🎯 このコードでやること: 各都道府県の人口について、 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)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 東京は 12 年で約 86 万人増、 鳥取は約 4 万 5 千人減、 沖縄は微増。 LAG (PARTITION BY Pref ORDER BY year) で「同じ Pref 内の前年値」が取れる。 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)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 47 都道府県の人口は全て異なるので、 ROW_NUMBER / RANK / DENSE_RANK が一致。 四分位 (NTILE(4)) で上位 12 都道府県は Q4。 Window 関数は SQL:2003 で標準化された比較的新しい機能だが、 現代分析には必須。
🎯 このコードでやること: 「都道府県データが既存なら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) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: UPSERT は「データの取り込みを冪等にする」便利パターン。 同じデータを何度実行しても結果が同じになるので、 ETL バッチで多用される。 PostgreSQL は ON CONFLICT、 MySQL は ON DUPLICATE KEY UPDATE。
UPDATE 表 SET 列 = 値 だけだと全行が書き換わる。 本番では BEGIN; UPDATE …; -- 確認 ROLLBACK; or COMMIT; の儀式を。LIKE '%abc%' は両端ワイルドカードで遅い。 インデックスが効かない。= NULL は常に偽。 必ず IS NULL を使う。WHERE 文字列カラム = 123 のような比較は予期せぬ結果を生む。| 状況 | NG 例 | 正しい書き方 |
|---|---|---|
| NULL 比較 | WHERE x = NULL | WHERE x IS NULL |
| 大小文字 | WHERE name = 'Tokyo' | WHERE LOWER(name) = 'tokyo' |
| 暗黙の型変換 | WHERE id = '123' (id が int) | WHERE id = 123 |
| 否定の NOT IN | NULL を含むサブクエリで予期せぬ結果 | NOT EXISTS に書き換え |
| LIKE のワイルドカード | LIKE '%abc%' (両端) でインデックス無効 | 全文検索エンジン (FTS) 検討 |
| 関数適用 | WHERE YEAR(date) = 2023 | WHERE date BETWEEN '2023-01-01' AND '2023-12-31' |
EXPLAIN ANALYZE)INSERT INTO ... VALUES (...), (...), ...| 種類 | 仕組み | 用途 |
|---|---|---|
| B-Tree (バランス木) | 標準的なソート済み木 | 等価・範囲検索の万能型 |
| Hash | ハッシュ値で位置特定 | 等価検索のみ、 範囲不可 |
| GIN (Generalized Inverted) | 転置インデックス | 全文検索、 JSONB |
| GIST | 幾何データ向け | 地理座標、 範囲型 |
| BRIN (Block Range) | ブロックレベル | 巨大時系列データ |
| 部分インデックス | WHERE 条件付き | 使う部分だけ |
| 関数インデックス | 関数結果にインデックス | LOWER(name) 等 |
| 複合インデックス | 複数列でソート | WHERE で複数条件 |
| DBMS | 長所 | 短所 | 用途 |
|---|---|---|---|
| SQLite | ファイル 1 個、 設定不要 | 同時書き込み弱い | 個人開発、 教育 |
| PostgreSQL | 機能豊富、 SQL 標準準拠 | 設定やや複雑 | 本格運用、 分析 |
| MySQL | 速い、 Web で定番 | SQL 標準準拠ゆるい | Web アプリ |
| MariaDB | MySQL 互換、 オープン | 同上 | MySQL 代替 |
| Oracle | エンタープライズ機能 | 高額 | 大企業 |
| SQL Server | Windows 統合 | 非 Windows で不便 | Microsoft 系 |
| DuckDB | 分析特化、 高速 | OLTP 不向き | データ分析 |
| BigQuery | クラウド、 ペタバイト級 | 有料、 ベンダーロック | 大規模分析 |
| Snowflake | マルチクラウド DWH | 有料 | 企業分析基盤 |
| Redshift | AWS 統合、 DWH | 運用コスト | AWS ユーザー |
「グループごとに集計しつつ、 行も個別に保持したい」というニーズに応えるのが Window 関数。 SQL:2003 以降標準。
| 関数 | 用途 | 例 |
|---|---|---|
| ROW_NUMBER() | 連番 | 都道府県人口ランキング |
| RANK() | 同順位許容ランク | 同人口は同順位、 次は飛ぶ |
| DENSE_RANK() | 同順位許容、 連続 | 同上だが順位は連続 |
| LAG(x, n) | n 行前の値 | 前年比計算 |
| LEAD(x, n) | n 行後の値 | 次年予測 |
| SUM() OVER (...) | 累積和 | 累積人口 |
| AVG() OVER (...) | 移動平均 | 5 年移動平均 |
| NTILE(n) | n 分割 | 四分位 |
例: 「都道府県別の前年比人口増減」:
= NULL ではなく IS NULLYYYY-MM-DD) で統一"order" や "user" はクォート必須% は 0 文字以上、 _ は 1 文字INSERT INTO t VALUES (...) で列順依存| ORM | 特徴 | 用途 |
|---|---|---|
| SQLAlchemy | Python 標準、 柔軟 | 幅広く |
| Django ORM | Django 統合 | Web アプリ |
| Peewee | 軽量 | 小規模 |
| SQLModel | FastAPI 統合、 型ヒント | モダン API |
| Tortoise ORM | 非同期対応 | async/await |
| raw SQL | ORM 不使用 | パフォーマンス重視 |
ORM は SQL を書かなくて済むが、 N+1 問題 (1 件取得ごとに 1 クエリ発行) などの落とし穴あり。 SSDSE 程度の分析なら pandas + raw SQL の方が直感的。
| 操作 | pandas | SQL |
|---|---|---|
| SELECT | df[['col1', 'col2']] | SELECT col1, col2 FROM t |
| WHERE | df[df.x > 100] | WHERE x > 100 |
| ORDER BY | df.sort_values('x') | ORDER BY x |
| GROUP BY | df.groupby('y').sum() | GROUP BY y |
| JOIN | pd.merge(df1, df2, on='id') | JOIN ON id |
| UPDATE | df.loc[df.x==1, 'y'] = 2 | UPDATE t SET y=2 WHERE x=1 |
| INSERT | df.append(new_row) | INSERT INTO t VALUES |
| DELETE | df.drop(idx) | DELETE FROM t WHERE |
使い分け: メモリに収まる (< 数 GB) なら pandas、 大規模なら SQL。 SSDSE-B-2026 (564 行) は完全に pandas 範囲。
DML (Data Manipulation Language) は SELECT/INSERT/UPDATE/DELETE/MERGE など、 リレーショナル DB のデータ自体を操作する SQL のサブセットです。 強力で生産性が高い反面、 トランザクション管理・性能設計・データ整合性の理解が不十分だと「数百万行を誤って更新」「本番環境のロックで業務停止」といった致命的事故を起こします。 SSDSE-B-2026 を題材に、 適用条件・限界・典型的な誤解を整理します。
BEGIN; ... COMMIT; で境界を明示。 暗黙コミット (autocommit) のままだと、 途中で失敗しても部分反映されて整合性が壊れます。 ACID 特性 (Atomicity, Consistency, Isolation, Durability) を意識した運用が前提です。UPDATE/DELETE は WHERE を省略すると全行が対象になります。 SSDSE のように 47 行しかないテーブルでも、 本番では数億行になることを想定し、 必ず WHERE + LIMIT + 事前 SELECT 確認の三点セットを徹底。EXPLAIN ANALYZE で実行計画を確認し、 想定通りにインデックスが使われているかを検証。READ COMMITTED (デフォルト多数)・REPEATABLE READ・SERIALIZABLE の違いを理解し、 業務要件に合わせて設定。 SSDSE のような分析専用 DB なら READ COMMITTED で十分ですが、 在庫・金融なら SERIALIZABLE 相当が必要なことも。LIMIT 10000 単位で分割し、 適度にコミットするのが定石。RETURNING、 MySQL の INSERT ... ON DUPLICATE KEY UPDATE、 SQL Server の MERGE、 BigQuery の MERGE INTO など差異があります。 移植性を考えるなら共通部分にとどめる工夫が必要。WHERE x = NULL は常に NULL (=偽) になり、 期待した行が返りません。 WHERE x IS NULL を使う必要があります。 集計関数も NULL の扱いが異なる (COUNT(*) vs COUNT(col))。EXISTS や IN の方が意図が明確で読みやすいケースが多い。INSERT INTO ... SELECT ... ORDER BY は宛先テーブルの並び順を保証しません。 ORDER BY は SELECT 結果の順序付けにのみ有効。UPDATE ... LIMIT 100 は対象がランダム抽出される可能性があり、 確定性がありません。 ORDER BY と組み合わせ、 主キーを基準に処理する。SELECT prefecture, elderly_pop FROM ssdse WHERE prefecture <> '東京'; で結果セットを確認。 想定行数・想定値域を目視。BEGIN; でトランザクション開始。UPDATE ssdse SET flagged = TRUE WHERE prefecture <> '東京' AND elderly_pop > 1000000;。 影響行数を EXPLAIN や RETURNING で確認。SELECT COUNT(*) FROM ssdse WHERE flagged = TRUE; で期待数と一致するかチェック。COMMIT;、 想定外なら ROLLBACK;。EXPLAIN (ANALYZE, BUFFERS) で実行計画とコストを確認し、 必要ならインデックス追加。VACUUM ANALYZE で統計情報を更新。WHERE id BETWEEN n AND n+10000 のように区切って繰り返す。 各チャンクの後に COMMIT し、 レプリカ遅延を監視。INSERT ... ON CONFLICT (id) DO UPDATE SET ... で「あれば更新、なければ挿入」を 1 文で実現。 SSDSE のような毎年の更新スナップショットに最適。UPDATE deleted_at = NOW() で論理削除。 監査・復元・参照整合性に強く、 多くの業務システムで採用される。DML は「データを動かす最後のキー操作」であると同時に「事故が一番起きる場所」です。 SSDSE-B-2026 のような小規模データで「SELECT で確認 → BEGIN → 更新 → 検証 → COMMIT」の規律を体に叩き込んでから本番に臨みましょう。
SELECT ... FOR UPDATE + ROLLBACK で「影響範囲だけ事前確認」する手法。 PostgreSQL なら EXPLAIN UPDATE ... や BEGIN; UPDATE ...; SELECT * FROM updated_table; ROLLBACK; を組み合わせて、 副作用なしで挙動を観察できます。UPDATE t SET col=val は禁止する社内ルールを敷きます。 ORM (Django, SQLAlchemy) でも .all().delete() のような書き方は警告する lint を導入。pg_locks, SHOW PROCESSLIST, v$session_longops 等でロック保持時間を監視し、 一定時間を超えたら自動 kill する仕組みを整備。 SSDSE のような分析専用 DB でも、 長時間ロックはレプリケーション遅延の原因。INSERT INTO history (op, old_row, new_row, updated_by, updated_at) を発火させ、 BEFORE/AFTER 値を残します。 GDPR の権利行使対応にも有効。ON CONFLICT で書く。DML を学び始めた人がよく踏む地雷を 8 つに整理しておきます。 1) WHERE を忘れた全行更新、 2) NULL = NULL の落とし穴、 3) サブクエリの相関エラー、 4) 結合キー間違いによるデカルト積、 5) 文字列リテラルとカラム名の混同 (シングル/ダブルクォート)、 6) DATE と TIMESTAMP の暗黙キャスト、 7) ORDER BY なしの LIMIT による不確定な結果、 8) UPDATE 直後の COMMIT 忘れ。 SSDSE-B-2026 のような小規模データで実験して全部踏んだ後、 本番に挑むのが理想です。
伝統的 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 (Data Manipulation Language: INSERT/UPDATE/DELETE/MERGE) は強力だが、 1 つの WHERE 句のミスで全行が壊れる。 SSDSE-B-2026 で実験する場合も、 本番運用と同じ規律で扱う必要がある。
DML は BEGIN; ... COMMIT; (または ROLLBACK) で囲み、 全体を 1 つの原子的な単位として扱う。 COMMIT を忘れたまま接続が切れると、 セッション末で自動 ROLLBACK して変更が消える。 SSDSE-B のような小データでも、 まず BEGIN + 確認 SELECT + COMMIT の習慣をつける。
UPDATE prefecture SET population=0 WHERE region='北海道' の前に、 必ず SELECT * FROM prefecture WHERE region='北海道' で対象行を目視する。 「north hokkaido」のようなタイポで対象が 0 件、 または逆に WHERE を抜いて全 47 県が更新される事故を防ぐ。
UPDATE/DELETE は元の値を破壊する。 監査可能性のため、 ① 別テーブルへ前値を INSERT、 ② temporal table (時間軸付きテーブル)、 ③ CDC (Change Data Capture) のいずれかを併用するのが理想。 SSDSE-B の年次データなら、 year 列を追加して上書きせず追記する設計が最も簡単。
UPDATE 1000万行 SET ... は WAL (Write-Ahead Log) を膨張させ、 ロック待ちで他クエリが詰まる。 chunked UPDATE (LIMIT 10000 × 1000 回) または INSERT INTO new_table SELECT ... ; RENAME のスワップ戦略に切り替える。
DELETE FROM tbl WHERE ... LIMIT 100 でどの 100 行が削除されるかは、 ORDER BY なしでは DB 実装依存。 PostgreSQL/MySQL でも結果が再現しない。 必ず ORDER BY id LIMIT 100 のように決定的に書く。
UPDATE prefecture SET region='関東' WHERE prefecture_code IN ('13','11','12','14') を実行する前に必ず行うべき確認手順を 3 ステップで記述せよ。UPDATE をすると数時間かかる。 1) chunked UPDATE と 2) スワップ戦略の長所と短所を比較せよ。MERGE INTO 文が「DWH ではよく使われるが、 OLTP では使われない」と言われる理由を 2 行で述べよ。BEGIN でトランザクション開始、 ② SELECT prefecture_code, region FROM prefecture WHERE prefecture_code IN (...) で対象行を目視、 ③ 想定行数と一致したら UPDATE 実行 → SELECT で再確認 → COMMIT。DELETE FROM prefecture WHERE id NOT IN (SELECT MIN(id) FROM prefecture GROUP BY name)。 ただし MySQL では同テーブル参照に制限あり、 一時テーブル経由が必要。DML は数学的には集合演算。 INSERT は集合の和 $R \leftarrow R \cup \{t\}$、 DELETE は差 $R \leftarrow R \setminus \sigma_p R$。 一連の DML 操作後のテーブル行数 $n$ は:
UPDATE は行数を変えない($\Delta = 0$)。 INSERT は $\Delta > 0$、 DELETE は $\Delta > 0$(引く方向)。
| 操作 | $\Delta$ (行数変化) | 残行数 $n$ |
|---|---|---|
| 初期状態 (SSDSE-B 2023 年分) | — | 47 |
| INSERT 5 行 (新規データ追加) | +5 | 52 |
| UPDATE WHERE pop > 5000000 (8 行対象) | 0 (件数不変) | 52 |
| DELETE WHERE pop < 600000 (2 行対象: 鳥取・島根) | −2 | 50 |
| INSERT 3 行 (補完データ) | +3 | 53 |
東京都の人口密度: $\text{density} = \dfrac{A1101 \times 1000}{B1101} = \dfrac{14048 \times 1000}{2194} \approx 6402.9 \text{ 人/km}^2$
このコードでやること: 手計算 Step 2 (行数変化の式) と Step 3 (東京都の人口密度) を Python で検証し、 一致を確認する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # Step 2: 行数変化の式 n_final = n0 + Σ(INSERT) - Σ(DELETE) n0 = 47 # SSDSE-B-2026 の 2023 年分 ins1, ins2 = 5, 3 # INSERT x2 del1 = 2 # DELETE (pop < 600000) n_final = n0 + ins1 + ins2 - del1 print(f"最終行数: {n0} + {ins1} + {ins2} - {del1} = {n_final} 行") # Step 3: 東京都の人口密度 density = A1101 * 1000 / B1101 tokyo_pop = 14048 # A1101 (千人単位) tokyo_area = 2194 # B1101 (km²) density = tokyo_pop * 1000 / tokyo_area print(f"東京都 人口密度: {density:.1f} 人/km²") |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 手計算 (Step 2) の 53 行と Python 出力が完全一致。 東京都の人口密度 6,402.9 人/km² も Step 3 の手計算 ($14048 \times 1000 / 2194 \approx 6402.9$) と一致。 UPDATE は行数を変えないという関係代数の原則を数値で確認できた。
図: DML の全体像。 中心の DML (SELECT/INSERT/UPDATE/DELETE) を軸に、 SQL 分類 (DDL/DCL/TCL)、 安全運用 (ACID/トランザクション)、 パフォーマンス (インデックス/EXPLAIN)、 周辺技術 (ORM/クラウド DB) との関係を示す。
? や %s) を使った (文字列連結禁止)| 技術 | 用途 | DML との関係 |
|---|---|---|
| DML (SQL) | 構造化データ操作 | 中心技術 |
| NoSQL (MongoDB 等) | 半構造化データ | 独自クエリ言語、 SQL ライクに収束中 |
| MapReduce | 大規模分散処理 | Hive SQL 等で SQL 化された |
| Spark SQL | 分散 SQL エンジン | DML 互換 |
| pandas | Python データ操作 | DML 相当の機能を Python で |
| R / dplyr | R データ操作 | SQL ライクなパイプ構文 |
| Excel/Sheets | 表計算 | GUI 版の DML |
| BI ツール | 可視化 | 裏で DML を生成 |
| Stream Processing (Kafka Streams) | リアルタイム | KSQL という SQL ライク言語 |
| GraphQL | API クエリ | 裏で DML を生成 |
「データ操作」の本質は DML が捉えており、 新しい技術もすべて SQL ライクな構文に収束しています。
| クエリ | 結果 |
|---|---|
| 2023 年全国人口合計 | 124,352,000 人 |
| 2023 年都道府県平均人口 | 2,645,808 人 |
| 2023 年人口中央値 | 1,549,000 人 (鹿児島県) |
| 2023 年最大人口 | 14,086,000 人 (東京都) |
| 2023 年最小人口 | 537,000 人 (鳥取県) |
| 最大/最小比 | 26.2 倍 |
| 2012 → 2023 全国人口変化 | 約 -326 万人 (-2.5%) |
| 2012 → 2023 増加県数 | 東京都・沖縄県・神奈川県・愛知県・埼玉県・千葉県・福岡県・滋賀県 (8 都道府県) |
| 2012 → 2023 減少県数 | 39 都道府県 |
| 500 万以上県の数 (2023) | 8 県 (東京〜福岡) |
| 100 万未満県の数 (2023) | 10 県 (鳥取〜香川) |
これらの数値はすべて SSDSE-B-2026 を SQL で集計したもの。 都道府県間の格差や人口減少の進行が一目で分かります。 統計データ解析コンペで活用可能。
| 状況 | 使うべき 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 で投入する前に欠損・外れ値を除去 |
| 上流(定義) | 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 / SQLAlchemy | Python オブジェクトから DML を自動生成 |
典型的な 分析パイプライン: データ収集 (スクレイピング / CSV) → DML (INSERT で DB 投入) → SELECT + GROUP BY で集計 → BI / pandas で可視化 → ML 特徴量として利用。 DML はこのパイプラインの「動脈」に位置する。
データ操作の目的と状況に応じて、 適切な DML 命令・ツール・アーキテクチャを選択するフロー。
| 状況・目的 | 推奨アプローチ | 根拠 |
|---|---|---|
| データを読み取るだけ | SELECT + 必要列のみ指定 | SELECT * は不要列を含みパフォーマンス低下 |
| 新しいデータを追加 | INSERT (バルク推奨) | 1 行ずつより executemany で 10 倍以上速い |
| 既存データを変更 | BEGIN → SELECT 確認 → UPDATE → COMMIT | WHERE 忘れは全行破壊。 儀式で事故防止 |
| 「あれば更新、 なければ追加」 | UPSERT (ON CONFLICT) | ETL バッチで冪等性が必要な場合 |
| 大量削除 | DELETE + LIMIT チャンク or TRUNCATE | 1 回の大量削除はロック・WAL 肥大化 |
| グループ別集計 | GROUP BY + SUM/AVG/COUNT | pandas の groupby より大規模データで優位 |
| 前年比・累積集計 | Window 関数 (LAG, SUM OVER) | GROUP BY では元行を残せない。 Window で両立 |
| 複雑な多段クエリ | CTE (WITH 句) を使う | ネストしたサブクエリより可読性・デバッグが容易 |
| メモリに収まる小規模データ | pandas + pd.read_sql / to_sql | SSDSE-B-2026 (564 行) は pandas で十分 |
| ペタバイト級の大規模分析 | BigQuery / Snowflake / Redshift の SQL | ローカル RDBMS は I/O ボトルネック |
| Web アプリの ORM が遅い | N+1 問題を特定 → SELECT IN か JOIN に書き換え | ORM は SELECT * + ループが多く遅くなる |
| リアルタイムデータパイプライン | CDC (Debezium) + Kafka + DWH MERGE | DML イベントをストリームとして伝播 |
要約フロー: データ操作目的 → 命令選択 (SELECT/INSERT/UPDATE/DELETE/MERGE) → スケール判断 (ローカル/クラウド) → トランザクション設計 → BEGIN; 確認; COMMIT の儀式。
このコーナーは DML の主役=データの「変化」(行の追加・条件付き更新・条件付き削除)を手を動かして体感する場です。 SQL プレイグラウンド(SELECT 中心の問い合わせ)や DDL(テーブル構造の定義)とは役割が異なり、 ここでは中身がどう書き換わるかだけに集中します。 実行はページ内蔵の簡易エンジン(JS)が正確に計算し、 変更された行を色付きでハイライトします。
※ 下の表は教材用の架空データ(在庫表)です。 SSDSE 等の実測統計ではありません。
在庫表行を 追加(INSERT)・条件付きで更新(UPDATE)・条件付きで削除(DELETE)。 実行するたびに、 影響を受けた行が 緑=新規 / 黄=更新 / 赤=削除予定 でハイライトされます。
上の UPDATE / DELETE パネルで 「WHERE を付けない(全行)」にチェックを入れて実行してみてください。 たった 1 回の操作ですべての行が書き換わる/消える様子が見えます。 これは実務で最も多い DML 事故で、 WHERE id = 3 と書くつもりが WHERE ごと消してしまうと、 全顧客・全在庫を一撃で破壊します。 だからこそ次の (c) トランザクションが命綱になります。
ここでの INSERT/UPDATE/DELETE は、 すべて「未確定(作業中)」の状態です。 COMMIT を押すまで確定されず、 ROLLBACK を押せば直前の確定状態までまるごと取り消せます。 事故った UPDATE/DELETE も、 COMMIT 前なら ROLLBACK で無かったことにできます。
INSERT は行を足し、 UPDATE は既存行の値を書き換え、 DELETE は行を消す。 DDL(CREATE で「入れ物」を作る)と違い、 DML は入れ物の中身を動かす。 SELECT は「見るだけ」で表は変わらないのに対し、 INSERT/UPDATE/DELETE は状態を変えるのが決定的な違い。UPDATE 表 SET x=0 や DELETE FROM 表 のように WHERE を書き忘れると全行に及ぶ。 実行前に必ず同じ条件で SELECT COUNT(*) して「何行に効くか」を確認する習慣が事故を防ぐ。BEGIN; で囲み、 結果を確認してから COMMIT;、 おかしければ ROLLBACK;。 (c) で体感した通り。INSERT ... ON CONFLICT DO UPDATE(PostgreSQL)や MERGE で 1 文にできる。 ETL バッチの冪等性(何度流しても同じ結果)に必須。 SQL の重要イディオム。アプリ開発で身についた 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,000DELETE ... WHERE 人口 < 1000000 が定義する集合は 10 県(最小は鳥取県 537,000)。 ところが WHERE を書き忘れた DELETE FROM 在庫表; は集合=47 県すべて。 たった 1 語の欠落で、対象集合が 10 から 47 へ跳ね上がる ── これが全行削除・全行更新の正体です。 UPDATE/DELETE 前に同じ WHERE で SELECT し、集合の大きさを先に目で確認するのが唯一の防御です。WHERE col = NULL や col <> 5 は、col が NULL の行を暗黙に集合から除外します(NULL との比較は真でも偽でもなく unknown)。 「消したはずが残る/更新したはずが漏れる」の多くはこれ。 NULL を含めたいなら IS NULL を明示します。SELECT COUNT(*) で集合の要素数が想定どおりか検算します。WHERE ... AND status <> '完了' 等で対象集合を狭める)にしておくと、リトライやバッチ再実行に強くなります。