diff --git a/Mathematics/Algebra/HackerRank/Easy/Difference and Product/Difference_and_Product.ipynb b/Mathematics/Algebra/HackerRank/Easy/Difference and Product/Difference_and_Product.ipynb new file mode 100644 index 00000000..a9b6b4c2 --- /dev/null +++ b/Mathematics/Algebra/HackerRank/Easy/Difference and Product/Difference_and_Product.ipynb @@ -0,0 +1,357 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0805330f", + "metadata": {}, + "source": [ + "# Difference and Product - 絶対差と積で数える整数対\n", + "\n", + "* **プラットフォーム**: HackerRank\n", + "* **問題ID**: Difference and Product\n", + "\n", + "---\n", + "\n", + "## 目次\n", + "\n", + "* [概要](#overview)\n", + "* [アルゴリズム要点 (TL;DR)](#tldr)\n", + "* [図解](#figures)\n", + "* [証明のスケッチ](#proof)\n", + "* [計算量](#complexity)\n", + "* [Python 実装](#impl)\n", + "* [CPython 最適化ポイント](#cpython)\n", + "* [エッジケースと検証](#edgecases)\n", + "* [FAQ](#faq)\n", + "\n", + "---\n", + "\n", + "

概要

\n", + "\n", + "* **問題要約**: 整数 $d$ と $p$ が与えられる。$|x-y|=d$ かつ $xy=p$ を満たす**有序**整数対 $(x,y)$ の個数を求める。\n", + "* **入出力仕様(簡潔)**:\n", + "\n", + " * 入力: $T$ テスト、各行に $d\\ p$\n", + " * 出力: 各テストについて解の個数(整数)\n", + "* **関数シグネチャ(HackerRank 準拠・必須)**: `solve(d: int, p: int) -> int`\n", + "* **想定データ構造**: 単一の整数演算のみ(補助配列不要)\n", + "* **代表例**:\n", + "\n", + " * $d=1,\\ p=2 \\Rightarrow 4$\n", + " * $d=0,\\ p=4 \\Rightarrow 2$\n", + " * $d=-1,\\ p=1 \\Rightarrow 0$\n", + "\n", + "---\n", + "\n", + "

アルゴリズム要点 (TL;DR)

\n", + "\n", + "* 和差変数を導入する:$s=x+y,\\ t=x-y$。すると\n", + " $$\n", + " |t|=d \\quad\\text{かつ}\\quad xy=\\frac{s^2-t^2}{4}=p \\ \\Rightarrow\\ s^2=4p+d^2\n", + " $$\n", + "* $N=4p+d^2$ とおく。$N$ が**完全平方**でなければ解は $0$。\n", + "* $q=\\sqrt{N}$(整数)とすると、$s\\in{\\pm q}$、$t\\in{d,-d}$。\n", + "\n", + " * $q>0$ なら $s$ は $2$ 通り、$q=0$ なら $1$ 通り。\n", + " * $d>0$ なら $t$ は $2$ 通り、$d=0$ なら $1$ 通り。\n", + "* よって個数は\n", + " $$\n", + " \\bigl(1 + \\mathbf{1}*{q>0}\\bigr)\\times \\bigl(1 + \\mathbf{1}*{d>0}\\bigr)\n", + " $$\n", + "* ただし $d<0$ は $|x-y|=d$ を満たせないため常に $0$。\n", + "* **計算量**: 時間 $O(1)$、空間 $O(1)$(`math.isqrt` による完全平方判定)。\n", + "\n", + "式変形と「通り数の数え方」が腑に落ちるよう、ステップごとに具体化します。数式は KaTeX で再掲し、ミニ例も置きます。\n", + "\n", + "---\n", + "\n", + "## 補足解説:TL;DR の具体化\n", + "\n", + "### 1) なぜ和差変数にするのか\n", + "\n", + "元の条件は\n", + "\n", + "* 差の条件:$|x-y|=d$\n", + "* 積の条件:$xy=p$\n", + "\n", + "これを\n", + "$$\n", + "s=x+y,\\quad t=x-y\n", + "$$\n", + "に置き換えると、$x$ と $y$ を $s,t$ から\n", + "$$\n", + "x=\\frac{s+t}{2},\\quad y=\\frac{s-t}{2}\n", + "$$\n", + "で一意に復元できます(順序付きなので $(s,t)$ が変われば一般に $(x,y)$ も変わる)。\n", + "\n", + "差の条件はただちに $|t|=d$、つまり $t\\in{d,-d}$ と書けます。\n", + "\n", + "積の条件は\n", + "$$\n", + "xy=\\frac{(x+y)^2-(x-y)^2}{4}=\\frac{s^2-t^2}{4}=p\n", + "$$\n", + "で、これを $s$ について解けば\n", + "$$\n", + "s^2=t^2+4p\n", + "$$\n", + "ここで $|t|=d$ を代入すると\n", + "$$\n", + "s^2=4p+d^2\n", + "$$\n", + "\n", + "### 2) 「完全平方」でなければ即 0\n", + "\n", + "$$\n", + "N = 4p+d^2\n", + "$$\n", + "と置くと、上の式は $s^2=N$。$s$ は整数なので、$N$ が**完全平方**でなければ整数 $s$ は存在せず、解は $0$ です。\n", + "(補足:$N<0$ のときも当然 $s^2=N$ は不可能なので $0$。)\n", + "\n", + "### 3) $s$ と $t$ の候補数を掛け合わせるだけ\n", + "\n", + "$N$ が完全平方なら $q=\\sqrt{N}\\in\\mathbb{Z}$ が存在します。このとき\n", + "$$\n", + "s\\in{+q,-q},\\quad t\\in{d,-d}.\n", + "$$\n", + "\n", + "* $q>0$ なら $s$ は ${+q,-q}$ の **2 通り**。\n", + "* $q=0$ なら $s=0$ の **1 通り**($+0$ と $-0$ は同じ)。\n", + "* $d>0$ なら $t$ は ${+d,-d}$ の **2 通り**。\n", + "* $d=0$ なら $t=0$ の **1 通り**(絶対値が 0 のときは符号の分岐がありません)。\n", + "\n", + "よって通り数は\n", + "$$\n", + "\\bigl(1+\\mathbf{1}*{q>0}\\bigr)\\times\\bigl(1+\\mathbf{1}*{d>0}\\bigr),\n", + "$$\n", + "つまり\n", + "\n", + "* $q>0$ なら $s$ 側が $2$、$q=0$ なら $1$\n", + "* $d>0$ なら $t$ 側が $2$、$d=0$ なら $1$\n", + " を掛け合わせた個数になります。\n", + "\n", + "> 注:$d<0$ の入力は、そもそも $|x-y|=d$ を満たせないので **常に 0**。\n", + "\n", + "### 4) 偶奇(パリティ)の心配は要らない理由\n", + "\n", + "$x=\\dfrac{s+t}{2},,y=\\dfrac{s-t}{2}$ が整数になるには、$s$ と $t$ の偶奇が一致している必要があります。\n", + "ここで $t=\\pm d$、$s^2=4p+d^2$ より\n", + "$$\n", + "s^2 \\equiv d^2 \\pmod{4}\n", + "$$\n", + "が成り立つので、$s$ と $d$ は同じ偶奇になります。よって $t=\\pm d$ とも偶奇一致し、$x,y$ は必ず整数になります。\n", + "(完全平方を満たした時点で偶奇条件は自動で通ります。)\n", + "\n", + "---\n", + "\n", + "## ミニ例で確認\n", + "\n", + "### 例1:$d=1,\\ p=2$\n", + "\n", + "* $N=4p+d^2=8+1=9$ は完全平方、$q=3>0$。\n", + "* $s\\in{+3,-3}$(2 通り)、$t\\in{+1,-1}$(2 通り)で合計 $2\\times 2=4$。\n", + "* 復元してみると\n", + " $$\n", + " (s,t)=(3,1)\\Rightarrow (x,y)=\\Bigl(\\frac{3+1}{2},\\frac{3-1}{2}\\Bigr)=(2,1) \\\n", + " (3,-1)\\Rightarrow (2,-1) \\\n", + " (-3,1)\\Rightarrow (-1,-2) \\\n", + " (-3,-1)\\Rightarrow (-2,-1)\n", + " $$\n", + " で確かに 4 通り。\n", + "\n", + "### 例2:$d=0,\\ p=4$\n", + "\n", + "* $N=4p+d^2=16$、$q=4>0$。\n", + "* $s$ は 2 通り($\\pm 4$)、$t$ は 1 通り($0$)なので合計 $2\\times 1=2$。\n", + "* 復元:$(s,t)=(4,0)\\Rightarrow (2,2)$、$(-4,0)\\Rightarrow (-2,-2)$。\n", + "\n", + "### 例3:$d=-1,\\ p=1$\n", + "\n", + "* $d<0$ は不可能なので即 $0$。\n", + "\n", + "---\n", + "\n", + "## つまずきポイント早見\n", + "\n", + "* **$q=0$ を 2 通りと数えない**:$s=\\pm 0$ は同一。\n", + "* **$d=0$ を 2 通りと数えない**:$t=\\pm 0$ は同一。\n", + "* **完全平方チェックだけで十分**:満たせば偶奇も自動で揃う。\n", + "* **順序対であることに注意**:$t$ の符号違いで $(x,y)$ の順序が入れ替わるため、$d>0$ なら $t$ 側で 2 通りが立つ。\n", + "\n", + "これで、TL;DR の各 bullet が「なぜそう数えるか」までクリアになるはずです。\n", + "\n", + "---\n", + "\n", + "

