You can't specify target table 'xxx' for update in FROM clause

のっけから、MySQLのエラー文である。

初めて目にしたエラー。

なんのことやら、さっぱり。

ググってみたら、出てくる出てくる。

MySQL 4.1 リファレンスマニュアル
http://dev.mysql.com/doc/refman/4.1/ja/subquery-errors.html

サブクエリは SELECT ステートメントと同じく、UPDATE ステートメントや DELETE ステートメントでも正式に使用できるので、UPDATE ステートメント内の割り当てにサブクエリを使用しても問題ありません。 しかし、サブクエリの FROM 節と更新対象の両方に同じテーブルを使用することはできません。

今回、僕がエラーに遭遇したクエリ文が、コレ↓

$sql = <<<SQL
UPDATE `{$tblname}`
SET `token` = 'logout_{$nowTime}'
WHERE `login_time` = (
  SELECT MAX(`login_time`)
  FROM `{$tblname}`
  WHERE `user_id` = '{$user_id}'
)
SQL;

そう、まさしく前回の記事“今日もMySQL。サブクエリ文について。”で書いた、サブクエリ文を用いたクエリ文である。

つまりは、サブクエリ文で読んでるテーブルを、同一クエリ文で更新することはできないのである。

MySQL で You can’t specify target table ~ for update in FROM clause エラー
http://futuremix.org/2007/08/mysql-update-with-subquery

PostgreSQL や Oracle ではこのようなエラーは起きずに更新が完了します。対処方法としてはテンポラリテーブルを使うか MySQL をやめること。テンポラリテーブルも同一クエリ内では2回以上使えなかったりと制限があります。

MySQL はちょっと凝ったことをしようとすると途端にできなくなるので、本当に困ります。

MySQLでサブクエリ(エラー#1093を回避する方法)
http://wsjp.blogspot.jp/2009/12/mysql1093.html

MySQLだからではなく、SQL標準から見ても間違った構文なんだそうだ。

、、、まあ、色々あるワケで(汗)

上記、“MySQLでサブクエリ(エラー#1093を回避する方法)”に、以下のようなことが書いてあった。

INSERT INTO exam ( type, code ) VALUES (
'A', (
SELECT IFNULL( MAX( code ) + 1, 1000 )
FROM exam WHERE type='A' )
);

#1093 - You can't specify target table 'exam' for update in FROM clause

と怒られてしまった。

(中略)

SQLでは、サブクエリー内のFrom句はテンポラリテーブルとして扱うことが可能で、次のように書き換えれば動くとのこと。

INSERT INTO exam ( type, code ) VALUES (
'A', (
SELECT IFNULL( max_code + 1, 10001 )
FROM ( SELECT MAX( code ) AS max_code FROM exam WHERE type='A' ) AS temp1 )
);

何をしているのかというと、
まさにサブクエリー内のFromを
「SELECT MAX( code ) AS max_code FROM table WHERE type='A'」
というクエリで書き換えて、それをtemp1とか名前をつけてます(つけないと怒られます)。

つうことは、僕の場合、

$sql = <<<SQL
UPDATE `{$tblname}`
SET `token` = 'logout_{$nowTime}'
WHERE `login_time` = (
  SELECT MAX(`login_time`)
  FROM `{$tblname}`
  WHERE `user_id` = '{$user_id}'
)
SQL;

これを、

$sql = <<<SQL
UPDATE `{$tblname}`
SET `token` = 'logout_{$nowTime}'
WHERE `login_time` = (
  SELECT `max_time`
  FROM (
    SELECT MAX(`login_time`) AS `max_time`
    FROM `{$tblname}`
    WHERE `user_id` = '{$user_id}'
  ) AS `temp`
)
SQL;

とすりゃ、ウマくいくということか?

結果。

ウマくいった。

いやあ、色々難しいねえ。

勉強勉強。