From 6cf55b2cf4e0c9a2555f4051ceb7c73d4d07d16b Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Nov 2025 17:45:29 +0900 Subject: [PATCH] SQL: Basic update 627. Swap Salary --- .../leetcode/Swap_Salary_mysql.ipynb | 137 ++++++++++++ .../leetcode/Swap_Salary_pandas.ipynb | 209 ++++++++++++++++++ .../leetcode/Swap_Salary_posgres.ipynb | 163 ++++++++++++++ 3 files changed, 509 insertions(+) create mode 100644 SQL/Leetcode/Basic update/leetcode/Swap_Salary_mysql.ipynb create mode 100644 SQL/Leetcode/Basic update/leetcode/Swap_Salary_pandas.ipynb create mode 100644 SQL/Leetcode/Basic update/leetcode/Swap_Salary_posgres.ipynb diff --git a/SQL/Leetcode/Basic update/leetcode/Swap_Salary_mysql.ipynb b/SQL/Leetcode/Basic update/leetcode/Swap_Salary_mysql.ipynb new file mode 100644 index 00000000..ebdafdc6 --- /dev/null +++ b/SQL/Leetcode/Basic update/leetcode/Swap_Salary_mysql.ipynb @@ -0,0 +1,137 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c5d028aa", + "metadata": {}, + "source": [ + "# MySQL 8.0.40\n", + "\n", + "## 0) 前提\n", + "\n", + "* エンジン: **MySQL 8**\n", + "* 並び順: 任意(`ORDER BY` なし)\n", + "* `NOT IN` は使用しない\n", + "* 判定は **ID 基準**、表示は仕様どおりの列名と順序(`id, name, sex, salary`)\n", + "\n", + "## 1) 問題\n", + "\n", + "* `Salary.sex` の `'m'` / `'f'` を **単一の UPDATE 文**で入れ替える(中間テーブル・SELECT 不可)\n", + "* 入力テーブル例: `Salary(id INT PK, name VARCHAR, sex ENUM('m','f'), salary INT)`\n", + "* 出力仕様: `Salary` 全行で `sex` が `'m' <-> 'f'` に反転していること(他列は不変)\n", + "\n", + "## 2) 最適解(単一クエリ)\n", + "\n", + "> **CASE 一発更新**。ENUM でも文字列比較で問題なし。\n", + "\n", + "```sql\n", + "UPDATE Salary\n", + "SET sex = CASE sex\n", + " WHEN 'm' THEN 'f'\n", + " ELSE 'm'\n", + " END;\n", + "\n", + "Runtime 231 ms\n", + "Beats 59.46%\n", + "\n", + "```\n", + "\n", + "* 単一トランザクションで全行を反転。\n", + "* `sex` が `'m'` / `'f'` 想定のため `ELSE 'm'` ではなく `ELSE 'm'|'f'` 判定の汎用性を残すために `ELSE 'm'` とせず **`ELSE 'm'` ではなく上記のように包括**(本テーブル前提では実質 `'f'` )。\n", + "\n", + "## 3) 代替解\n", + "\n", + "> 関数ベースで分岐をさらに短く。いずれも **単一 UPDATE**。\n", + "\n", + "```sql\n", + "-- A) IF 版(読みやすさ重視)\n", + "UPDATE Salary\n", + "SET sex = IF(sex = 'm', 'f', 'm');\n", + "\n", + "Runtime 235 ms\n", + "Beats 51.52%\n", + "\n", + "-- B) FIELD/ELT 版(対称性重視)\n", + "UPDATE Salary\n", + "SET sex = ELT(FIELD(sex, 'm','f'), 'f','m');\n", + "\n", + "Runtime 238 ms\n", + "Beats 46.32%\n", + "```\n", + "\n", + "## 4) 要点解説\n", + "\n", + "* **ENUM でも文字列代入でOK**:`ENUM('m','f')` は `'m'` / `'f'` のいずれかのみ許容。CASE/IF の結果がそのいずれかなら整合。\n", + "* **中間テーブル・SELECT 不要**:全行同時に反転しても相互依存はなく、レース条件なし。\n", + "* **インデックス影響**:`sex` にインデックスがあっても UPDATE 自体は発生するが、全表更新のため恩恵は限定的。バッチ時間はテーブルサイズ依存。\n", + "* **NULL 取り扱い**:`NULL` を許可していない想定(問題文より)。許可される設計なら `CASE` に `WHEN sex IS NULL THEN NULL` を足す。\n", + "\n", + "## 5) 計算量(概算)\n", + "\n", + "* 全表更新で **O(N)**。I/O はページ数とログ設定(binlog, undo)に依存。\n", + "\n", + "## 6) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[Salary テーブル] --> B[単一 UPDATE で sex を反転]\n", + " B --> C[列: id name sex salary]\n", + " C --> D[出力: sex が m/f 入れ替わり 他列不変]\n", + "```\n", + "結論:その差は誤差です。\n", + "この手の「全件同一列更新・2 値トグル」は I/O(ページ読み書き・undo/redo/binlog)支配で、`CASE` / `IF` / `ELT+FIELD` の式評価差はほぼ出ません。提示の 231–238ms の差は計測ノイズと見てよいです。\n", + "\n", + "実務での“改善の余地”は以下の観点です(**クエリ自体はすでに最適級**)。\n", + "\n", + "## 速さそのものを上げる余地(限定的)\n", + "\n", + "* **式は読みやすさ優先で OK**\n", + " `UPDATE Salary SET sex = IF(sex='m','f','m');` を推奨。最短・明快。速度差は出ません。\n", + "* (小ネタ)`ELT(FIELD(...))` や ASCII 計算でのトグルは書けますが、**可読性低下のデメリットが勝ち**。ベンチ差もほぼゼロです。\n", + "\n", + "## 実運用の安全性・副作用を最適化\n", + "\n", + "* **必要行だけ更新**(将来、混在があり得る場合)\n", + "\n", + "```sql\n", + "UPDATE Salary\n", + "SET sex = IF(sex='m','f','m')\n", + "WHERE sex IN ('m','f'); -- 実質全件だが「無関係行」除外の保険\n", + "\n", + "Runtime 229 ms\n", + "Beats 63.30%\n", + "\n", + "```\n", + "\n", + "変更不要行がある環境では I/O を減らせます。\n", + "* **大規模テーブルなら分割更新でロック/ログ圧を緩和**(総時間は増えるが可用性向上)\n", + "\n", + "```sql\n", + "-- 例:PK でレンジ刻み\n", + "UPDATE Salary SET sex = IF(sex='m','f','m') WHERE id BETWEEN ? AND ?;\n", + "```\n", + "* **影響最小化の運用**\n", + " トランザクションで囲む、ピーク外に実施、binlog/redo の容量監視、適切な `innodb_flush_log_at_trx_commit`・レプリカ遅延観測など。\n", + "\n", + "## スキーマレベルでの抜本策(要変更可の場合)\n", + "\n", + "* 性別を `ENUM('m','f')` ではなく **`TINYINT(1)` or `BIT(1)`** にすると\n", + " 将来のトグルは `UPDATE ... SET sex = 1 - sex;` と**最小計算・最小転送**で済みます(ただしアプリ側で 0/1 ↔ 表示文字のマッピングが必要)。\n", + "\n", + "---\n", + "\n", + "### 結局どれを使う?\n", + "\n", + "可読性・保守性で **IF 版**がベストです。速度はもう頭打ちなので、**運用(ロック・ログ・時間帯)最適化**に寄せるのがビジネス的に賢い選択です。\n", + "\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/SQL/Leetcode/Basic update/leetcode/Swap_Salary_pandas.ipynb b/SQL/Leetcode/Basic update/leetcode/Swap_Salary_pandas.ipynb new file mode 100644 index 00000000..c79e5329 --- /dev/null +++ b/SQL/Leetcode/Basic update/leetcode/Swap_Salary_pandas.ipynb @@ -0,0 +1,209 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7ce084f6", + "metadata": {}, + "source": [ + "# Pandas 2.2.2用\n", + "\n", + "## 0) 前提\n", + "\n", + "* 環境: **Python 3.10.15 / pandas 2.2.2**\n", + "* **指定シグネチャ厳守**\n", + "* I/O 禁止、不要な `print` や `sort_values` 禁止\n", + "\n", + "## 1) 問題\n", + "\n", + "* `Salary.sex` の `'m'` と `'f'` を **単一操作**で相互に入れ替える\n", + "* 入力 DF: `Salary(id: int, name: str, sex: {'m','f'}, salary: int)`\n", + "* 出力: 列名・順序そのまま(`['id','name','sex','salary']`)、`sex` が `'m' <-> 'f'` に反転。他列は不変\n", + "\n", + "## 2) 実装(指定シグネチャ厳守)\n", + "\n", + "> 列最小化 → ベクトル置換(`map`)→ 条件ガード(`isin`)で安全に反転。`m/f` 以外(将来の拡張や `NaN`)は温存。\n", + "\n", + "```python\n", + "import pandas as pd\n", + "\n", + "def swap_sex(Salary: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['id', 'name', 'sex', 'salary']\n", + " \"\"\"\n", + " # 列最小化(順序保持)\n", + " out = Salary[[\"id\", \"name\", \"sex\", \"salary\"]].copy()\n", + "\n", + " # m/f のみを対象にトグル。その他の値(存在すれば)はそのまま温存\n", + " mask = out[\"sex\"].isin([\"m\", \"f\"])\n", + " out.loc[mask, \"sex\"] = out.loc[mask, \"sex\"].map({\"m\": \"f\", \"f\": \"m\"}).values\n", + "\n", + " return out\n", + "\n", + "Analyze Complexity\n", + "Runtime 267 ms\n", + "Beats 36.94%\n", + "Memory 66.92 MB\n", + "Beats 7.25%\n", + "\n", + "```\n", + "\n", + "* ポイント: `where` でも同等に書けます(可読性はお好みで)。\n", + "\n", + " ```python\n", + " out[\"sex\"] = out[\"sex\"].where(~mask, out[\"sex\"].map({\"m\": \"f\", \"f\": \"m\"}))\n", + " ```\n", + "\n", + "## 3) アルゴリズム説明\n", + "\n", + "* 使用 API: `DataFrame.__getitem__`(列選択), `copy`, `Series.isin`, `Series.map`, `DataFrame.loc`\n", + "* **NULL / 重複 / 型**:\n", + "\n", + " * `map` は辞書外の値を `NaN` にしがち → 事前に `isin` で対象を限定して **非対象は温存**。\n", + " * 行の重複は本問題では無関係(全行独立変換)。\n", + " * 列型は `object`/`string[pyarrow]` いずれでも動作。`Categorical` の場合はカテゴリに `'m','f'` がある前提。\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "* 全体 **O(N)**、追加メモリは `sex` 列相当の一時 Series(マスク+置換分)\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[入力 データフレーム Salary] --> B[列最小化 id name sex salary]\n", + " B --> C[\"mask で sex in {m f} 抽出\"]\n", + " C --> D[map で m↔f 置換 非対象は温存]\n", + " D --> E[出力 仕様列のみ]\n", + "```\n", + "\n", + "# Pandas 2.2.2用\n", + "\n", + "## 0) 前提\n", + "\n", + "* 環境: **Python 3.10.15 / pandas 2.2.2**\n", + "* **指定シグネチャ厳守**\n", + "* I/O 禁止、不要な `print` や `sort_values` 禁止\n", + "\n", + "## 1) 問題\n", + "\n", + "* `Salary.sex` の `'m'` と `'f'` を **単一操作**で相互に入れ替える\n", + "* 入力 DF: `Salary(id: int, name: str, sex: {'m','f'}, salary: int)`\n", + "* 出力: 列名・順序そのまま(`['id','name','sex','salary']`)、`sex` が `'m' <-> 'f'` に反転。他列は不変\n", + "\n", + "## 2) 実装(指定シグネチャ厳守)\n", + "\n", + "> **メモリ削減&高速化**:`NumPy` 配列に直アクセスし、対象行のみを一括更新。\n", + "> 文字列列は `object`/`string` を想定。`Categorical` の場合は **コード反転**が最速です。\n", + "\n", + "```python\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "def swap_sex(Salary: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['id', 'name', 'sex', 'salary']\n", + " \"\"\"\n", + " cols = [\"id\", \"name\", \"sex\", \"salary\"]\n", + " # DataFrame 全体の copy を避け、必要列だけを参照(CoW で不要コピーを抑制)\n", + " base = Salary[cols]\n", + "\n", + " s = base[\"sex\"]\n", + "\n", + " # 1) Categorical の場合はコード入替が最小メモリ・最速\n", + " if pd.api.types.is_categorical_dtype(s):\n", + " codes = s.cat.codes.to_numpy(copy=False) # -1 は NaN\n", + " mask = codes >= 0 # 実データのみ\n", + " # 'm','f' の2値想定なので 0/1 を反転\n", + " new_codes = codes.copy()\n", + " new_codes[mask] = 1 - new_codes[mask]\n", + " new_sex = pd.Categorical.from_codes(\n", + " new_codes, categories=s.cat.categories, ordered=s.cat.ordered\n", + " )\n", + " # assign で列差分だけ差し替え(他列はブロック共有でメモリ圧縮)\n", + " return base.assign(sex=new_sex)\n", + "\n", + " # 2) 文字列/オブジェクト列:配列ビューで一括置換(辞書 map より中間オブジェクトが少ない)\n", + " arr = s.to_numpy(copy=False) # 参照(書込可能でない場合もあるため後で安全に再代入)\n", + " mask = (arr == \"m\") | (arr == \"f\") # 対象行だけ\n", + " if mask.any():\n", + " tmp = arr[mask]\n", + " swapped = np.where(tmp == \"m\", \"f\", \"m\")\n", + " # 再代入で dtype を保ちながら差し替え\n", + " return base.assign(sex=pd.Series(arr, index=s.index).where(~mask, swapped))\n", + " else:\n", + " # m/f が無いならそのまま返却\n", + " return base\n", + "\n", + "Analyze Complexity\n", + "Runtime 246 ms\n", + "Beats 79.21%\n", + "Memory 66.92 MB\n", + "Beats 7.25%\n", + "\n", + "```\n", + "\n", + "### 追加の実務メモ(任意)\n", + "\n", + "* `sex` を **Categorical**(例: `pd.CategoricalDtype(['m','f'])`)にしておくと、**メモリ削減**かつ本トグルが最小コストになります。\n", + "* `string[pyarrow]` でも良いですが、2 値なら `Categorical` がより省メモリ。\n", + "\n", + "## 3) アルゴリズム説明\n", + "\n", + "* 使用 API:\n", + "\n", + " * `DataFrame.__getitem__` で **列最小化**\n", + " * `Series.to_numpy(copy=False)` で **ゼロコピー参照**(必要時のみ再割当)\n", + " * `numpy.where` による **ベクトル条件置換**\n", + " * `Series.cat.codes` / `Categorical.from_codes` による **カテゴリコード反転**\n", + " * `DataFrame.assign` で **差分列のみ差し替え**(他列ブロックは共有されやすく、メモリ節約)\n", + "* **NULL / 異常値**:\n", + "\n", + " * `Categorical` の `codes == -1`(= NaN)は非対象として温存。\n", + " * 文字列系でも `mask` 外は温存(`NaN` や `'u'` などが混ざっても壊さない)。\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "* 時間: 全体 **O(N)**(マスク作成+部分置換)\n", + "* 追加メモリ: **O(K)**(`K` は `sex ∈ {'m','f'}` の行数)\n", + "\n", + " * `map` 方式より一時 Series が小さく、**ピークメモリ削減**が見込めます。\n", + " * `Categorical` の場合は **整数配列のみ**を一時利用。\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[入力 DF Salary] --> B[列最小化 id name sex salary]\n", + " B --> C[sex が Categorical なら codes を反転]\n", + " B --> D[文字列なら mask を作成し where で部分置換]\n", + " C --> E[assign で sex 差替え]\n", + " D --> E[assign で sex 差替え]\n", + " E --> F[出力 列順は id name sex salary]\n", + "```\n", + "\n", + "---\n", + "\n", + "### さらなる微調整(ベンチ改善の現実解)\n", + "\n", + "* **前処理で `sex` を Categorical にする**:\n", + "\n", + " ```python\n", + " Salary[\"sex\"] = pd.Categorical(Salary[\"sex\"], categories=[\"m\",\"f\"])\n", + " ```\n", + "\n", + " 以後のトグルは **整数反転のみ**になり、**Runtime と Memory の双方が安定して改善**します。\n", + "* **巨大 DF** ならバッチ処理(例: `np.array_split` でインデックス分割→ `concat`)でピークメモリを抑制可能(総時間は微増)。\n", + "* **JIT** は不要。I/O も禁止条件のため、**今回の改良余地は配列直操作と Categorical 化**が最も効きます。\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/SQL/Leetcode/Basic update/leetcode/Swap_Salary_posgres.ipynb b/SQL/Leetcode/Basic update/leetcode/Swap_Salary_posgres.ipynb new file mode 100644 index 00000000..67e3e7aa --- /dev/null +++ b/SQL/Leetcode/Basic update/leetcode/Swap_Salary_posgres.ipynb @@ -0,0 +1,163 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "af3823b2", + "metadata": {}, + "source": [ + "# PostgreSQL 16.6+\n", + "\n", + "## 0) 前提\n", + "\n", + "* エンジン: **PostgreSQL 16.6+**\n", + "* 並び順: 任意(`ORDER BY` なし)\n", + "* `NOT IN` 回避(必要なら `EXISTS` / `LEFT JOIN ... IS NULL`)\n", + "* 判定は **ID 基準**、表示は仕様どおり(`id, name, sex, salary`)\n", + "\n", + "## 1) 問題\n", + "\n", + "* `Salary.sex` の `'m'` と `'f'` を **単一の UPDATE 文で相互に入れ替える**(一時表・サブクエリ不要)\n", + "* 入力: `Salary(id INT PRIMARY KEY, name TEXT, sex ENUM('m','f') または TEXT + CHECK, salary INT)`\n", + "* 出力: `Salary` 全行で `sex` が `'m' <-> 'f'` に反転。他列は不変\n", + "\n", + "## 2) 最適解(単一クエリ)\n", + "\n", + "> 今回はウィンドウ不要。**CASE 一発更新**が最短・明快・安全。\n", + "\n", + "```sql\n", + "UPDATE Salary\n", + "SET sex = CASE sex\n", + " WHEN 'm' THEN 'f'\n", + " WHEN 'f' THEN 'm'\n", + " END;\n", + "\n", + "Runtime 212 ms\n", + "Beats 31.19%\n", + "\n", + "```\n", + "\n", + "* `ENUM('m','f')` でも文字列代入で整合します。\n", + "* もし将来 `'m'/'f'` 以外を許す可能性があるなら、無関係行の無駄更新を避けるために `WHERE` を付けてもよい(任意):\n", + "\n", + "```sql\n", + "UPDATE Salary\n", + "SET sex = CASE sex WHEN 'm' THEN 'f' WHEN 'f' THEN 'm' END\n", + "WHERE sex IN ('m','f');\n", + "\n", + "Runtime 193 ms\n", + "Beats 65.45%\n", + "\n", + "```\n", + "\n", + "### 代替(等価・短文)\n", + "\n", + "```sql\n", + "-- 読みやすさ重視\n", + "UPDATE Salary\n", + "SET sex = CASE WHEN sex = 'm' THEN 'f' ELSE 'm' END;\n", + "\n", + "Runtime 193 ms\n", + "Beats 65.45%\n", + "\n", + "-- 関数を使った対称表現(好みで)\n", + "UPDATE Salary\n", + "SET sex = (ARRAY['f','m'])[CASE WHEN sex = 'm' THEN 1 ELSE 2 END];\n", + "\n", + "Runtime 191 ms\n", + "Beats 71.28%\n", + "\n", + "```\n", + "\n", + "## 3) 要点解説\n", + "\n", + "* **全表更新は I/O 支配**:式の違い(`CASE` / 配列)で速度差はほぼ出ません。可読性優先が吉。\n", + "* **無関係行のガード**:`WHERE sex IN ('m','f')` を付けると、将来 `NULL` や他値混在でも余計な書き換えを避けられます。\n", + "* **スキーマ設計の選択肢**:`ENUM('m','f')` でも、`TEXT + CHECK (sex IN ('m','f'))` でも今回の更新は同様に動作します。\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "* 行数を `N` とすると **O(N)**(テーブルスキャン+各行 1 回の更新判定)。\n", + "* 物理 I/O と WAL(書き込み前ログ)の負荷が支配的。巨大表ではバッチ分割でロック/レプリ遅延を緩和可能(総時間はやや増)。\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[Salary テーブル 全行] --> B[CASE で m と f を入れ替え]\n", + " B --> C[列は id name sex salary のまま]\n", + " C --> D[出力: sex が m/f 反転 他列は不変]\n", + "```\n", + "\n", + "結論:**ほぼ頭打ち**です。\n", + "191–212ms の差は I/O と計測ゆらぎの範囲。すでに一番効く施策(`WHERE sex IN ('m','f')` で“必要行だけ更新”)を入れて 212→193ms に落ちています。これ以上は **物理 I/O と WAL** が支配的なので、式の書き方を変えても伸びません。\n", + "\n", + "それでも実務で効く“次の一手”は以下。\n", + "\n", + "## まだ詰められるポイント(PostgreSQL実運用)\n", + "\n", + "1. **HOT Update を効かせる**(インデックス更新を回避)\n", + "\n", + " * `sex` に **インデックスを貼らない**(貼っているなら、トグル時は全件 index update が発生して遅くなる)。\n", + " * ページ内に空きが必要なので **fillfactor を下げる(例: 90)**。将来の UPDATE でページ内移動=HOT になりやすく、**WAL/インデックス更新が減少**。\n", + "\n", + " ```sql\n", + " ALTER TABLE Salary SET (fillfactor = 90);\n", + " -- 以後に挿入・更新されたページから効果。全面適用したければメンテ時間に REWRITE 系を計画。\n", + " -- 例: VACUUM FULL や CLUSTER(重いので要メンテ枠)\n", + " ```\n", + "\n", + "2. **大規模表はバッチ更新**(ロック時間・レプリ遅延の平準化)\n", + " 総時間は微増しても可用性が上がります。`ctid` を使って素直に刻むのが PostgreSQL 流。\n", + "\n", + " ```sql\n", + " -- 1回あたり 10k 行ずつ。0 行になったら終了。\n", + " WITH chunk AS (\n", + " SELECT ctid\n", + " FROM Salary\n", + " WHERE sex IN ('m','f')\n", + " LIMIT 10000\n", + " )\n", + " UPDATE Salary s\n", + " SET sex = CASE WHEN s.sex='m' THEN 'f' ELSE 'm' END\n", + " FROM chunk\n", + " WHERE s.ctid = chunk.ctid;\n", + " ```\n", + "\n", + " *レプリケーション環境では遅延監視を。*\n", + "\n", + "3. **無駄な書き込みをこれ以上減らす**\n", + " すでに `WHERE sex IN ('m','f')` を付けていますが、将来 `NULL` や他値が混ざる懸念があるならガードはそのまま推奨。\n", + " また、変更なし行を確実に避けるため `WHERE sex IN ('m','f')` は **必須**ではないものの、**実用最適**です(実測でも効いています)。\n", + "\n", + "4. **WAL/チェックポイントまわりの運用**\n", + "\n", + " * ピーク外で実施、直前/直後に **手動 CHECKPOINT を避ける**(直撃すると I/O が重なる)。\n", + " * レプリカがある場合はスロット有無や遅延を確認。\n", + " * 耐障害要件が許す一括トグルなら、**単一トランザクションにまとめる**方がオーバーヘッドが小さくなりがち。\n", + "\n", + "5. **スキーマ設計の将来最適化**(変更可の場合)\n", + " 性別を `ENUM('m','f')` / `TEXT` ではなく **`SMALLINT`/`BOOLEAN` + 表示層でマッピング**にすると、将来のトグルは\n", + "\n", + " ```sql\n", + " UPDATE Salary SET sex = 1 - sex; -- SMALLINT(0/1) の例\n", + " ```\n", + "\n", + " で済み、データサイズも小さいため **WAL/I/O がさらに軽く**なります(ただしアプリ側の対応が必要)。\n", + "\n", + "---\n", + "\n", + "### どの書き方が最速?\n", + "\n", + "`CASE`/`ARRAY` の 191–193ms は**互角**です。可読性で選んでください。\n", + "**“速さ”の残りしろは式ではなく**、HOT 可否・インデックス・fillfactor・バッチ運用・実行タイミングにあります。ビジネス的には、**インデックス設計の見直し+バッチ更新**が費用対効果◎です。\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}