図解

\n", + "\n", + "**フローチャート(式変形の流れ)**\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " Start[開始]\n", + " Eq1[\"和差の導入 s=x+y, t=x-y\"]\n", + " Transform[\"積の式変形で s^2=4p+d^2\"]\n", + " CheckSquare[N が完全平方か判定]\n", + " Count[s,t の通り数を積]\n", + " Output[個数を出力]\n", + "\n", + " Start --> Eq1\n", + " Eq1 --> Transform\n", + " Transform --> CheckSquare\n", + " CheckSquare --> Count\n", + " Count --> Output\n", + "```\n", + "\n", + "*説明*: 和差変数で二次式に落としてから、完全平方かどうかのみを判定し、$s$ と $t$ の選択肢の積で個数を得る。\n", + "\n", + "**データフロー(入力から結果まで)**\n", + "\n", + "```mermaid\n", + "graph LR\n", + " A[\"入力 d,p\"]\n", + " B[\"N=4p+d^2\"]\n", + " C[\"q=isqrt(N)\"]\n", + " D[完全平方チェック]\n", + " E[個数計算]\n", + " F[出力]\n", + "\n", + " A --> B\n", + " B --> C\n", + " C --> D\n", + " D --> E\n", + " E --> F\n", + "```\n", + "\n", + "*説明*: $N$ の計算と整数平方根での完全平方判定を経て、通り数の直積で答えを得る。\n", + "\n", + "---\n", + "\n", + "

証明のスケッチ

\n", + "\n", + "* **設定**: $s=x+y,\\ t=x-y$ とする。$|t|=d$ より $t\\in{d,-d}$。\n", + "* **主要式**: $xy=\\dfrac{s^2-t^2}{4}=p$ より $s^2=4p+d^2$。\n", + "* **必要十分性**:\n", + "\n", + " * $N=4p+d^2$ が完全平方であれば $q=\\sqrt{N}\\in\\mathbb{Z}$ が存在し、$s\\in{\\pm q}$ を取れる。\n", + " * $t\\in{d,-d}$ と組にすれば\n", + " $$\n", + " x=\\frac{s+t}{2},\\quad y=\\frac{s-t}{2}\n", + " $$\n", + " は整数となる($s^2\\equiv d^2 \\pmod{4}$ が成り立ち、$s$ と $d$ の偶奇が一致するため)。\n", + " * 逆に $N$ が完全平方でなければ整数 $s$ は存在せず、よって解は存在しない。\n", + "* **計数**:\n", + "\n", + " * $q>0$ のとき $s$ は $2$ 通り、$q=0$ のとき $1$ 通り。\n", + " * $d>0$ のとき $t$ は $2$ 通り、$d=0$ のとき $1$ 通り。\n", + " * よって個数は $\\left(1+\\mathbf{1}*{q>0}\\right)\\left(1+\\mathbf{1}*{d>0}\\right)$。\n", + "* **基底ケース**:\n", + "\n", + " * $d<0$ は $|x-y|=d$ が不可能なので $0$。\n", + " * $d=0,\\ p=0$ では $N=0$、$q=0$、$s=0,\\ t=0$ の一組 $(0,0)$ のみで $1$。\n", + "* **終了性**: 各判定は有限個の整数演算のみで完了する。\n", + "\n", + "---\n", + "\n", + "

