質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

Q&A

解決済

2回答

7663閲覧

MysqlのSELECT句中でサブクエリのWHERE条件

sanapapa

総合スコア30

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

0グッド

0クリップ

投稿2018/11/14 07:24

編集2018/11/15 02:27

例えば下記のような2つのテーブルがあるとします。

Stock(在庫テーブル)

SQL

1CREATE TABLE `Stock` ( 2 `stock_id` INT(11) NOT NULL AUTO_INCREMENT, 3 `location_id` INT(11) NULL DEFAULT NULL, 4 `product_id` INT(11) NULL DEFAULT NULL, 5 `stock_num` INT(11) NULL DEFAULT NULL, 6 PRIMARY KEY (`stock_id`), 7 INDEX `location_id` (`location_id`, `product_id`) 8) 9COLLATE='utf8_general_ci' 10ENGINE=InnoDB 11;

|stock_id|location_id|product_id|stock_num|
|:--|:--:|--:|
|1|100|1000|5|
|2|101|1001|3|
|3|102|2000|1|
|4|103|1000|1|
|5|104|3000|4|
|6|103|1001|3|
|7|100|1001|2|
|8|101|2000|4|

BoughtStock(購入在庫)

SQL

1CREATE TABLE `BoughtStock` ( 2 `stock_id` INT(11) NULL DEFAULT NULL, 3 `bought_num` INT(11) NULL DEFAULT NULL, 4 INDEX `stock_id` (`stock_id`) 5) 6COLLATE='utf8_general_ci' 7ENGINE=InnoDB 8ROW_FORMAT=COMPACT 9;

※購入されるたびにレコードが挿入される(stock_idは重複する)

stock_idbought_num
11
11
41
11
21
21
31

このデータから↓のような結果を抽出しようとしています。
・ロケ、商品ごとの在庫総数
・引当総数(購入点数):そのロケのその商品が何点購入されているのかを抽出
・ロケ内総在庫数(商品問わず):ロケ内の在庫を全商品ですべて合算する

|ロケーションID|商品ID|在庫数|引当総数|ロケ内総在庫数|
|:--|:--:|--:|
|100|1000|5|3|7|
|100|1001|2|0|7|
|101|1001|3|2|7|
|101|2000|4|0|7|
|102|2000|1|1|1|
|103|1000|1|1|4|
|103|1001|3|0|4|
|104|3000|4|0|4|

SQL

1SELECT 2 stk.location_id AS 'ロケーションID' 3 , stk.product_id AS '商品ID' 4 , SUM(stk.stock_num) AS '在庫数' 5 , IFNULL((SELECT SUM(bst.bought_num) FROM BoughtStock bst WHERE bst.stock_id = stk.stock_id), 0) AS '引当総数' 6 , (SELECT SUM(stk2.stock_num) FROM Stock stk2 WHERE stk2.location_id = stk.location_id) AS 'ロケ内総在庫数' 7FROM Stock stk 8GROUP BY stk.location_id, stk.product_id 9ORDER BY stk.location_id, stk.product_id

実行計画
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
|:--|:--:|--:|
|1|PRIMARY|stk|index||location_id|10||8||
|3|DEPENDENT SUBQUERY|stk2|ref|location_id|location_id|5|func|1|Using where|
|2|DEPENDENT SUBQUERY|bst|ref|stock_id|stock_id|5|func|1|Using where|

上記のSQLで結果自体は問題ありませんし、EXPLAINで実行計画を取得してもインデックスも効いているのですが
実際何千行の大量データで抽出すると著しく速度が低下します。

SELECT句のサブクエリの項目を外すと速度は低下しません。

もしかしてSELECT句でサブクエリを使用する場合、上記SQLのような条件の書き方ではインデックスが効かない仕様なのでしょうか。

また、速度低下せずに抽出できるSQLを考案可能な方がいらっしゃいましたらご教示いただきたいです。
※BoughtStockはstock_idの重複があるため、単純なLEFT JOINでは「在庫数」など他の集計項目が倍々になります。

何卒宜しくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2018/11/14 07:26

何の取得を目的としたクエリなのか解説を入れられたほうがリファクタリングもやりやすくなりますので、追記願います。
sanapapa

2018/11/14 07:32

失礼いたしました、修正してみました。ご確認お願いします。
Orlofsky

2018/11/14 09:15

質問にCREATE TABLE, CREATE INDEX や現状の実行計画も載せた方が適切なコメントが付き易いです。
sanapapa

2018/11/15 02:56

ご指摘ありがとうございます。既にベストアンサーは選んでしまいましたが、参考までに記載させていただきました。
guest

回答2

0

ベストアンサー

引当総数ロケ内総在庫数location_idproduct_idの組み合わせで一意であったり、値が変わらない為、MySQLの仕様(12.19.3 MySQL での GROUP BY の処理)によって容認された結果に過ぎません。
※他のDBMSなら文法エラー
説明します。
先ず集計前の状態の以下のSQLは正しい結果だと思います。

SQL

1SELECT location_id, product_id, stock_num 2 , IFNULL((SELECT SUM(bst.bought_num) FROM BoughtStock bst WHERE bst.stock_id = stk.stock_id), 0) AS '引当総数' 3 , (SELECT SUM(stk2.stock_num) FROM Stock stk2 WHERE stk2.location_id = stk.location_id) AS 'ロケ内総在庫数' 4FROM Stock

