回答編集履歴

3 推敲

sazi

sazi score 23087

2018/11/14 21:36  投稿

`引当総数`や`ロケ内総在庫数`は`location_id`と`product_id`の組み合わせで一意であったり、値が変わらない為、MySQLの仕様([12.19.3 MySQL での GROUP BY の処理](https://dev.mysql.com/doc/refman/5.6/ja/group-by-handling.html))によって容認された結果に過ぎません。
※他のDBMSなら文法エラー
説明します。
先ず集計前の状態の以下のSQLは正しい結果だと思います。
```SQL
SELECT location_id, product_id, stock_num
    , IFNULL((SELECT SUM(bst.bought_num) FROM BoughtStock bst WHERE bst.stock_id = stk.stock_id), 0) AS '引当総数'
    , (SELECT SUM(stk2.stock_num) FROM Stock stk2 WHERE stk2.location_id = stk.location_id) AS 'ロケ内総在庫数'
FROM Stock
```
これを、`location_id`, `product_id`単位で集計としていますが、じゃあ、`引当総数`と`ロケ内総在庫数`はどんな集計されているの?って事です。これらは集計されず適当な何れかの値が採用されています。
相関問い合わせなので、それらの相関内の何れかの値が使用されていますが、何れも同じ値なので、求めたいものと同じになっているに過ぎません。
基本に忠実に、SQLを組み立てると、必要なインデックスも明確になると思います。
性能が改善するかどうかわかりませんが、素直にキーを揃えるように組み立てると以下のようなSQLになるのではないかと。
```SQL
select stk.location_id AS 'ロケーションID'
    , stk.product_id AS '商品ID'
    , stk.prdct_stock_sum AS '在庫数'
    , coalesce(stk.prdct_bought_sum, 0) AS '引当総数'
    , stk_loc.loc_stock_sum AS 'ロケ内総在庫数'
from (
       select location_id, product_id, sum(stk_stock_sum) as prdct_stock_sum, sum(bought_sum) as prdct_bought_sum
       from (
               select location_id, product_id, stock_id, SUM(stock_num) AS stk_stock_sum
               FROM Stock stk
               group by location_id, product_id, stock_id
           ) stk
           left join (
               select stock_id, SUM(bought_num) as bought_sum FROM BoughtStock group by stock_id
           ) bst
           on stk.stock_id=bst.stock_id
       group by location_id, product_id
   ) stk inner join (
       select location_id, sum(stock_num) as loc_stock_sum from Stock group by location_id
   ) stk_loc
   on stk.location_id=stk_loc.location_id
ORDER BY stk.location_id, stk.product_id
```
```DATA
CREATE TABLE Stock(stock_id int, location_id int, product_id int, stock_num int);
INSERT INTO Stock(stock_id, location_id, product_id, stock_num)
VALUES
   (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)
;
CREATE TABLE BoughtStock(stock_id int, bought_num int);
INSERT INTO BoughtStock(stock_id, bought_num)
VALUES
   (1, 1),
   (1, 1),
   (4, 1),
   (1, 1),
   (2, 1),
   (2, 1),
   (3, 1)
;
```
2 推敲

sazi

sazi score 23087

2018/11/14 21:34  投稿

`引当総数`や`ロケ内総在庫数`は`location_id`と`product_id`の組み合わせで一意であったり、値が変わらない為、MySQLの仕様([12.19.3 MySQL での GROUP BY の処理](https://dev.mysql.com/doc/refman/5.6/ja/group-by-handling.html))によって容認された結果に過ぎません。
※他のDBMSなら文法エラー
説明します。
先ず集計前の状態の以下のSQLは正しい結果だと思います。
```SQL
SELECT location_id, product_id, stock_num
    , IFNULL((SELECT SUM(bst.bought_num) FROM BoughtStock bst WHERE bst.stock_id = stk.stock_id), 0) AS '引当総数'
    , (SELECT SUM(stk2.stock_num) FROM Stock stk2 WHERE stk2.location_id = stk.location_id) AS 'ロケ内総在庫数'
FROM Stock
```
これを、`location_id`, `product_id`単位で集計としていますが、じゃあ、`引当総数`と`ロケ内総在庫数`はどんな集計されているの?って事です。これらは集計されず適当な何れかの値が採用されています。
相関問い合わせなので、それらの相関内の何れかの値が使用されていますが、何れも同じ値なので、求めたいものと同じになっているに過ぎません。
結果的にサブクエリーをさらに集計するような仕組みが働いているので、遅いのではないかと思われます。
基本に忠実に、SQLを組み立てると、必要なインデックスも明確になると思います。
性能が改善するかどうかわかりませんが、素直に組み立てると以下のようなSQLになるのではないかと。
性能が改善するかどうかわかりませんが、素直にキーを揃えるように組み立てると以下のようなSQLになるのではないかと。
```SQL
select stk.location_id AS 'ロケーションID'
    , stk.product_id AS '商品ID'
    , stk.prdct_stock_sum AS '在庫数'
    , coalesce(stk.prdct_bought_sum, 0) AS '引当総数'
    , stk_loc.loc_stock_sum AS 'ロケ内総在庫数'
from (
       select location_id, product_id, sum(stk_stock_sum) as prdct_stock_sum, sum(bought_sum) as prdct_bought_sum
       from (
               select location_id, product_id, stock_id, SUM(stock_num) AS stk_stock_sum
               FROM Stock stk
               group by location_id, product_id, stock_id
           ) stk
           left join (
               select stock_id, SUM(bought_num) as bought_sum FROM BoughtStock group by stock_id
           ) bst
           on stk.stock_id=bst.stock_id
       group by location_id, product_id
   ) stk inner join (
       select location_id, sum(stock_num) as loc_stock_sum from Stock group by location_id
   ) stk_loc
   on stk.location_id=stk_loc.location_id
ORDER BY stk.location_id, stk.product_id
```
```DATA
CREATE TABLE Stock(stock_id int, location_id int, product_id int, stock_num int);
INSERT INTO Stock(stock_id, location_id, product_id, stock_num)
VALUES
   (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)
;
CREATE TABLE BoughtStock(stock_id int, bought_num int);
INSERT INTO BoughtStock(stock_id, bought_num)
VALUES
   (1, 1),
   (1, 1),
   (4, 1),
   (1, 1),
   (2, 1),
   (2, 1),
   (3, 1)
;
```
1 追記

sazi

sazi score 23087

2018/11/14 18:25  投稿

`引当総数`や`ロケ内総在庫数`は`location_id`と`product_id`の組み合わせで一意であったり、値が変わらない為、MySQLの仕様([12.19.3 MySQL での GROUP BY の処理](https://dev.mysql.com/doc/refman/5.6/ja/group-by-handling.html))によって容認された結果に過ぎません。
※他のDBMSなら文法エラー
説明します。  
先ず集計前の状態の以下のSQLは正しい結果だと思います。  
```SQL  
SELECT location_id, product_id, stock_num  
    , IFNULL((SELECT SUM(bst.bought_num) FROM BoughtStock bst WHERE bst.stock_id = stk.stock_id), 0) AS '引当総数'  
    , (SELECT SUM(stk2.stock_num) FROM Stock stk2 WHERE stk2.location_id = stk.location_id) AS 'ロケ内総在庫数'  
FROM Stock  
```  
これを、`location_id`, `product_id`単位で集計としていますが、じゃあ、`引当総数`と`ロケ内総在庫数`はどんな集計されているの?って事です。これらは集計されず適当な何れかの値が採用されています。  
相関問い合わせなので、それらの相関内の何れかの値が使用されていますが、何れも同じ値なので、求めたいものと同じになっているに過ぎません。  
サブクエリーをさらに集計するような仕組みが働いているので、遅いのではないかと思われます。
結果的にサブクエリーをさらに集計するような仕組みが働いているので、遅いのではないかと思われます。
基本に忠実に、SQLを組み立てると、必要なインデックスも明確になると思います。
性能が改善するかどうかわかりませんが、素直に組み立てると以下のようなSQLになるのではないかと。
```SQL
select stk.location_id AS 'ロケーションID'
    , stk.product_id AS '商品ID'
    , stk.prdct_stock_sum AS '在庫数'
    , coalesce(stk.prdct_bought_sum, 0) AS '引当総数'
    , stk_loc.loc_stock_sum AS 'ロケ内総在庫数'
from (
       select location_id, product_id, sum(stk_stock_sum) as prdct_stock_sum, sum(bought_sum) as prdct_bought_sum
       from (
               select location_id, product_id, stock_id, SUM(stock_num) AS stk_stock_sum
               FROM Stock stk
               group by location_id, product_id, stock_id
           ) stk
           left join (
               select stock_id, SUM(bought_num) as bought_sum FROM BoughtStock group by stock_id
           ) bst
           on stk.stock_id=bst.stock_id
       group by location_id, product_id
   ) stk inner join (
       select location_id, sum(stock_num) as loc_stock_sum from Stock group by location_id
   ) stk_loc
   on stk.location_id=stk_loc.location_id
ORDER BY stk.location_id, stk.product_id
```
```DATA
CREATE TABLE Stock(stock_id int, location_id int, product_id int, stock_num int);
INSERT INTO Stock(stock_id, location_id, product_id, stock_num)
VALUES
   (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)
;
CREATE TABLE BoughtStock(stock_id int, bought_num int);
INSERT INTO BoughtStock(stock_id, bought_num)
VALUES
   (1, 1),
   (1, 1),
   (4, 1),
   (1, 1),
   (2, 1),
   (2, 1),
   (3, 1)
;
```

思考するエンジニアのためのQ&Aサイト「teratail」について詳しく知る