Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions SQL/Leetcode/Basic update/leetcode/Swap_Salary_mysql.ipynb
Original file line number Diff line number Diff line change
@@ -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
}
209 changes: 209 additions & 0 deletions SQL/Leetcode/Basic update/leetcode/Swap_Salary_pandas.ipynb
Original file line number Diff line number Diff line change
@@ -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
}
Loading