MochiuWiki : SUSE, EC, PCB
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
MySQL - 相関副問い合わせのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
MySQL - 相関副問い合わせ
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == 相関副問い合わせ (相関サブクエリ) は、外部クエリの列を参照するサブクエリである。<br> <br> 相関サブクエリは、外部クエリの各行に対して異なる値を返すことができるため、非相関サブクエリよりも柔軟な条件指定が可能である。<br> ただし、外部クエリの各行ごとにサブクエリが実行されるため、パフォーマンスへの影響に注意が必要である。<br> <br> 相関サブクエリは、<code>SELECT</code> 句、<code>WHERE</code> 句、<code>HAVING</code> 句で使用でき、また <code>UPDATE</code> 文や <code>DELETE</code> 文でも使用できる。<br> 典型的な用途として、各行に対する集計値の取得、グループごとの最大値を持つ行の抽出、存在チェックによるフィルタリングなどがある。<br> <br> 相関サブクエリの実行回数は外部クエリの行数に比例する (O(M×N) の計算量) ため、大量のデータに対してはパフォーマンス問題が発生する可能性がある。<br> MySQL 8.0以降では、オプティマイザーが相関サブクエリを自動的に最適化する機能があり、特定の条件下では <code>JOIN</code> や派生テーブルに変換される。<br> MySQL 9.0では、<code>LIMIT 1</code> を持つスカラー相関サブクエリの最適化も導入されている。<br> <br> 副問い合わせ (サブクエリ) 全般については、[[MySQL - 副問い合わせ]]のページを参照すること。<br> <br><br> == 基本構文と実行の流れ == ==== 基本構文 ==== 相関サブクエリは、サブクエリ内で外部クエリのテーブルの列を参照する。<br> <br> 外部クエリのテーブルにはエイリアスを付け、サブクエリ内でそのエイリアスを使用して列を参照する。<br> <br> <syntaxhighlight lang="mysql"> # 基本構文 SELECT column1, column2 FROM table1 AS outer_table WHERE column3 operator (SELECT aggregate_function(column4) FROM table2 WHERE table2.column5 = outer_table.column6); # 例 : 各従業員と所属部署の平均給与を比較 SELECT name, salary FROM employees AS e1 WHERE salary > (SELECT AVG(salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id); </syntaxhighlight> <br> サブクエリ内で外部クエリのテーブル (<u>e1</u>) の列 (<u>e1.department_id</u>) を参照している点が特徴である。<br> <br> 同一テーブルに対する相関サブクエリでは、外部テーブルと内部テーブルで異なるエイリアスを付ける必要がある。<br> <br> <syntaxhighlight lang="mysql"> # 同一テーブルへの相関サブクエリ (エイリアス必須) SELECT e1.name, e1.salary FROM employees AS e1 WHERE e1.salary > (SELECT AVG(e2.salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id); # エイリアスがないとエラーになる SELECT name, salary FROM employees WHERE salary > (SELECT AVG(salary) FROM employees WHERE department_id = department_id); # サブクエリ内のdepartment_idがどちらのテーブルを指すか不明確 </syntaxhighlight> <br> ==== 実行の流れ ==== 相関サブクエリの実行は、以下に示すステップで行われる。<br> <br> <syntaxhighlight lang="mysql"> # 例: 部署平均給与より高い従業員を取得 SELECT e1.name, e1.salary FROM employees AS e1 WHERE e1.salary > (SELECT AVG(e2.salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id); </syntaxhighlight> <br> 実行フローを以下に示す。<br> <br> # 外部クエリが最初の行をフェッチ (例: 従業員ID=1、department_id=10) #: 外部クエリのテーブル <code>e1</code> から1行目を読み取る。 # サブクエリがその行の値を使用して実行 #: サブクエリは <code>e1.department_id</code> (=10) を受け取り、部署10の平均給与を計算する。 # サブクエリの結果と外部クエリの列を比較 #: 従業員の給与 (<code>e1.salary</code>) が部署平均給与より大きいかを判定する。 # 条件を満たせば結果セットに含める #: 条件が <code>TRUE</code> なら、その行を結果に含める。 # 外部クエリの次の行に対してステップ1〜4を繰り返す #: 全ての行が処理されるまで繰り返す。 <br> このように、相関サブクエリは外部クエリの各行ごとに実行される。<br> <br> ==== 非相関サブクエリとの比較 ==== 相関サブクエリと非相関サブクエリの違いを以下に示す。<br> <br> <center> {| class="wikitable" |+ 相関サブクエリと非相関サブクエリの比較 ! 項目 !! 非相関サブクエリ !! 相関サブクエリ |- | 外部クエリへの参照 || 外部クエリの列を参照しない || 外部クエリの列を参照する |- | 実行回数 || 1回のみ || 外部クエリの各行ごと (N回) |- | 独立性 || 単独で実行可能 || 外部クエリなしでは実行不可 |- | パフォーマンス || 一般的に高速 || 外部行数が多いと遅い |- | 使用場面 || 全体の集計値との比較 || 行ごとの動的な比較 |} </center> <br> <syntaxhighlight lang="mysql"> # 非相関サブクエリ (全従業員の平均給与と比較) SELECT name, salary FROM employees WHERE salary > (SELECT AVG(salary) FROM employees); # サブクエリは1回のみ実行される # 相関サブクエリ (各従業員が所属部署の平均給与と比較) SELECT name, salary FROM employees AS e1 WHERE salary > (SELECT AVG(salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id); # サブクエリは外部クエリの各行ごとに実行される </syntaxhighlight> <br><br> == WHERE句での相関サブクエリ == ==== 比較演算子との組み合わせ ==== <code>WHERE</code> 句での相関サブクエリは、比較演算子と組み合わせて使用されることが多い。<br> <br> <syntaxhighlight lang="mysql"> # 各従業員の給与と所属部署の平均給与を比較 SELECT name, salary, department_id FROM employees AS e1 WHERE salary > (SELECT AVG(salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id); # 各商品の価格とそのカテゴリの最高価格を比較 SELECT product_name, price FROM products AS p1 WHERE price = (SELECT MAX(price) FROM products AS p2 WHERE p2.category_id = p1.category_id); # 各注文の金額とそのユーザの平均注文金額を比較 SELECT order_id, amount FROM orders AS o1 WHERE amount > (SELECT AVG(amount) FROM orders AS o2 WHERE o2.user_id = o1.user_id); # 各従業員の入社日と同じ部署の最早入社日を比較 SELECT name, hire_date FROM employees AS e1 WHERE hire_date = (SELECT MIN(hire_date) FROM employees AS e2 WHERE e2.department_id = e1.department_id); </syntaxhighlight> <br> グループごとの最大値を持つ行を取得するパターンは、MySQLの公式ドキュメント 5.6.4で紹介されている典型的な手法である。<br> <br> <syntaxhighlight lang="mysql"> # グループごとの最大値を持つ行を取得 SELECT product_id, product_name, category_id, price FROM products AS p1 WHERE price = (SELECT MAX(p2.price) FROM products AS p2 WHERE p2.category_id = p1.category_id); # 各部署で最も給与の高い従業員を取得 SELECT department_id, name, salary FROM employees AS e1 WHERE salary = (SELECT MAX(e2.salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id); </syntaxhighlight> <br> ==== EXISTS / NOT EXISTS ==== <code>EXISTS</code> 演算子は、相関サブクエリと組み合わせて使用されることが多い。<br> <br> <code>EXISTS</code> は、サブクエリが1行以上返す場合に <code>TRUE</code> を返す。<br> <code>NOT EXISTS</code> は、サブクエリが0行の場合に <code>TRUE</code> を返す。<br> <br> <syntaxhighlight lang="mysql"> # 注文がある従業員を取得 SELECT name FROM employees AS e WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.employee_id = e.id); # 注文のない従業員を取得 SELECT name FROM employees AS e WHERE NOT EXISTS (SELECT 1 FROM orders AS o WHERE o.employee_id = e.id); # 特定条件を満たす注文がある顧客を取得 SELECT name FROM customers AS c WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.customer_id = c.id AND o.status = 'completed' AND o.amount > 10000); # 2024年以降に注文のないユーザを取得 SELECT name FROM users AS u WHERE NOT EXISTS (SELECT 1 FROM orders AS o WHERE o.user_id = u.id AND o.ordered_at >= '2024-01-01'); </syntaxhighlight> <br> <code>EXISTS</code> では、慣習として <code>SELECT 1</code> を使用する。<br> <br> <code>EXISTS</code> は行の存在のみをチェックするため、サブクエリで選択する列は結果に影響しない。<br> <code>SELECT 1</code>、<code>SELECT *</code>、<code>SELECT NULL</code> のいずれも同じ結果となるが、<code>SELECT 1</code> の方が意図が明確である。<br> <br> <syntaxhighlight lang="mysql"> # 以下は全て同じ結果 WHERE EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id); WHERE EXISTS (SELECT * FROM orders WHERE orders.user_id = users.id); WHERE EXISTS (SELECT NULL FROM orders WHERE orders.user_id = users.id); </syntaxhighlight> <br> <code>EXISTS</code> は <code>NULL</code> の影響を受けないため、<code>NOT IN</code> より安全である。<br> <br> <syntaxhighlight lang="mysql"> # NOT INはNULLがある場合に問題が発生する SELECT name FROM employees WHERE department_id NOT IN (SELECT manager_id FROM departments); # manager_idにNULLが含まれる場合、結果は0行 # NOT EXISTSはNULLの影響を受けない SELECT name FROM employees AS e WHERE NOT EXISTS (SELECT 1 FROM departments AS d WHERE d.manager_id = e.department_id); # NULLがあっても正しく動作する </syntaxhighlight> <br><br> == SELECT句での相関サブクエリ == <code>SELECT</code> 句で相関サブクエリを使用すると、外部クエリの各行に対してスカラー値を取得できる。<br> <br> 各行に対して集計値や関連データを取得する際に有用である。<br> <br> <syntaxhighlight lang="mysql"> # 各ユーザの注文数を取得 SELECT id, name, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count FROM users; # 各部署の従業員数を取得 SELECT department_name, (SELECT COUNT(*) FROM employees WHERE employees.department_id = departments.id) AS employee_count FROM departments; # 各商品の最終注文日を取得 SELECT product_id, product_name, (SELECT MAX(ordered_at) FROM order_items WHERE order_items.product_id = products.id) AS last_ordered FROM products; # 各従業員の所属部署の平均給与を取得 SELECT name, salary, (SELECT AVG(salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id) AS dept_avg_salary FROM employees AS e1; </syntaxhighlight> <br> 複数の相関サブクエリを組み合わせることもできる。<br> <br> <syntaxhighlight lang="mysql"> # 各ユーザの注文数と合計金額を取得 SELECT id, name, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count, (SELECT SUM(amount) FROM orders WHERE orders.user_id = users.id) AS total_amount FROM users; # 各商品の在庫数と注文数を取得 SELECT product_name, (SELECT SUM(quantity) FROM inventory WHERE inventory.product_id = products.id) AS stock, (SELECT SUM(quantity) FROM order_items WHERE order_items.product_id = products.id) AS ordered FROM products; </syntaxhighlight> <br> 注意事項として、<code>SELECT</code> 句の相関サブクエリは必ずスカラー値 (1行1列) を返す必要がある。<br> <br> 複数行を返すとエラー (ERROR 1242: Subquery returns more than 1 row) が発生する。<br> <br> <syntaxhighlight lang="mysql"> # エラーになる例 (複数行が返される可能性) SELECT name, (SELECT salary FROM employees WHERE department_id = departments.id) AS dept_salary FROM departments; # 各部署に複数の従業員がいる場合、エラー # 集計関数を使用してスカラー値にする SELECT name, (SELECT AVG(salary) FROM employees WHERE department_id = departments.id) AS avg_dept_salary FROM departments; # LIMIT 1で単一行に制限する SELECT name, (SELECT salary FROM employees WHERE department_id = departments.id LIMIT 1) AS sample_salary FROM departments; </syntaxhighlight> <br> <code>SELECT</code> 句の相関サブクエリは、外部クエリの各行ごとに実行されるため、パフォーマンスへの影響が大きい。<br> 大量のデータに対しては <code>JOIN</code> への書き換えを検討する必要がある。<br> <br><br> == HAVING句での相関サブクエリ == <code>HAVING</code> 句で相関サブクエリを使用すると、集約結果と動的な値を比較できる。<br> <br> ただし、<code>HAVING</code> 句の相関サブクエリには制限がある。<br> 相関列 (外部クエリの列への参照) は、サブクエリの <code>WHERE</code> 句にのみ記述でき、サブクエリの <code>SELECT</code> 句や <code>HAVING</code> 句には記述できない。<br> <br> <syntaxhighlight lang="mysql"> # 全体平均より多くの従業員を持つ部署を取得 SELECT department_id, COUNT(*) AS employee_count FROM employees GROUP BY department_id HAVING COUNT(*) > (SELECT AVG(cnt) FROM (SELECT COUNT(*) AS cnt FROM employees GROUP BY department_id) AS dept_counts); # 各ユーザの注文数が全体平均より多いユーザを取得 SELECT user_id, COUNT(*) AS order_count FROM orders GROUP BY user_id HAVING COUNT(*) > (SELECT AVG(cnt) FROM (SELECT COUNT(*) AS cnt FROM orders GROUP BY user_id) AS user_counts); # 各カテゴリの合計売上が全体合計の10%以上のカテゴリを取得 SELECT category_id, SUM(amount) AS total_sales FROM order_items GROUP BY category_id HAVING SUM(amount) >= (SELECT SUM(amount) * 0.1 FROM order_items); </syntaxhighlight> <br> <code>GROUP BY</code> と組み合わせて、グループごとの集計結果を条件として使用できる。<br> <br> <syntaxhighlight lang="mysql"> # 各部署の平均給与が全体平均給与より高い部署を取得 SELECT department_id, AVG(salary) AS avg_salary FROM employees GROUP BY department_id HAVING AVG(salary) > (SELECT AVG(salary) FROM employees); # 各カテゴリの商品数が全カテゴリの平均商品数より多いカテゴリを取得 SELECT category_id, COUNT(*) AS product_count FROM products GROUP BY category_id HAVING COUNT(*) > (SELECT AVG(cnt) FROM (SELECT COUNT(*) AS cnt FROM products GROUP BY category_id) AS cat_counts); </syntaxhighlight> <br><br> == UPDATE / DELETE文での相関サブクエリ == ==== UPDATE文での使用 ==== <code>UPDATE</code> 文で相関サブクエリを使用すると、他のテーブルや同じテーブルの他の行の値に基づいて更新できる。<br> <br> <code>SET</code> 句と <code>WHERE</code> 句の両方で相関サブクエリを使用できる。<br> <br> <syntaxhighlight lang="mysql"> # SET句で相関サブクエリを使用 UPDATE employees AS e1 SET salary = (SELECT AVG(salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id) WHERE hire_date < '2020-01-01'; # WHERE句で相関サブクエリを使用 UPDATE products SET status = 'discontinued' WHERE NOT EXISTS (SELECT 1 FROM order_items WHERE order_items.product_id = products.id); # 各ユーザの累積購入金額を更新 UPDATE users AS u SET total_purchased = (SELECT SUM(amount) FROM orders AS o WHERE o.user_id = u.id) WHERE EXISTS (SELECT 1 FROM orders WHERE orders.user_id = u.id); # 各商品の在庫を最新の入荷数で更新 UPDATE products AS p SET stock = (SELECT quantity FROM inventory AS i WHERE i.product_id = p.id ORDER BY received_at DESC LIMIT 1) WHERE EXISTS (SELECT 1 FROM inventory WHERE inventory.product_id = p.id); </syntaxhighlight> <br> 複数列を更新する場合も相関サブクエリを使用できる。<br> <br> <syntaxhighlight lang="mysql"> # 複数列を相関サブクエリで更新 UPDATE employees AS e SET salary = (SELECT AVG(salary) FROM employees WHERE department_id = e.department_id), updated_at = NOW() WHERE hire_date < '2020-01-01'; </syntaxhighlight> <br> 詳細は、[[MySQL - UPDATE]] のページを参照すること。<br> <br> ==== DELETE文での使用 ==== <code>DELETE</code> 文で相関サブクエリを使用すると、他のテーブルの値に基づいて行を削除できる。<br> <br> <syntaxhighlight lang="mysql"> # 注文のないユーザを削除 DELETE FROM users WHERE NOT EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id); # 在庫のない商品を削除 DELETE FROM products AS p WHERE NOT EXISTS (SELECT 1 FROM inventory AS i WHERE i.product_id = p.id); # 最終ログイン日が1年以上前のユーザを削除 DELETE FROM users AS u WHERE (SELECT MAX(logged_at) FROM login_logs WHERE login_logs.user_id = u.id) < DATE_SUB(NOW(), INTERVAL 1 YEAR); # アーカイブ済みのログを削除 DELETE FROM logs WHERE EXISTS (SELECT 1 FROM archived_logs WHERE archived_logs.log_id = logs.id); </syntaxhighlight> <br> <code>EXISTS</code> / <code>NOT EXISTS</code> を使用した削除は、<code>IN</code> / <code>NOT IN</code> よりも安全である。<br> <br> <syntaxhighlight lang="mysql"> # NOT INは、サブクエリにNULLがあると全て削除されない DELETE FROM products WHERE id NOT IN (SELECT product_id FROM order_items); # product_idにNULLがある場合、削除が行われない # NOT EXISTSは、NULLの影響を受けない DELETE FROM products AS p WHERE NOT EXISTS (SELECT 1 FROM order_items AS oi WHERE oi.product_id = p.id); # NULLがあっても正しく動作する </syntaxhighlight> <br> 詳細は、[[MySQL - DELETE]] のページを参照すること。<br> <br><br> == 相関サブクエリのパターン == ==== グループごとの最大値を持つ行の取得 ==== グループごとの最大値を持つ行を取得するパターンは、相関サブクエリの典型的な用途である。<br> <br> MySQL公式ドキュメント5.6.4で紹介されている手法である。<br> <br> <syntaxhighlight lang="mysql"> # 各カテゴリで最も高い価格の商品を取得 SELECT category_id, product_name, price FROM products AS p1 WHERE price = (SELECT MAX(price) FROM products AS p2 WHERE p2.category_id = p1.category_id); # 各部署で最も給与の高い従業員を取得 SELECT department_id, name, salary FROM employees AS e1 WHERE salary = (SELECT MAX(salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id); # 各ユーザの最新注文を取得 SELECT user_id, order_id, ordered_at FROM orders AS o1 WHERE ordered_at = (SELECT MAX(ordered_at) FROM orders AS o2 WHERE o2.user_id = o1.user_id); # 各カテゴリで最も在庫の多い商品を取得 SELECT category_id, product_name, stock FROM products AS p1 WHERE stock = (SELECT MAX(stock) FROM products AS p2 WHERE p2.category_id = p1.category_id); </syntaxhighlight> <br> このパターンでは、同じ最大値を持つ複数の行が返される可能性がある。<br> <br> <syntaxhighlight lang="mysql"> # 最高価格が同じ商品が複数ある場合、全て返される SELECT category_id, product_name, price FROM products AS p1 WHERE price = (SELECT MAX(price) FROM products AS p2 WHERE p2.category_id = p1.category_id); # カテゴリ1に価格1000円の商品が2つある場合、両方とも返される </syntaxhighlight> <br> ==== 存在チェックによるフィルタリング ==== <code>EXISTS</code> / <code>NOT EXISTS</code> を使用した存在チェックは、相関サブクエリの主要な用途である。<br> <br> <syntaxhighlight lang="mysql"> # 注文があるユーザのみを取得 SELECT id, name FROM users AS u WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.user_id = u.id); # 在庫のある商品のみを取得 SELECT product_name FROM products AS p WHERE EXISTS (SELECT 1 FROM inventory AS i WHERE i.product_id = p.id AND i.quantity > 0); # 特定期間に注文があった顧客を取得 SELECT name FROM customers AS c WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.customer_id = c.id AND o.ordered_at BETWEEN '2024-01-01' AND '2024-12-31'); # レビューのある商品を取得 SELECT product_name FROM products AS p WHERE EXISTS (SELECT 1 FROM reviews AS r WHERE r.product_id = p.id); </syntaxhighlight> <br> <code>NOT EXISTS</code> を使用した除外パターンも有用である。<br> <br> <syntaxhighlight lang="mysql"> # 注文のないユーザを取得 SELECT id, name FROM users AS u WHERE NOT EXISTS (SELECT 1 FROM orders AS o WHERE o.user_id = u.id); # 在庫のない商品を取得 SELECT product_name FROM products AS p WHERE NOT EXISTS (SELECT 1 FROM inventory AS i WHERE i.product_id = p.id); # 2024年に注文のなかった顧客を取得 SELECT name FROM customers AS c WHERE NOT EXISTS (SELECT 1 FROM orders AS o WHERE o.customer_id = c.id AND o.ordered_at >= '2024-01-01' AND o.ordered_at < '2025-01-01'); </syntaxhighlight> <br> ==== 累積計算 ==== 相関サブクエリを使用して、累積合計 (ランニングトータル) や累積カウントを計算できる。<br> <br> <syntaxhighlight lang="mysql"> # 各注文の累積合計金額を計算 SELECT order_id, ordered_at, amount, (SELECT SUM(amount) FROM orders AS o2 WHERE o2.user_id = o1.user_id AND o2.ordered_at <= o1.ordered_at) AS cumulative_amount FROM orders AS o1 ORDER BY user_id, ordered_at; # 各日の累積販売数を計算 SELECT sale_date, quantity, (SELECT SUM(quantity) FROM sales AS s2 WHERE s2.product_id = s1.product_id AND s2.sale_date <= s1.sale_date) AS cumulative_quantity FROM sales AS s1 ORDER BY product_id, sale_date; # 各月の累積ユーザ数を計算 SELECT signup_month, new_users, (SELECT SUM(new_users) FROM monthly_stats AS m2 WHERE m2.signup_month <= m1.signup_month) AS cumulative_users FROM monthly_stats AS m1 ORDER BY signup_month; </syntaxhighlight> <br> ただし、累積計算は行数に比例してコストが増加するため、大量のデータに対しては窓関数 (Window Function) の使用を推奨する。<br> <br> <syntaxhighlight lang="mysql"> # MySQL 8.0以降では窓関数を使用する方が効率的 SELECT order_id, ordered_at, amount, SUM(amount) OVER (PARTITION BY user_id ORDER BY ordered_at) AS cumulative_amount FROM orders ORDER BY user_id, ordered_at; </syntaxhighlight> <br><br> == JOINへの書き換え == 相関サブクエリは、多くの場合で <code>JOIN</code> に書き換えることができる。<br> <br> <code>JOIN</code> への書き換えにより、パフォーマンスが改善される場合がある。<br> <br> ==== EXISTS → INNER JOIN ==== <code>EXISTS</code> を使用した相関サブクエリは、<code>INNER JOIN</code> に書き換えることができる。<br> <br> <syntaxhighlight lang="mysql"> # EXISTSを使用した相関サブクエリ SELECT name FROM users AS u WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.user_id = u.id); # INNER JOINに書き換え SELECT DISTINCT u.name FROM users AS u INNER JOIN orders AS o ON u.id = o.user_id; </syntaxhighlight> <br> ただし、<code>INNER JOIN</code> では重複行が発生する可能性があるため、<code>DISTINCT</code> が必要である。<br> <br> <syntaxhighlight lang="mysql"> # 条件付きEXISTS SELECT name FROM customers AS c WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.customer_id = c.id AND o.status = 'completed'); # INNER JOINに書き換え SELECT DISTINCT c.name FROM customers AS c INNER JOIN orders AS o ON c.id = o.customer_id WHERE o.status = 'completed'; </syntaxhighlight> <br> ==== スカラー相関サブクエリ → JOIN ==== <code>SELECT</code> 句の相関サブクエリは、<code>JOIN</code> と派生テーブルに書き換えることができる。<br> <br> <syntaxhighlight lang="mysql"> # SELECT句で相関サブクエリを使用 SELECT name, salary, (SELECT AVG(salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id) AS dept_avg_salary FROM employees AS e1; # JOINに書き換え SELECT e.name, e.salary, dept_avg.avg_salary AS dept_avg_salary FROM employees AS e JOIN (SELECT department_id, AVG(salary) AS avg_salary FROM employees GROUP BY department_id) AS dept_avg ON e.department_id = dept_avg.department_id; </syntaxhighlight> <br> 複数の相関サブクエリも同様に書き換えられる。<br> <br> <syntaxhighlight lang="mysql"> # 複数の相関サブクエリ SELECT id, name, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count, (SELECT SUM(amount) FROM orders WHERE orders.user_id = users.id) AS total_amount FROM users; # JOINに書き換え SELECT u.id, u.name, COALESCE(o.order_count, 0) AS order_count, COALESCE(o.total_amount, 0) AS total_amount FROM users AS u LEFT JOIN (SELECT user_id, COUNT(*) AS order_count, SUM(amount) AS total_amount FROM orders GROUP BY user_id) AS o ON u.id = o.user_id; </syntaxhighlight> <br> ==== NOT EXISTS → LEFT JOIN ==== <code>NOT EXISTS</code> を使用した相関サブクエリは、<code>LEFT JOIN</code> と <code>IS NULL</code> に書き換えることができる。<br> <br> <syntaxhighlight lang="mysql"> # NOT EXISTSを使用した相関サブクエリ SELECT name FROM users AS u WHERE NOT EXISTS (SELECT 1 FROM orders AS o WHERE o.user_id = u.id); # LEFT JOINに書き換え SELECT u.name FROM users AS u LEFT JOIN orders AS o ON u.id = o.user_id WHERE o.user_id IS NULL; </syntaxhighlight> <br> <code>LEFT JOIN</code> では、結合先のテーブルに該当行がない場合、結合先の列が <code>NULL</code> になる。<br> <br> <syntaxhighlight lang="mysql"> # 在庫のない商品を取得 # NOT EXISTSを使用 SELECT product_name FROM products AS p WHERE NOT EXISTS (SELECT 1 FROM inventory AS i WHERE i.product_id = p.id); # LEFT JOINに書き換え SELECT p.product_name FROM products AS p LEFT JOIN inventory AS i ON p.id = i.product_id WHERE i.product_id IS NULL; </syntaxhighlight> <br><br> == パフォーマンスと最適化 == ==== 実行コストの理解 ==== 相関サブクエリの実行コストは、外部クエリの行数に比例する。<br> <br> 外部クエリがM行、サブクエリがN行を処理する場合、実行時間はO(M×N)になる可能性がある。<br> <br> <syntaxhighlight lang="mysql"> # この相関サブクエリは、外部クエリの各行ごとに実行される SELECT name, salary FROM employees AS e1 WHERE salary > (SELECT AVG(salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id); # 外部クエリが1000行、各部署が平均100人の場合: # - 外部クエリ : 1000行をスキャン # - サブクエリ : 各行ごとに100行をスキャン (合計100,000行) # - 合計 : 101,000行の処理 </syntaxhighlight> <br> インデックスの有無により、パフォーマンスが大きく変わる。<br> <br> <syntaxhighlight lang="mysql"> # インデックスがない場合、フルテーブルスキャンが発生 SELECT name FROM users AS u WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.user_id = u.id); # ordersテーブルのuser_idにインデックスがないと、各行ごとにフルスキャン # インデックスがある場合、高速に検索可能 CREATE INDEX idx_user_id ON orders(user_id); # インデックスにより、各行の検索が高速化される </syntaxhighlight> <br> ==== オプティマイザーの自動最適化 ==== MySQL 8.0以降では、オプティマイザーが相関サブクエリを自動的に最適化する場合がある。<br> <br> <code>optimizer_switch</code> の <code>subquery_to_derived</code> オプションにより、相関サブクエリが派生テーブルに変換される。<br> <br> <syntaxhighlight lang="mysql"> # optimizer_switchの確認 SELECT @@optimizer_switch; # subquery_to_derivedの有効化 (MySQL 8.0.21以降ではデフォルトで有効) SET optimizer_switch='subquery_to_derived=on'; </syntaxhighlight> <br> 変換の条件は以下の通りである。<br> <br> * サブクエリが <code>SELECT</code> 句に存在する *: <code>SELECT</code> 句の相関スカラーサブクエリが対象。 * サブクエリが集計関数 (COUNT、SUM、AVG等) を使用している *: 集計結果を返すサブクエリが対象。 * サブクエリが外部クエリのテーブルを参照している (相関サブクエリである) *: 相関列を持つサブクエリが対象。 * サブクエリが単一の値を返す (スカラーサブクエリである) *: スカラー値を返すサブクエリが対象。 <br> <syntaxhighlight lang="mysql"> # この相関サブクエリは SELECT name, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count FROM users; # 内部的に以下のように変換される可能性がある SELECT u.name, COALESCE(derived.order_count, 0) AS order_count FROM users AS u LEFT JOIN (SELECT user_id, COUNT(*) AS order_count FROM orders GROUP BY user_id) AS derived ON u.id = derived.user_id; </syntaxhighlight> <br> MySQL 9.0では、<code>LIMIT 1</code> を持つスカラー相関サブクエリの最適化も導入されている。<br> <br> <syntaxhighlight lang="mysql"> # MySQL 9.0では、この相関サブクエリが最適化される SELECT name, (SELECT product_name FROM order_items WHERE order_items.order_id = orders.id LIMIT 1) AS first_product FROM orders; # LIMIT 1により、最初の一致で停止することが保証される </syntaxhighlight> <br> ==== EXPLAINでの確認方法 ==== <code>EXPLAIN</code> 文を使用して、相関サブクエリの実行計画を確認できる。<br> <br> <syntaxhighlight lang="mysql"> # 実行計画を確認 EXPLAIN SELECT name, salary FROM employees AS e1 WHERE salary > (SELECT AVG(salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id); </syntaxhighlight> <br> <code>select_type</code> 列で、サブクエリの種類を確認できる。<br> <br> * DEPENDENT SUBQUERY *: 相関サブクエリ (外部クエリの各行ごとに実行される)。 * SUBQUERY *: 非相関サブクエリ (1回のみ実行される)。 * DERIVED *: 派生テーブル (オプティマイザーによる変換)。 <br> <syntaxhighlight lang="mysql"> # select_typeを確認 EXPLAIN SELECT name FROM employees AS e WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.employee_id = e.id); # select_type: DEPENDENT SUBQUERY (相関サブクエリ) # オプティマイザーによる最適化後 # select_type: SIMPLE (セミジョインに変換された場合) </syntaxhighlight> <br> MySQL 8.0.18以降では、<code>EXPLAIN ANALYZE</code> で実際の実行時間を確認できる。<br> <br> <syntaxhighlight lang="mysql"> # 実際の実行時間を確認 EXPLAIN ANALYZE SELECT name FROM users AS u WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.user_id = u.id); # 実行時間とコストが表示される # actual time, rows, loops などの情報が取得できる </syntaxhighlight> <br> <code>EXPLAIN</code> の <code>Extra</code> 列には、追加情報が表示される。<br> <br> * Using where *: WHERE句による絞り込みが行われている。 * Using index *: インデックスのみで処理が完結している (カバリングインデックス)。 * Using temporary *: 一時テーブルが使用されている。 * Start temporary, End temporary *: セミジョイン最適化 (Duplicate Weedout) が適用されている。 <br> <syntaxhighlight lang="mysql"> # Extra列の確認 EXPLAIN SELECT name FROM employees AS e1 WHERE salary > (SELECT AVG(salary) FROM employees AS e2 WHERE e2.department_id = e1.department_id); # Extra: Using where (WHERE句による絞り込み) </syntaxhighlight> <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:MySQL]]
MySQL - 相関副問い合わせ
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse