Skip to content

Commit 6d83d3a

Browse files
authored
Merge pull request #204 from myoshi2891/dev-from-macmini
SQL: Basic update 627. Swap Salary
2 parents 8d2ad27 + 6cf55b2 commit 6d83d3a

3 files changed

Lines changed: 509 additions & 0 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "c5d028aa",
6+
"metadata": {},
7+
"source": [
8+
"# MySQL 8.0.40\n",
9+
"\n",
10+
"## 0) 前提\n",
11+
"\n",
12+
"* エンジン: **MySQL 8**\n",
13+
"* 並び順: 任意(`ORDER BY` なし)\n",
14+
"* `NOT IN` は使用しない\n",
15+
"* 判定は **ID 基準**、表示は仕様どおりの列名と順序(`id, name, sex, salary`)\n",
16+
"\n",
17+
"## 1) 問題\n",
18+
"\n",
19+
"* `Salary.sex` の `'m'` / `'f'` を **単一の UPDATE 文**で入れ替える(中間テーブル・SELECT 不可)\n",
20+
"* 入力テーブル例: `Salary(id INT PK, name VARCHAR, sex ENUM('m','f'), salary INT)`\n",
21+
"* 出力仕様: `Salary` 全行で `sex` が `'m' <-> 'f'` に反転していること(他列は不変)\n",
22+
"\n",
23+
"## 2) 最適解(単一クエリ)\n",
24+
"\n",
25+
"> **CASE 一発更新**。ENUM でも文字列比較で問題なし。\n",
26+
"\n",
27+
"```sql\n",
28+
"UPDATE Salary\n",
29+
"SET sex = CASE sex\n",
30+
" WHEN 'm' THEN 'f'\n",
31+
" ELSE 'm'\n",
32+
" END;\n",
33+
"\n",
34+
"Runtime 231 ms\n",
35+
"Beats 59.46%\n",
36+
"\n",
37+
"```\n",
38+
"\n",
39+
"* 単一トランザクションで全行を反転。\n",
40+
"* `sex` が `'m'` / `'f'` 想定のため `ELSE 'm'` ではなく `ELSE 'm'|'f'` 判定の汎用性を残すために `ELSE 'm'` とせず **`ELSE 'm'` ではなく上記のように包括**(本テーブル前提では実質 `'f'` )。\n",
41+
"\n",
42+
"## 3) 代替解\n",
43+
"\n",
44+
"> 関数ベースで分岐をさらに短く。いずれも **単一 UPDATE**。\n",
45+
"\n",
46+
"```sql\n",
47+
"-- A) IF 版(読みやすさ重視)\n",
48+
"UPDATE Salary\n",
49+
"SET sex = IF(sex = 'm', 'f', 'm');\n",
50+
"\n",
51+
"Runtime 235 ms\n",
52+
"Beats 51.52%\n",
53+
"\n",
54+
"-- B) FIELD/ELT 版(対称性重視)\n",
55+
"UPDATE Salary\n",
56+
"SET sex = ELT(FIELD(sex, 'm','f'), 'f','m');\n",
57+
"\n",
58+
"Runtime 238 ms\n",
59+
"Beats 46.32%\n",
60+
"```\n",
61+
"\n",
62+
"## 4) 要点解説\n",
63+
"\n",
64+
"* **ENUM でも文字列代入でOK**:`ENUM('m','f')` は `'m'` / `'f'` のいずれかのみ許容。CASE/IF の結果がそのいずれかなら整合。\n",
65+
"* **中間テーブル・SELECT 不要**:全行同時に反転しても相互依存はなく、レース条件なし。\n",
66+
"* **インデックス影響**:`sex` にインデックスがあっても UPDATE 自体は発生するが、全表更新のため恩恵は限定的。バッチ時間はテーブルサイズ依存。\n",
67+
"* **NULL 取り扱い**:`NULL` を許可していない想定(問題文より)。許可される設計なら `CASE` に `WHEN sex IS NULL THEN NULL` を足す。\n",
68+
"\n",
69+
"## 5) 計算量(概算)\n",
70+
"\n",
71+
"* 全表更新で **O(N)**。I/O はページ数とログ設定(binlog, undo)に依存。\n",
72+
"\n",
73+
"## 6) 図解(Mermaid 超保守版)\n",
74+
"\n",
75+
"```mermaid\n",
76+
"flowchart TD\n",
77+
" A[Salary テーブル] --> B[単一 UPDATE で sex を反転]\n",
78+
" B --> C[列: id name sex salary]\n",
79+
" C --> D[出力: sex が m/f 入れ替わり 他列不変]\n",
80+
"```\n",
81+
"結論:その差は誤差です。\n",
82+
"この手の「全件同一列更新・2 値トグル」は I/O(ページ読み書き・undo/redo/binlog)支配で、`CASE` / `IF` / `ELT+FIELD` の式評価差はほぼ出ません。提示の 231–238ms の差は計測ノイズと見てよいです。\n",
83+
"\n",
84+
"実務での“改善の余地”は以下の観点です(**クエリ自体はすでに最適級**)。\n",
85+
"\n",
86+
"## 速さそのものを上げる余地(限定的)\n",
87+
"\n",
88+
"* **式は読みやすさ優先で OK**\n",
89+
" `UPDATE Salary SET sex = IF(sex='m','f','m');` を推奨。最短・明快。速度差は出ません。\n",
90+
"* (小ネタ)`ELT(FIELD(...))` や ASCII 計算でのトグルは書けますが、**可読性低下のデメリットが勝ち**。ベンチ差もほぼゼロです。\n",
91+
"\n",
92+
"## 実運用の安全性・副作用を最適化\n",
93+
"\n",
94+
"* **必要行だけ更新**(将来、混在があり得る場合)\n",
95+
"\n",
96+
"```sql\n",
97+
"UPDATE Salary\n",
98+
"SET sex = IF(sex='m','f','m')\n",
99+
"WHERE sex IN ('m','f'); -- 実質全件だが「無関係行」除外の保険\n",
100+
"\n",
101+
"Runtime 229 ms\n",
102+
"Beats 63.30%\n",
103+
"\n",
104+
"```\n",
105+
"\n",
106+
"変更不要行がある環境では I/O を減らせます。\n",
107+
"* **大規模テーブルなら分割更新でロック/ログ圧を緩和**(総時間は増えるが可用性向上)\n",
108+
"\n",
109+
"```sql\n",
110+
"-- 例:PK でレンジ刻み\n",
111+
"UPDATE Salary SET sex = IF(sex='m','f','m') WHERE id BETWEEN ? AND ?;\n",
112+
"```\n",
113+
"* **影響最小化の運用**\n",
114+
" トランザクションで囲む、ピーク外に実施、binlog/redo の容量監視、適切な `innodb_flush_log_at_trx_commit`・レプリカ遅延観測など。\n",
115+
"\n",
116+
"## スキーマレベルでの抜本策(要変更可の場合)\n",
117+
"\n",
118+
"* 性別を `ENUM('m','f')` ではなく **`TINYINT(1)` or `BIT(1)`** にすると\n",
119+
" 将来のトグルは `UPDATE ... SET sex = 1 - sex;` と**最小計算・最小転送**で済みます(ただしアプリ側で 0/1 ↔ 表示文字のマッピングが必要)。\n",
120+
"\n",
121+
"---\n",
122+
"\n",
123+
"### 結局どれを使う?\n",
124+
"\n",
125+
"可読性・保守性で **IF 版**がベストです。速度はもう頭打ちなので、**運用(ロック・ログ・時間帯)最適化**に寄せるのがビジネス的に賢い選択です。\n",
126+
"\n"
127+
]
128+
}
129+
],
130+
"metadata": {
131+
"language_info": {
132+
"name": "python"
133+
}
134+
},
135+
"nbformat": 4,
136+
"nbformat_minor": 5
137+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "7ce084f6",
6+
"metadata": {},
7+
"source": [
8+
"# Pandas 2.2.2用\n",
9+
"\n",
10+
"## 0) 前提\n",
11+
"\n",
12+
"* 環境: **Python 3.10.15 / pandas 2.2.2**\n",
13+
"* **指定シグネチャ厳守**\n",
14+
"* I/O 禁止、不要な `print` や `sort_values` 禁止\n",
15+
"\n",
16+
"## 1) 問題\n",
17+
"\n",
18+
"* `Salary.sex` の `'m'` と `'f'` を **単一操作**で相互に入れ替える\n",
19+
"* 入力 DF: `Salary(id: int, name: str, sex: {'m','f'}, salary: int)`\n",
20+
"* 出力: 列名・順序そのまま(`['id','name','sex','salary']`)、`sex` が `'m' <-> 'f'` に反転。他列は不変\n",
21+
"\n",
22+
"## 2) 実装(指定シグネチャ厳守)\n",
23+
"\n",
24+
"> 列最小化 → ベクトル置換(`map`)→ 条件ガード(`isin`)で安全に反転。`m/f` 以外(将来の拡張や `NaN`)は温存。\n",
25+
"\n",
26+
"```python\n",
27+
"import pandas as pd\n",
28+
"\n",
29+
"def swap_sex(Salary: pd.DataFrame) -> pd.DataFrame:\n",
30+
" \"\"\"\n",
31+
" Returns:\n",
32+
" pd.DataFrame: 列名と順序は ['id', 'name', 'sex', 'salary']\n",
33+
" \"\"\"\n",
34+
" # 列最小化(順序保持)\n",
35+
" out = Salary[[\"id\", \"name\", \"sex\", \"salary\"]].copy()\n",
36+
"\n",
37+
" # m/f のみを対象にトグル。その他の値(存在すれば)はそのまま温存\n",
38+
" mask = out[\"sex\"].isin([\"m\", \"f\"])\n",
39+
" out.loc[mask, \"sex\"] = out.loc[mask, \"sex\"].map({\"m\": \"f\", \"f\": \"m\"}).values\n",
40+
"\n",
41+
" return out\n",
42+
"\n",
43+
"Analyze Complexity\n",
44+
"Runtime 267 ms\n",
45+
"Beats 36.94%\n",
46+
"Memory 66.92 MB\n",
47+
"Beats 7.25%\n",
48+
"\n",
49+
"```\n",
50+
"\n",
51+
"* ポイント: `where` でも同等に書けます(可読性はお好みで)。\n",
52+
"\n",
53+
" ```python\n",
54+
" out[\"sex\"] = out[\"sex\"].where(~mask, out[\"sex\"].map({\"m\": \"f\", \"f\": \"m\"}))\n",
55+
" ```\n",
56+
"\n",
57+
"## 3) アルゴリズム説明\n",
58+
"\n",
59+
"* 使用 API: `DataFrame.__getitem__`(列選択), `copy`, `Series.isin`, `Series.map`, `DataFrame.loc`\n",
60+
"* **NULL / 重複 / 型**:\n",
61+
"\n",
62+
" * `map` は辞書外の値を `NaN` にしがち → 事前に `isin` で対象を限定して **非対象は温存**。\n",
63+
" * 行の重複は本問題では無関係(全行独立変換)。\n",
64+
" * 列型は `object`/`string[pyarrow]` いずれでも動作。`Categorical` の場合はカテゴリに `'m','f'` がある前提。\n",
65+
"\n",
66+
"## 4) 計算量(概算)\n",
67+
"\n",
68+
"* 全体 **O(N)**、追加メモリは `sex` 列相当の一時 Series(マスク+置換分)\n",
69+
"\n",
70+
"## 5) 図解(Mermaid 超保守版)\n",
71+
"\n",
72+
"```mermaid\n",
73+
"flowchart TD\n",
74+
" A[入力 データフレーム Salary] --> B[列最小化 id name sex salary]\n",
75+
" B --> C[\"mask で sex in {m f} 抽出\"]\n",
76+
" C --> D[map で m↔f 置換 非対象は温存]\n",
77+
" D --> E[出力 仕様列のみ]\n",
78+
"```\n",
79+
"\n",
80+
"# Pandas 2.2.2用\n",
81+
"\n",
82+
"## 0) 前提\n",
83+
"\n",
84+
"* 環境: **Python 3.10.15 / pandas 2.2.2**\n",
85+
"* **指定シグネチャ厳守**\n",
86+
"* I/O 禁止、不要な `print` や `sort_values` 禁止\n",
87+
"\n",
88+
"## 1) 問題\n",
89+
"\n",
90+
"* `Salary.sex` の `'m'` と `'f'` を **単一操作**で相互に入れ替える\n",
91+
"* 入力 DF: `Salary(id: int, name: str, sex: {'m','f'}, salary: int)`\n",
92+
"* 出力: 列名・順序そのまま(`['id','name','sex','salary']`)、`sex` が `'m' <-> 'f'` に反転。他列は不変\n",
93+
"\n",
94+
"## 2) 実装(指定シグネチャ厳守)\n",
95+
"\n",
96+
"> **メモリ削減&高速化**:`NumPy` 配列に直アクセスし、対象行のみを一括更新。\n",
97+
"> 文字列列は `object`/`string` を想定。`Categorical` の場合は **コード反転**が最速です。\n",
98+
"\n",
99+
"```python\n",
100+
"import pandas as pd\n",
101+
"import numpy as np\n",
102+
"\n",
103+
"def swap_sex(Salary: pd.DataFrame) -> pd.DataFrame:\n",
104+
" \"\"\"\n",
105+
" Returns:\n",
106+
" pd.DataFrame: 列名と順序は ['id', 'name', 'sex', 'salary']\n",
107+
" \"\"\"\n",
108+
" cols = [\"id\", \"name\", \"sex\", \"salary\"]\n",
109+
" # DataFrame 全体の copy を避け、必要列だけを参照(CoW で不要コピーを抑制)\n",
110+
" base = Salary[cols]\n",
111+
"\n",
112+
" s = base[\"sex\"]\n",
113+
"\n",
114+
" # 1) Categorical の場合はコード入替が最小メモリ・最速\n",
115+
" if pd.api.types.is_categorical_dtype(s):\n",
116+
" codes = s.cat.codes.to_numpy(copy=False) # -1 は NaN\n",
117+
" mask = codes >= 0 # 実データのみ\n",
118+
" # 'm','f' の2値想定なので 0/1 を反転\n",
119+
" new_codes = codes.copy()\n",
120+
" new_codes[mask] = 1 - new_codes[mask]\n",
121+
" new_sex = pd.Categorical.from_codes(\n",
122+
" new_codes, categories=s.cat.categories, ordered=s.cat.ordered\n",
123+
" )\n",
124+
" # assign で列差分だけ差し替え(他列はブロック共有でメモリ圧縮)\n",
125+
" return base.assign(sex=new_sex)\n",
126+
"\n",
127+
" # 2) 文字列/オブジェクト列:配列ビューで一括置換(辞書 map より中間オブジェクトが少ない)\n",
128+
" arr = s.to_numpy(copy=False) # 参照(書込可能でない場合もあるため後で安全に再代入)\n",
129+
" mask = (arr == \"m\") | (arr == \"f\") # 対象行だけ\n",
130+
" if mask.any():\n",
131+
" tmp = arr[mask]\n",
132+
" swapped = np.where(tmp == \"m\", \"f\", \"m\")\n",
133+
" # 再代入で dtype を保ちながら差し替え\n",
134+
" return base.assign(sex=pd.Series(arr, index=s.index).where(~mask, swapped))\n",
135+
" else:\n",
136+
" # m/f が無いならそのまま返却\n",
137+
" return base\n",
138+
"\n",
139+
"Analyze Complexity\n",
140+
"Runtime 246 ms\n",
141+
"Beats 79.21%\n",
142+
"Memory 66.92 MB\n",
143+
"Beats 7.25%\n",
144+
"\n",
145+
"```\n",
146+
"\n",
147+
"### 追加の実務メモ(任意)\n",
148+
"\n",
149+
"* `sex` を **Categorical**(例: `pd.CategoricalDtype(['m','f'])`)にしておくと、**メモリ削減**かつ本トグルが最小コストになります。\n",
150+
"* `string[pyarrow]` でも良いですが、2 値なら `Categorical` がより省メモリ。\n",
151+
"\n",
152+
"## 3) アルゴリズム説明\n",
153+
"\n",
154+
"* 使用 API:\n",
155+
"\n",
156+
" * `DataFrame.__getitem__` で **列最小化**\n",
157+
" * `Series.to_numpy(copy=False)` で **ゼロコピー参照**(必要時のみ再割当)\n",
158+
" * `numpy.where` による **ベクトル条件置換**\n",
159+
" * `Series.cat.codes` / `Categorical.from_codes` による **カテゴリコード反転**\n",
160+
" * `DataFrame.assign` で **差分列のみ差し替え**(他列ブロックは共有されやすく、メモリ節約)\n",
161+
"* **NULL / 異常値**:\n",
162+
"\n",
163+
" * `Categorical` の `codes == -1`(= NaN)は非対象として温存。\n",
164+
" * 文字列系でも `mask` 外は温存(`NaN` や `'u'` などが混ざっても壊さない)。\n",
165+
"\n",
166+
"## 4) 計算量(概算)\n",
167+
"\n",
168+
"* 時間: 全体 **O(N)**(マスク作成+部分置換)\n",
169+
"* 追加メモリ: **O(K)**(`K` は `sex ∈ {'m','f'}` の行数)\n",
170+
"\n",
171+
" * `map` 方式より一時 Series が小さく、**ピークメモリ削減**が見込めます。\n",
172+
" * `Categorical` の場合は **整数配列のみ**を一時利用。\n",
173+
"\n",
174+
"## 5) 図解(Mermaid 超保守版)\n",
175+
"\n",
176+
"```mermaid\n",
177+
"flowchart TD\n",
178+
" A[入力 DF Salary] --> B[列最小化 id name sex salary]\n",
179+
" B --> C[sex が Categorical なら codes を反転]\n",
180+
" B --> D[文字列なら mask を作成し where で部分置換]\n",
181+
" C --> E[assign で sex 差替え]\n",
182+
" D --> E[assign で sex 差替え]\n",
183+
" E --> F[出力 列順は id name sex salary]\n",
184+
"```\n",
185+
"\n",
186+
"---\n",
187+
"\n",
188+
"### さらなる微調整(ベンチ改善の現実解)\n",
189+
"\n",
190+
"* **前処理で `sex` を Categorical にする**:\n",
191+
"\n",
192+
" ```python\n",
193+
" Salary[\"sex\"] = pd.Categorical(Salary[\"sex\"], categories=[\"m\",\"f\"])\n",
194+
" ```\n",
195+
"\n",
196+
" 以後のトグルは **整数反転のみ**になり、**Runtime と Memory の双方が安定して改善**します。\n",
197+
"* **巨大 DF** ならバッチ処理(例: `np.array_split` でインデックス分割→ `concat`)でピークメモリを抑制可能(総時間は微増)。\n",
198+
"* **JIT** は不要。I/O も禁止条件のため、**今回の改良余地は配列直操作と Categorical 化**が最も効きます。\n"
199+
]
200+
}
201+
],
202+
"metadata": {
203+
"language_info": {
204+
"name": "python"
205+
}
206+
},
207+
"nbformat": 4,
208+
"nbformat_minor": 5
209+
}

0 commit comments

Comments
 (0)