計算量

\n", + "\n", + "* **時間計算量**: $O(1)$\n", + "* **空間計算量**: $O(1)$\n", + "\n", + "---\n", + "\n", + "

Python 実装

\n", + "\n", + "> 注: HackerRank では関数ベースで採点されます。下記は **CPython 3.13.3** 想定、型注釈付き、純粋関数です。スタブ(入出力処理)はプラットフォーム側に従ってください。\n", + "\n", + "```python\n", + "from __future__ import annotations\n", + "from typing import Final\n", + "import math\n", + "\n", + "def solve(d: int, p: int) -> int:\n", + " \"\"\"\n", + " |x - y| = d かつ x*y = p を満たす有序整数対 (x,y) の個数を返す。\n", + "\n", + " 数式対応(本文の KaTeX と対応):\n", + " - s = x + y, t = x - y\n", + " - |t| = d\n", + " - x*y = (s^2 - t^2)/4 = p より s^2 = 4p + d^2\n", + " - N = 4p + d^2\n", + " - N が完全平方で q = sqrt(N) が整数なら:\n", + " s は {+q, -q}(q>0 で2通り、q=0で1通り)\n", + " t は {+d, -d}(d>0で2通り、d=0で1通り)\n", + " 個数 = (1 if q==0 else 2) * (1 if d==0 else 2)\n", + " - d < 0 のときは |x - y| = d を満たせないので 0\n", + " \"\"\"\n", + " # |x - y| = d は d >= 0 が必須\n", + " if d < 0:\n", + " return 0\n", + "\n", + " # N = 4p + d^2\n", + " N: Final[int] = 4 * p + d * d\n", + " if N < 0:\n", + " # 4p が負で d^2 を足しても負になる極端なケース(p が十分に負)\n", + " return 0\n", + "\n", + " # 完全平方判定: math.isqrt は丸め誤差なしの整数平方根\n", + " q: int = math.isqrt(N)\n", + " if q * q != N:\n", + " return 0\n", + "\n", + " s_count: int = 1 if q == 0 else 2\n", + " t_count: int = 1 if d == 0 else 2\n", + " return s_count * t_count\n", + "```\n", + "\n", + "---\n", + "\n", + "

CPython 最適化ポイント

\n", + "\n", + "* **`math.isqrt`**: 整数の平方根を誤差なく返し、完全平方判定に最適。\n", + "* **任意精度整数**: Python の `int` は任意精度でオーバーフローを気にしない。\n", + "* **分岐の最小化**: 判定順序を $d<0 \\Rightarrow N<0 \\Rightarrow$ 完全平方 の順にして早期リターン。\n", + "\n", + "---\n", + "\n", + "

エッジケースと検証

\n", + "\n", + "* $d<0$ $\\Rightarrow$ $0$。\n", + "* $N=4p+d^2<0$(大きく負の $p$)$\\Rightarrow$ $0$。\n", + "* $N$ が完全平方でない $\\Rightarrow$ $0$。\n", + "* $d=0$:\n", + "\n", + " * $N=4p$ が完全平方なら $q=2\\sqrt{|p|}$ が整数のとき $s$ は $2$ 通り(ただし $q=0$ なら $1$ 通り)、$t$ は $1$ 通り。\n", + " * 例: $d=0,\\ p=4 \\Rightarrow N=16,\\ q=4 \\Rightarrow 2$。\n", + "* $d=0,\\ p=0$:\n", + "\n", + " * $N=0,\\ q=0 \\Rightarrow 1$($(0,0)$ のみ)。\n", + "* 代表例との整合:\n", + "\n", + " * $d=1,\\ p=2$: $N=9,\\ q=3>0 \\Rightarrow (2)\\times(2)=4$。\n", + " * $d=0,\\ p=4$: $N=16,\\ q=4>0 \\Rightarrow (2)\\times(1)=2$。\n", + " * $d=-1,\\ p=1$: $d<0 \\Rightarrow 0$。\n", + "\n", + "---\n", + "\n", + "

FAQ

