ど素人から毛を生やす。<延>

[MySQL]SUMしてUPDATE

Web > Other 2019年8月15日(最終更新:3月前)

どもです。
要件としては、以下。

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倍くらい時間かかりました。

これ系のケースは多少面倒でもテンポラリテーブル使った方が良いですね。

この記事は役に立ちましたか?
  • _(:3」∠)_ 面白かった (0)
  • (・∀・) 参考になった (0)
  • (`・ω・´) 役に立った (0)