これを、location_id, product_id単位で集計としていますが、じゃあ、引当総数ロケ内総在庫数はどんな集計されているの?って事です。これらは集計されず適当な何れかの値が採用されています。
相関問い合わせなので、それらの相関内の何れかの値が使用されていますが、何れも同じ値なので、求めたいものと同じになっているに過ぎません。

結果的にサブクエリーをさらに集計するような仕組みが働いて、遅いのではないかと思われます。
基本に忠実に、SQLを組み立てると、必要なインデックスも明確になると思います。

性能が改善するかどうかわかりませんが、素直にキーを揃えるように組み立てると以下のようなSQLになるのではないかと。

SQL

1select stk.location_id AS 'ロケーションID' 2 , stk.product_id AS '商品ID' 3 , stk.prdct_stock_sum AS '在庫数' 4 , coalesce(stk.prdct_bought_sum, 0) AS '引当総数' 5 , stk_loc.loc_stock_sum AS 'ロケ内総在庫数' 6from ( 7 select location_id, product_id, sum(stk_stock_sum) as prdct_stock_sum, sum(bought_sum) as prdct_bought_sum 8 from ( 9 select location_id, product_id, stock_id, SUM(stock_num) AS stk_stock_sum 10 FROM Stock stk 11 group by location_id, product_id, stock_id 12 ) stk 13 left join ( 14 select stock_id, SUM(bought_num) as bought_sum FROM BoughtStock group by stock_id 15 ) bst 16 on stk.stock_id=bst.stock_id 17 group by location_id, product_id 18 ) stk inner join ( 19 select location_id, sum(stock_num) as loc_stock_sum from Stock group by location_id 20 ) stk_loc 21 on stk.location_id=stk_loc.location_id 22ORDER BY stk.location_id, stk.product_id

DATA

1CREATE TABLE Stock(stock_id int, location_id int, product_id int, stock_num int); 2INSERT INTO Stock(stock_id, location_id, product_id, stock_num) 3VALUES 4 (1, 100, 1000, 5), 5 (2, 101, 1001, 3), 6 (3, 102, 2000, 1), 7 (4, 103, 1000, 1), 8 (5, 104, 3000, 4), 9 (6, 103, 1001, 3), 10 (7, 100, 1001, 2), 11 (8, 101, 2000, 4) 12; 13CREATE TABLE BoughtStock(stock_id int, bought_num int); 14INSERT INTO BoughtStock(stock_id, bought_num) 15VALUES 16 (1, 1), 17 (1, 1), 18 (4, 1), 19 (1, 1), 20 (2, 1), 21 (2, 1), 22 (3, 1) 23;

投稿2018/11/14 09:03

編集2018/11/14 12:36
sazi

総合スコア25195

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sanapapa

2018/11/15 02:17

sazi様のご提示のSQLを参考にSQLを発行すると期待通りの結果でレスポンス速度もバッチリでした! 初心に返って書いた方が近道だったりするんですね。 大変勉強になりました。ベストアンサーとさせていただきます。 またよろしくお願いします。
phpsyoshinsya

2018/11/15 03:22

今後のために私に詳しい解説をお願いしたく存じます。質問者さんの意図を私が上手にくみ取れていなかったのはあきらかですが、primary  keyを設定しないで、どうやってどうパフォーマンスを向上させたのですか?
sazi

2018/11/15 04:23 編集

パフォーマンスについては、プライマリーキーが設定されているかどうかは関係ありません。 主に、条件(結合条件含む)や並びに関係している項目についてインデックスが設定されているかどうかです。集計などでは、集計する項目もインデックスに含まれていれば、インデックスのみで処理されるのでより高速です。 今回は、既に適切なインデックスがあり、それを効率よく使うようにマッチングしたSQLに結果的になったという事です。 実行計画では意図したインデックスが使用されているという事であったので、そうなるであろうと予想はしていましたが。
phpsyoshinsya

2018/11/15 04:34

なるほど、create tableで重要なカラムでよくセレクトでhaving・group・whereを使うときにはindexもしっかり付けた方がいいよということですね。 selectを三つだしていましたが、これはmysqLターミナルでは可能でPHPでは結果セットがあふれかえってしまうのであまり使えない方法ということになりますか?
sazi

2018/11/15 05:35 編集

>indexもしっかり付けた方がいいよということですね。 概ねそうです。ただ、インデックスを付けすぎると更新時のパフォーマンスは下がりますし、DISK資源も消費します。効果を見ながらというのも必要です。 >結果セットがあふれかえって 1回のSQLで返却される結果セットは一つですよ select毎に返却されるわけではありません。サブクエリーは単にネストした問い合わせですので。
guest

0

###stockテーブルの問題点

もし重複するのが困るなら、
insert into
を使わないことと、
なにが唯一(
primary keyなのかがはっきりしていないですよ。

もしまだ表の再構築が可能ならcreate tableを実行して、どのカラム(列)を唯一にするのか設定しなおしてください。

もし、際構築が不可能なら、
alter table
で唯一を設定してください。

もしくは唯一がすでに設定されているなら質問本文を修正してください。

###現状での対策

現状ではhavingやgrouping区を使って対処するしかない気がします。

投稿2018/11/14 09:08

phpsyoshinsya

総合スコア156

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問