\n", + "\n", + "* **偶奇条件は確認不要か**\n", + " $s^2=4p+d^2$ より $s^2 \\equiv d^2 \\pmod{4}$。$s$ と $d$ の偶奇が一致するため、$x=(s+t)/2,\\ y=(s-t)/2$ は常に整数になる。\n", + "* **符号付きの $s$ を数える理由**\n", + " $s=\\pm q$ はそれぞれ異なる $(x,y)$ を与える可能性があり、有序対の計数では両方を数える必要がある。\n", + "* **なぜ $O(1)$ か**\n", + " 各テストに対して加減乗算と `isqrt`、数回の比較のみで、入力値の大きさに関わらず定数時間で終わるため。\n", + "\n", + "---\n", + "\n", + "**補足(Mermaid と日本語フォント)**: Mermaid 図で日本語が豆腐になる場合、利用側の CSS で `.mermaid { font-family: \"Noto Sans JP\", sans-serif; }` のように指定してください。\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/SQL/Leetcode/Basic join/1068. Product Sales Analysis I/gpt 5.1 thinking customized/Product_Sales_Analysis_I_mysql.ipynb b/SQL/Leetcode/Basic join/1068. Product Sales Analysis I/gpt 5.1 thinking customized/Product_Sales_Analysis_I_mysql.ipynb new file mode 100644 index 00000000..989af957 --- /dev/null +++ b/SQL/Leetcode/Basic join/1068. Product Sales Analysis I/gpt 5.1 thinking customized/Product_Sales_Analysis_I_mysql.ipynb @@ -0,0 +1,303 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a4a02d79", + "metadata": {}, + "source": [ + "## 0) 前提\n", + "\n", + "* エンジン: **MySQL 8.0.40**\n", + "* 結合キー:\n", + "\n", + " * `Sales.product_id` ↔ `Product.product_id`\n", + "* 要求出力:\n", + "\n", + " * 各 `sale_id` ごとに\n", + " `product_name, year, price`\n", + "* 集計・順位付けは不要なので **ウィンドウ関数は使わない方がシンプルで高速**\n", + "\n", + "---\n", + "\n", + "## 1) 問題整理\n", + "\n", + "* 入力テーブル\n", + "\n", + " **Sales**\n", + "\n", + " * `sale_id` (PK の一部: `sale_id, year`)\n", + " * `product_id` (FK → `Product.product_id`)\n", + " * `year`\n", + " * `quantity`\n", + " * `price`(単価)\n", + "\n", + " **Product**\n", + "\n", + " * `product_id` (PK)\n", + " * `product_name`\n", + "\n", + "* 出力仕様\n", + "\n", + " * 列: `product_name, year, price`\n", + " * 各行は `Sales` の 1 レコードに対応\n", + " * 並び順は任意(`ORDER BY` なし)\n", + "\n", + "* 方針\n", + "\n", + " * 各売上行に対して、対応する商品の名前を `INNER JOIN` で取得し、そのまま投影\n", + "\n", + "---\n", + "\n", + "## 2) 最適解(単一クエリ)\n", + "\n", + "> 今回は「各行そのまま+名前付け」なので、**単純な内部結合のみ**が最短・最速です。\n", + "> ウィンドウ関数や CTE による前処理は不要です。\n", + "\n", + "```sql\n", + "SELECT\n", + " p.product_name,\n", + " s.year,\n", + " s.price\n", + "FROM Sales AS s\n", + "JOIN Product AS p\n", + " ON p.product_id = s.product_id;\n", + "\n", + "Runtime 1155 ms\n", + "Beats 69.04%\n", + "\n", + "```\n", + "\n", + "* `JOIN`(= `INNER JOIN`)なので、`Product` に存在しない `product_id` は自然に除外\n", + "* 並び順要件なし → `ORDER BY` を付与しないことで余計なソートコストを回避\n", + "\n", + "---\n", + "\n", + "## 3) 代替解\n", + "\n", + "### 3-1. 相関サブクエリ版(JOIN 不使用)\n", + "\n", + "JOIN が苦手な読者向けの書き方としては、以下のように **相関サブクエリ** も可能です(ただしパフォーマンス面では JOIN が優位)。\n", + "\n", + "```sql\n", + "SELECT\n", + " (\n", + " SELECT p.product_name\n", + " FROM Product AS p\n", + " WHERE p.product_id = s.product_id\n", + " ) AS product_name,\n", + " s.year,\n", + " s.price\n", + "FROM Sales AS s;\n", + "\n", + "Runtime 5589 ms\n", + "Beats 5.01%\n", + "\n", + "```\n", + "\n", + "* `Sales` 側の行ごとに `Product` テーブルを参照\n", + "* インデックスがあれば許容されるが、大量データでは JOIN の方が一般的に高速\n", + "\n", + "---\n", + "\n", + "## 4) 要点解説\n", + "\n", + "1. **ID ベースでの結合**\n", + "\n", + " * 問題文が「`product_id` は `Product` の外部キー」と明示しているため、\n", + "\n", + " * **結合条件**: `Sales.product_id = Product.product_id`\n", + " * 結果の正しさは ID ベースで保証される\n", + "\n", + "2. **集計・フィルタが不要**\n", + "\n", + " * 「各 sale_id について」情報を出すだけなので\n", + "\n", + " * 集約 (`GROUP BY`)\n", + " * 順位付け (`ROW_NUMBER`, `RANK` など)\n", + " * TOP k 抽出 (`LIMIT`)\n", + " は一切不要 → **シンプルな SELECT + JOIN が最適**\n", + "\n", + "3. **`INNER JOIN` を選ぶ理由**\n", + "\n", + " * 問題文は「Sales テーブルの sale_id ごと」としか書いていないが、同時に `product_id` は `Product` の FK とあるので「整合データ前提」と解釈し、**商品マスタに存在しない ID をわざわざ残す必要はない** ⇒ `INNER JOIN` で十分\n", + "\n", + "4. **並び順任意 → `ORDER BY` 省略**\n", + "\n", + " * LeetCode 的には「any order」なら `ORDER BY` は不要\n", + " * 省略によりソートステップがなくなり、クエリプランが軽くなる\n", + "\n", + "5. **インデックス利用**\n", + "\n", + " * 典型的には以下が用意されている想定\n", + "\n", + " * `Product(product_id)`(PK)\n", + " * `Sales(product_id)`(FK 用インデックス)\n", + " * これにより、`Sales` から `Product` への結合は **ほぼ O(N)** でスキャン可能\n", + "\n", + "---\n", + "\n", + "## 5) 計算量(概算)\n", + "\n", + "* `Sales` の件数を `N`、`Product` の件数を `M` とする\n", + "* インデックス付きネストループ / ハッシュ結合を想定\n", + "\n", + "1. **JOIN 処理**\n", + "\n", + " * `Sales` を 1 度スキャン: O(N)\n", + " * 各 `product_id` に対し `Product` をインデックスルックアップ: O(1) 近似 × N\n", + " * 合計: **O(N)** 近似(`M` がメモリに乗る前提)\n", + "\n", + "2. **追加のソートや集計なし**\n", + "\n", + " * `GROUP BY` / `ORDER BY` がないため、\n", + " ウィンドウ処理やソート起因の **O(N log N)** コストは発生しない\n", + "\n", + "---\n", + "\n", + "## 6) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[入力 Sales テーブル]\n", + " B[入力 Product テーブル]\n", + " C[結合 product_id で内部結合]\n", + " D[列選択 product_name year price]\n", + " E[出力 結果テーブル]\n", + "\n", + " A --> C\n", + " B --> C\n", + " C --> D\n", + " D --> E\n", + "```\n", + "\n", + "この問題は「JOIN の基本」を確認するタイプなので、\n", + "**余計なウィンドウ関数や CTE を足さず、最短の INNER JOIN で書けるか**がポイントになります。\n", + "\n", + "結論から言うと、この問題に関しては **ほぼ最適** なので、実務目線でも LeetCode 目線でも「これ以上いじる価値がある改善」はありません 👍\n", + "\n", + "---\n", + "\n", + "## 1. 現在のクエリの評価\n", + "\n", + "```sql\n", + "SELECT\n", + " p.product_name,\n", + " s.year,\n", + " s.price\n", + "FROM Sales AS s\n", + "JOIN Product AS p\n", + " ON p.product_id = s.product_id;\n", + "```\n", + "\n", + "* やりたいこと:\n", + "\n", + " * `Sales` の各行に対して、`Product` から `product_name` を引いてくるだけ\n", + "* それに対して:\n", + "\n", + " * 不要な列は出していない\n", + " * 不要な `DISTINCT` / `GROUP BY` / `ORDER BY` もなし\n", + " * 結合条件も PK–FK で素直\n", + "\n", + "**アルゴリズム的には O(N)**(`Sales` を 1 回なめて `Product` にインデックスルックアップ)なので、これ以上いい形はほぼありません。\n", + "\n", + "LeetCode の **Runtime 1155 ms / Beats 69.04%** も、この種の SQL 問題としては十分優秀な部類です。\n", + "\n", + "---\n", + "\n", + "## 2. 相関サブクエリ版が遅い理由\n", + "\n", + "```sql\n", + "SELECT\n", + " (\n", + " SELECT p.product_name\n", + " FROM Product AS p\n", + " WHERE p.product_id = s.product_id\n", + " ) AS product_name,\n", + " s.year,\n", + " s.price\n", + "FROM Sales AS s;\n", + "```\n", + "\n", + "* これは「`Sales` の行ごとに `Product` を探しに行く」構造なので、\n", + "\n", + " * プランによっては **行数 × サブクエリ** になりやすく、\n", + " * JOIN よりもオーバーヘッドが大きい\n", + "* その結果:\n", + "\n", + " * Runtime 5589 ms / Beats 5.01% と、数字が正直に「非効率です」と言ってます。\n", + "\n", + "**教科書的にも・実務的にも JOIN が正解**なので、ここは今の理解でバッチリです。\n", + "\n", + "---\n", + "\n", + "## 3. 「それでも何かできる?」という観点でのコメント\n", + "\n", + "LeetCode 環境だと、ここから先は **ほぼ誤差の世界** です。\n", + "\n", + "やれるとしてもせいぜい:\n", + "\n", + "* `JOIN` → `INNER JOIN` と明示してもプランはほぼ変わらないはず\n", + "* `USING (product_id)` を使って少しだけスッキリ書く程度(性能は同じ)\n", + "\n", + "例:\n", + "\n", + "```sql\n", + "SELECT\n", + " p.product_name,\n", + " s.year,\n", + " s.price\n", + "FROM Sales AS s\n", + "JOIN Product AS p USING (product_id);\n", + "\n", + "Runtime 1306 ms\n", + "Beats 31.05%\n", + "\n", + "```\n", + "\n", + "ですが、これは **可読性の好みの問題** であって、実行計画レベルでの改善はほとんど期待できません。\n", + "\n", + "---\n", + "\n", + "## 4. Runtime の数字をどう捉えるか\n", + "\n", + "LeetCode の Runtime は、以下のような要因でブレます:\n", + "\n", + "* 実行サーバの負荷状況\n", + "* キャッシュの有無\n", + "* 同じクエリでも再実行で数百 ms レベルで揺れることがある\n", + "\n", + "この問題のように\n", + "\n", + "* シンプルな JOIN\n", + "* 行数もそこまで多くない\n", + "* インデックス前提の PK–FK 結合\n", + "\n", + "というケースでは、**「Beats 69%」なら十分・これ以上追うのは趣味の最適化**という感覚で大丈夫です。\n", + "\n", + "---\n", + "\n", + "## まとめ\n", + "\n", + "* 今の JOIN クエリは **ロジックも計算量もベストプラクティスど真ん中**。\n", + "* 相関サブクエリ版が遅いのも理屈どおりで、理解は完璧。\n", + "* LeetCode Runtime はノイズも多いので、これ以上は「数字のための微調整」になりがちです。\n", + "\n", + "この問題については、**「もう終わりでよいレベル」**と判断して次の問題に進んでOKだと思います。\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "575a4473", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/SQL/Leetcode/Basic join/1068. Product Sales Analysis I/gpt 5.1 thinking customized/Product_Sales_Analysis_I_pandas.ipynb b/SQL/Leetcode/Basic join/1068. Product Sales Analysis I/gpt 5.1 thinking customized/Product_Sales_Analysis_I_pandas.ipynb new file mode 100644 index 00000000..670066ca --- /dev/null +++ b/SQL/Leetcode/Basic join/1068. Product Sales Analysis I/gpt 5.1 thinking customized/Product_Sales_Analysis_I_pandas.ipynb @@ -0,0 +1,344 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "61602ed9", + "metadata": {}, + "source": [ + "## 0) 前提\n", + "\n", + "* 環境: **Python 3.10.15 / pandas 2.2.2**\n", + "* シグネチャ(LeetCode 想定):\n", + "\n", + "```python\n", + "import pandas as pd\n", + "\n", + "def sales_analysis(sales: pd.DataFrame, product: pd.DataFrame) -> pd.DataFrame:\n", + " ...\n", + "```\n", + "\n", + "* 制約:\n", + "\n", + " * IO なし\n", + " * `print` / `sort_values` 不使用\n", + "* 判定は ID 基準:\n", + "\n", + " * `product.product_id`(商品マスタ)\n", + " * `sales.product_id`(売上側 FK → マスタ)\n", + "\n", + "---\n", + "\n", + "## 1) 問題\n", + "\n", + "### PROBLEM_STATEMENT\n", + "\n", + "`Sales` と `Product` の 2 つのテーブル(DataFrame)がある。\n", + "\n", + "* **Sales**\n", + "\n", + " * 各行は、ある年における `product_id` の販売\n", + " * `price` は 1 単位あたりの単価\n", + "* **Product**\n", + "\n", + " * 各 `product_id` ごとの `product_name`\n", + "\n", + "**各 sale_id について**, 対応する `product_name`, `year`, `price` を出力せよ。\n", + "結果の行順は任意。\n", + "\n", + "### 入力 DF(INPUT_DATAFRAMES)\n", + "\n", + "* `sales: pd.DataFrame`\n", + "\n", + " * 列: `['sale_id', 'product_id', 'year', 'quantity', 'price']`\n", + "* `product: pd.DataFrame`\n", + "\n", + " * 列: `['product_id', 'product_name']`\n", + "\n", + "### 出力(OUTPUT_COLUMNS_AND_RULES)\n", + "\n", + "* 戻り値: `pd.DataFrame`\n", + "* 列名・順序:\n", + "\n", + "```text\n", + "['product_name', 'year', 'price']\n", + "```\n", + "\n", + "* 各行は `Sales` の 1 レコードに対応\n", + "\n", + "---\n", + "\n", + "## 2) 実装(指定シグネチャ厳守)\n", + "\n", + "> この問題は **グループ処理不要** なので、\n", + "> 「列最小化 → 軽量 `map` で名前付与 → 必要列だけ DataFrame 化」が最短です。\n", + "\n", + "```python\n", + "import pandas as pd\n", + "\n", + "def sales_analysis(sales: pd.DataFrame, product: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['product_name', 'year', 'price']\n", + " \"\"\"\n", + " # 1) Product 側を「product_id → product_name」のマッピングに圧縮\n", + " key_to_name = product.set_index('product_id')['product_name']\n", + "\n", + " # 2) Sales の product_id をマップして product_name を付与し、\n", + " # year と price はそのまま流用して新しい DataFrame を構成\n", + " out = pd.DataFrame({\n", + " 'product_name': sales['product_id'].map(key_to_name),\n", + " 'year': sales['year'],\n", + " 'price': sales['price'],\n", + " })\n", + "\n", + " return out\n", + "\n", + "Analyze Complexity\n", + "Runtime 375 ms\n", + "Beats 20.66%\n", + "Memory 69.10 MB\n", + "Beats 99.76%\n", + "\n", + "```\n", + "\n", + "* 列順は dict リテラルの順序どおり → `['product_name', 'year', 'price']`\n", + "* `sort_values` / `print` 未使用\n", + "* groupby / rank も不要なため使っていない\n", + "\n", + "---\n", + "\n", + "## 3) アルゴリズム説明\n", + "\n", + "使っている主な API は以下の通りです。\n", + "\n", + "1. **`set_index`**\n", + "\n", + " * `product.set_index('product_id')['product_name']`\n", + " * `product_id` をインデックスにして、`product_name` だけ取り出した `Series` を作成\n", + " * これで「単一キー → 単一値」のマップを構築\n", + "\n", + "2. **`map`**\n", + "\n", + " * `sales['product_id'].map(key_to_name)`\n", + " * 各 `product_id` に対して、対応する `product_name` を引く\n", + " * PK(`product_id` 一意)前提のため、`merge` より軽量な 1 対 1 置換として最適\n", + "\n", + "3. **DataFrame の再構成**\n", + "\n", + " * `pd.DataFrame({...})` で必要列のみをまとめる\n", + " * 余計な列(`sale_id`, `quantity` 等)は出力しない\n", + "\n", + "### NULL / 重複 / 型まわり\n", + "\n", + "* **重複**\n", + "\n", + " * `Product.product_id` は PK なので重複の心配なし\n", + " * よって `map` で「どの値を取るか」問題は発生しない\n", + "* **NULL**\n", + "\n", + " * 正常系では `Sales.product_id` は FK → `Product` 側に必ず存在する前提\n", + " * もし存在しない場合は `product_name` が `NaN` になるが、問題文の前提上考慮不要\n", + "* **型**\n", + "\n", + " * `product_id` は両 DataFrame で `int` 想定\n", + " * 型がズレるとマッピングできないので、実務なら `astype` で揃えるが、LeetCode では前提一致\n", + "\n", + "---\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "* `product.set_index('product_id')['product_name']`\n", + "\n", + " * インデックス構築: **O(M)**\n", + "* `sales['product_id'].map(key_to_name)`\n", + "\n", + " * 各行でハッシュルックアップ: **O(N)** 近似\n", + "* DataFrame 再構成: **O(N)**\n", + "\n", + "ここで\n", + "\n", + "* `N = len(sales)`\n", + "* `M = len(product)`\n", + "\n", + "よって全体は **O(N + M)**。\n", + "`merge` でも実質同程度ですが、「1 対 1 マッピング」前提なら `map` の方がコードもコストも少しだけ軽い実装になります。\n", + "\n", + "---\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[入力 sales DataFrame]\n", + " B[入力 product DataFrame]\n", + " C[product を product_id と product_name に圧縮してマップ作成]\n", + " D[sales の product_id に map で product_name を付与]\n", + " E[year と price をそのまま利用して列を構成]\n", + " F[出力 product_name year price の DataFrame]\n", + "\n", + " A --> D\n", + " B --> C\n", + " C --> D\n", + " D --> E\n", + " E --> F\n", + "```\n", + "\n", + "この問題は、「**Pandas での JOIN 相当処理は `merge` だけでなく `map` も強力**」というパターンを押さえておくと、後続の問題でもかなり使い回せます。\n", + "\n", + "結論から言うと:\n", + "\n", + "> **アルゴリズム的にはほぼ最適**で、375ms / Beats 20% は「悪くないが特別速くもない」くらい。\n", + "> 大きく改善する余地は小さいですが、**実装スタイルを少し変えてワンチャン狙う余地**はあります。\n", + "\n", + "---\n", + "\n", + "## 1. 現状の評価\n", + "\n", + "実装(`map` 版)はこんなイメージでした:\n", + "\n", + "```python\n", + "key_to_name = product.set_index('product_id')['product_name']\n", + "\n", + "out = pd.DataFrame({\n", + " 'product_name': sales['product_id'].map(key_to_name),\n", + " 'year': sales['year'],\n", + " 'price': sales['price'],\n", + "})\n", + "```\n", + "\n", + "* 計算量: **O(N + M)**(N = sales 行数, M = product 行数)\n", + "* 余計な処理なし(groupby / sort / 重複処理などゼロ)\n", + "* メモリ: Beats 99.76% → 実際かなり優秀\n", + "\n", + "Runtime のパーセンタイルが低いのは、\n", + "\n", + "* LeetCode の pandas ランタイムが結構ブレる\n", + "* `merge` ベース実装に最適化が効いている可能性\n", + "\n", + "あたりが効いていそうです。\n", + "\n", + "---\n", + "\n", + "## 2. 試す価値がある改善案(`merge` ベース)\n", + "\n", + "pandas の内部実装的には、**`merge`(ハッシュ結合)に最適化が乗りやすい**ことが多いので、\n", + "以下のような **列最小化 → merge → 列並べ替え** パターンは試す価値があります。\n", + "\n", + "```python\n", + "import pandas as pd\n", + "\n", + "def sales_analysis(sales: pd.DataFrame, product: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['product_name', 'year', 'price']\n", + " \"\"\"\n", + " # 1) 列最小化(不要な列を抱えない)\n", + " sales_small = sales[['product_id', 'year', 'price']]\n", + " product_small = product[['product_id', 'product_name']]\n", + "\n", + " # 2) 左結合(Sales ベース)で product_name を付与\n", + " merged = sales_small.merge(\n", + " product_small,\n", + " on='product_id',\n", + " how='left'\n", + " )\n", + "\n", + " # 3) 仕様どおりの列のみ・順序に整形\n", + " out = merged[['product_name', 'year', 'price']]\n", + "\n", + " return out\n", + "\n", + "Analyze Complexity\n", + "Runtime 341 ms\n", + "Beats 62.84%\n", + "Memory 70.59 MB\n", + "Beats 9.35%\n", + "\n", + "```\n", + "\n", + "### この書き方の狙い\n", + "\n", + "* **列最小化**\n", + "\n", + " * `sales_small`, `product_small` で「ほんとうに使う列だけ」に絞る\n", + " * 中間 DataFrame の列数が少ないほど、結合やコピーのコストが減る\n", + "* **`merge` の内部最適化に乗る**\n", + "\n", + " * C 実装のハッシュ結合がフル活用されるので、環境によっては `map` より速くなることがある\n", + "* **DataFrame の再構成を 1 回にする**\n", + "\n", + " * `merged[['product_name', 'year', 'price']]` で slice するだけなので、\n", + " dict からの新規 DataFrame 構築よりわずかに有利なパターンもあり得る\n", + "\n", + "---\n", + "\n", + "## 3. もう一つの軽量案(`join` + index)\n", + "\n", + "`set_index` を使うなら、`map` ではなく `join` に寄せるパターンもあります。\n", + "\n", + "```python\n", + "import pandas as pd\n", + "\n", + "def sales_analysis(sales: pd.DataFrame, product: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['product_name', 'year', 'price']\n", + " \"\"\"\n", + " sales_small = sales[['product_id', 'year', 'price']]\n", + " product_idx = product.set_index('product_id')[['product_name']]\n", + "\n", + " merged = sales_small.join(product_idx, on='product_id')\n", + "\n", + " out = merged[['product_name', 'year', 'price']]\n", + "\n", + " return out\n", + "\n", + "Analyze Complexity\n", + "Runtime 345 ms\n", + "Beats 57.03%\n", + "Memory 70.14 MB\n", + "Beats 65.28%\n", + "\n", + "```\n", + "\n", + "* `join(..., on='product_id')` は「左側の index を使って右側 index と結合」する仕組み\n", + "* これも **O(N + M)** で、`merge` とほぼ同等の計算量\n", + "\n", + "---\n", + "\n", + "## 4. どこまで追うかの目安\n", + "\n", + "* 375ms / Beats 20.66% は、\n", + " pandas 問題としては **「普通〜やや遅めだけど許容範囲」** くらいの位置\n", + "* すでに **メモリはほぼ最上位** なので、\n", + "\n", + " * これ以上メモリを削る意味は薄い\n", + " * もし `merge`/`join` に変えてランタイムが少し良くなればラッキー、程度の世界\n", + "\n", + "正直、**アルゴリズムはもう最適レベル**なので、\n", + "ここから先は「スタイル変更で実行エンジンのクセに乗るかどうか」の話になります。\n", + "\n", + "---\n", + "\n", + "### まとめ\n", + "\n", + "* 今の `map` 実装でも十分 OK(アルゴリズム的には改善余地ほぼなし)\n", + "* それでも Runtime を追いたいなら:\n", + "\n", + " * 上記の **`merge` + 列最小化** 版\n", + " * または **`join` + index** 版\n", + " を一度投げてみる価値はあります\n", + "* いずれも計算量は同じで、差は「実装スタイル」と「エンジン側の最適化次第」です\n", + "\n", + "「学習として試す」なら `merge` 版を一度投げて、Runtime の変化を見ておくと今後の問題にも効いてきます 💡\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/SQL/Leetcode/Basic join/1068. Product Sales Analysis I/gpt 5.1 thinking customized/Product_Sales_Analysis_I_posgres.ipynb b/SQL/Leetcode/Basic join/1068. Product Sales Analysis I/gpt 5.1 thinking customized/Product_Sales_Analysis_I_posgres.ipynb new file mode 100644 index 00000000..24e6d1e0 --- /dev/null +++ b/SQL/Leetcode/Basic join/1068. Product Sales Analysis I/gpt 5.1 thinking customized/Product_Sales_Analysis_I_posgres.ipynb @@ -0,0 +1,223 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5aef0b48", + "metadata": {}, + "source": [ + "## 0) 前提\n", + "\n", + "* エンジン: **PostgreSQL 16.6+**\n", + "* 主キー・外部キー\n", + "\n", + " * `Sales (sale_id, year)` が複合 PK\n", + " * `Sales.product_id` → `Product.product_id` への FK\n", + "* 並び順: 任意(`ORDER BY` 不要)\n", + "* 判定は ID 基準:\n", + "\n", + " * `product_id` で結合\n", + "* `NOT IN` は不要(単純な内部結合だけで良いケース)\n", + "\n", + "---\n", + "\n", + "## 1) 問題\n", + "\n", + "### PROBLEM_STATEMENT\n", + "\n", + "Sales と Product の 2 テーブルがある。\n", + "\n", + "* **Sales**\n", + "\n", + " * 各行は、ある年における `product_id` の販売を表す\n", + " * `price` は 1 単位あたりの単価\n", + "* **Product**\n", + "\n", + " * `product_id` ごとの `product_name` を持つ\n", + "\n", + "**各 sale_id に対して**, 対応する `product_name`, `year`, `price` を取得するクエリを書け。\n", + "結果の行順は任意。\n", + "\n", + "### TABLES_OR_SCHEMAS\n", + "\n", + "```text\n", + "Table: Sales\n", + "+-------------+-------+\n", + "| Column Name | Type |\n", + "+-------------+-------+\n", + "| sale_id | int |\n", + "| product_id | int |\n", + "| year | int |\n", + "| quantity | int |\n", + "| price | int |\n", + "+-------------+-------+\n", + "(sale_id, year) が PK\n", + "product_id は Product.product_id への FK\n", + "\n", + "Table: Product\n", + "+--------------+---------+\n", + "| Column Name | Type |\n", + "+--------------+---------+\n", + "| product_id | int |\n", + "| product_name | varchar |\n", + "+--------------+---------+\n", + "product_id が PK\n", + "```\n", + "\n", + "### OUTPUT_COLUMNS_AND_RULES\n", + "\n", + "* 出力列\n", + "\n", + " * `product_name`\n", + " * `year`\n", + " * `price`\n", + "* 各行は Sales の 1 レコードに対応\n", + "* 結果の並び順は任意\n", + "\n", + "---\n", + "\n", + "## 2) 最適解(単一クエリ)\n", + "\n", + "この問題は\n", + "\n", + "* 「各 Sales 行に対して、Product から名前を引いてくるだけ」\n", + "* グループ集計・上位 k 抽出・重複排除のロジックも不要\n", + "\n", + "なので、**CTE やウィンドウ関数は使わず、素直な内部結合が最短・最速**です。\n", + "\n", + "```sql\n", + "SELECT\n", + " p.product_name,\n", + " s.year,\n", + " s.price\n", + "FROM Sales AS s\n", + "JOIN Product AS p\n", + " ON p.product_id = s.product_id;\n", + "\n", + "Runtime 1178 ms\n", + "Beats 65.91%\n", + "\n", + "```\n", + "\n", + "### ポイント\n", + "\n", + "* 必要列だけを投影\n", + "\n", + " * `sale_id` や `quantity` は要求されていないので出さない\n", + "* `INNER JOIN`(省略形 `JOIN`)でProduct に存在しない `product_id` は自動的に除外される\n", + "* 並び順指定なし(any order)なので `ORDER BY` も省略し、余計なソートコストをかけない\n", + "\n", + "---\n", + "\n", + "### 代替(構造をテンプレに寄せる CTE 版・性能は同じ)\n", + "\n", + "テンプレ構造を意識して「事前に必要列だけ抜き出す」書き方もできます。\n", + "実行計画的にはほぼ同じですが、ロジックを段階的に読ませたいときに使えます。\n", + "\n", + "```sql\n", + "WITH pre AS (\n", + " SELECT\n", + " s.product_id,\n", + " s.year,\n", + " s.price\n", + " FROM Sales AS s\n", + ")\n", + "SELECT\n", + " p.product_name,\n", + " pre.year,\n", + " pre.price\n", + "FROM pre\n", + "JOIN Product AS p\n", + " ON p.product_id = pre.product_id;\n", + "\n", + "Runtime 1163 ms\n", + "Beats 70.73%\n", + "\n", + "```\n", + "\n", + "* `pre` CTE で\n", + "\n", + " * Sales から必要列だけを抽出\n", + "* その結果に対して Product を JOIN\n", + "\n", + "処理内容は最初の解と同じで、PostgreSQL は CTE をインライン化するので **性能差はほぼゼロ** です。\n", + "\n", + "---\n", + "\n", + "## 3) 要点解説\n", + "\n", + "1. **ID 基準の結合がすべて**\n", + "\n", + " * 問題の本質は「`Sales.product_id` を `Product.product_id` にマッピングする」だけ\n", + " * したがって、`JOIN` 以外の複雑なロジックは不要\n", + "\n", + "2. **ウィンドウ関数・LATERAL は出番なし**\n", + "\n", + " * ランキング・グループ内上位・累積集計などは一切要求されていない\n", + " * `ROW_NUMBER` / `DENSE_RANK` / `LATERAL` は、「別の問題」で活躍するツールと割り切ってよい\n", + "\n", + "3. **`NOT IN` 問題もなし**\n", + "\n", + " * 今回は「存在する Product を結合するだけ」なので `NOT IN` 自体が登場しない\n", + " * PostgreSQL では `NOT IN (…NULL…)` が罠になるが、そのリスクもゼロ\n", + "\n", + "4. **並び順任意 → `ORDER BY` を付けない**\n", + "\n", + " * LeetCode は「any order」と明記 → `ORDER BY` は付けない方が速い\n", + " * ソートは O(N log N) のコストを食うため、不要ならカット\n", + "\n", + "5. **インデックス前提のプラン**\n", + "\n", + " * 典型的には\n", + "\n", + " * `Product.product_id` に PK インデックス\n", + " * `Sales.product_id` に FK インデックス\n", + " * これにより、`Sales` の全行を走査しつつ `Product` をインデックスルックアップする形で、ほぼ線形時間で処理可能\n", + "\n", + "---\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "`Sales` の件数を **N**, `Product` の件数を **M** とする。\n", + "\n", + "1. **JOIN(インデックスあり)**\n", + "\n", + " * `Sales` 全行スキャン: O(N)\n", + " * 各行について `Product` へのインデックスルックアップ: O(1) 近似 × N\n", + " * 合計: **O(N)** 近似\n", + "\n", + "2. **追加処理**\n", + "\n", + " * 集約 (`GROUP BY`)・ウィンドウ・ソート (`ORDER BY`) なし\n", + " * よって、典型的な RDB の結合クエリとしてはほぼ最小限のコスト\n", + "\n", + "---\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[入力 Sales テーブル]\n", + " B[入力 Product テーブル]\n", + " C[結合 product_id で内部結合]\n", + " D[列選択 product_name year price]\n", + " E[出力 結果テーブル]\n", + "\n", + " A --> C\n", + " B --> C\n", + " C --> D\n", + " D --> E\n", + "```\n", + "\n", + "この問題は「**PostgreSQL であっても、変に凝らず素直な JOIN が最適**」という、\n", + "良いウォームアップ問題の位置づけです。次のウィンドウ関数系の問題に進む足慣らしとしては完璧なレベル感になっています。\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}