どもです。
要件としては、以下。
TABLE01 | |
---|---|
ID | quantity |
A | |
B | |
C |
TABLE02 | |
---|---|
ID | quantity |
A | 10 |
A | 15 |
B | 2 |
C | 1 |
C | 6 |
TABLE2の各IDごとのquantityを合計して、TABLE01をUPDATEしたい。
具体的には、TABLE01をこう更新したい。
TABLE01 | |
---|---|
ID | quantity |
A | 25 |
B | 2 |
C | 7 |
# 失敗例
UPDATE
TABLE01 t1
INNER JOIN
TABLE02 t2 ON t1.id = t2.id
SET
t1.quantity = SUM(t2.quantity);
# 結果→ Invalid use of group function
どうしたものか。
もちろんIDを抜き出してforeachして1行ごとにUPDATEを実行すれば片が付くものの、想定されるTABLE01の行数がかなり多いので、それは最終手段にしたい。
UPDATE
TABLE01 t1
INNER JOIN
(SELECT id, SUM(quantity) AS quantity FROM TABLE02 GROUP BY id) tmp
ON t1.id = tmp.id
SET
t1.quantity = tmp.quantity;
サブクエリで作成した疑似的なテーブルにJOINすることで解決できました。
本当に?
サブクエリを利用する以上、重くならないかという懸念が生じます。
まずはサブクエリ .. SELECT * FROM t1が展開される。100万行に対するフルフェッチ。
その100万行に対してPRIMARYのクエリ、SELECT .. FROM .. WHERE ..が実行される。
サブクエリの結果にはINDEXが無いので、これもまるまるフェッチ。
合計200万行フェッチしている算段。
[日々の覚書]MySQLのFROM句サブクエリの順番
つまり、
①サブクエリで一時テーブルをメモリ上に作成
②一時テーブルに対してJOINし、UPDATEを実行。
という手順を辿っており、かつ一時テーブルにはINDEXが存在しないため、一時テーブルの内容が膨大であった場合には多大な時間がかかってしまうわけです。
サブクエリで獲れる件数が1件とか2件とかじゃないなら、INDEXの貼れるテンポラリテーブルを作成した方が良いってことです。
CREATE TEMPORARY TABLE tmp (
`id` VARCHAR(2) NOT NULL,
`quantity` TINYINT(3) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO tmp(id, quantity)
SELECT TABLE02.id, SUM(TABLE02.quantity) AS quantity
FROM TABLE02
GROUP BY TABLE02.id;
UPDATE
TABLE01
INNER JOIN
tmp ON TABLE01.id = tmp.id
SET
TABLE01.quantity = tmp.quantity;
実際の案件(もちっと複雑なSQL)で検証してみましたが、TABLE02が5,000件程度として、抽出したtmpが10件程度でも、サブクエリの方が平均3倍くらい時間かかりました。
これ系のケースは多少面倒でもテンポラリテーブル使った方が良いですね。