- PR -

MySQL でクエリを分割せずに Error 1093 を回避するには?

1
投稿者投稿内容
asahi
会議室デビュー日: 2009/01/10
投稿数: 5
投稿日時: 2009-01-10 16:58
MySQL にて以下のテーブル t1 に対して
「ある行の id と value を入力とし、その行から上に向かって value を順に見ていき、入力された value 以下の value を持つ最初の id を見つけ、見つかった id と入力された id の間に挟まれている行の value をすべて +1 する」
という処理を行おうとしています。
なお、id=1 はターミネータであり、入力の id は 2 以上を仮定して構いません。id はユニーク、value は非負整数です。

テーブル t1
id, value
1, 0
2, 1
3, 1 ←以下の例で見つけたい行(value が 2 以下の最初の行)
4, 4 ←以下の例で更新したい行
5, 3 ←以下の例で更新したい行
6, 2 ←以下の例での入力行 (この行から上に向かって探索)
7, 3
8, 2
9, 1

例えば入力が id=6, value=2 の場合、見つける id は 3 で、value を更新するのは id=4 と id=5 です。
最初に考えたクエリは
 UPDATE t1
  SET value=value+1
  WHERE (id > (SELECT MAX(id)
             FROM t1
             WHERE (id < 6) AND (value <= 2)))
   AND (id < 6);
です。

しかし、これだと
ERROR 1093 (HY000): You can't specify target table 't1' for update in FROM claus
というエラーになってしまいます。
http://dev.mysql.com/doc/refman/4.1/ja/subquery-errors.html
の一番下によるとサブクエリの FROM に更新対象の t1 は指定できないとのことです。

とりあえずユーザー変数を使って
 SET @start_value = (SELECT MAX(id) FROM t1 WHERE (id < 6) AND (value <= 2));
 UPDATE t1 SET value=value+1 WHERE (id > @start_value) AND (id < 6);
の 2 文に分割すれば実現できるのですが、クエリを分割せずに解決することは可能でしょうか?


追記
MySQL のバージョンは 5.0.67 です。

[ メッセージ編集済み 編集者: asahi 編集日時 2009-01-10 17:06 ]
明智重蔵
大ベテラン
会議室デビュー日: 2005/09/05
投稿数: 127
投稿日時: 2009-01-11 13:27
そこで、結合を使うupdate文ですよ

コード:
create table t1(
id    int,
value int);

delete from t1;
insert into t1 values
(1, 0),
(2, 1),
(3, 1),
(4, 4),
(5, 3),
(6, 2),
(7, 3),
(8, 2),
(9, 1);

update t1,(SELECT MAX(a.id) as tid
             FROM t1 a
            WHERE id < 6 AND value <= 2) as b
set value = value+1
where tid < id
  and id < 6;


asahi
会議室デビュー日: 2009/01/10
投稿数: 5
投稿日時: 2009-01-11 15:06
ご回答ありがとうございます。
サブクエリの結果をテーブル b として用意しておくわけですね。
私はまだ結合をうまく使いこなせないので、もっと勉強してみます。

ちなみに、例では (6, 2) を入力としましたが、value は id から得られるので最終的に以下のクエリになりました。

コード:
UPDATE t1, (SELECT MAX(id) as tid
              FROM t1
             WHERE (id < 6)
               AND (value <= (SELECT value
                                FROM t1
                               WHERE id = 6))) as t2
   SET value = value + 1
 WHERE (tid < id)
   AND (id < 6);



とりあえず当初の目的は達したのでクローズとしますが、
もし他にこうした方がいいという代案がありましたら追加返信も歓迎です。

ありがとうございました!
忠犬
大ベテラン
会議室デビュー日: 2006/05/01
投稿数: 109
投稿日時: 2009-01-13 09:49
なぜ、こんな扱いにくいテーブル設計にしたのでしょう?

SQLの構文を聞くより、テーブルの設計方法を聞いたほうがいいかも知れません。
asahi
会議室デビュー日: 2009/01/10
投稿数: 5
投稿日時: 2009-01-13 13:21
スレタイからは逸脱してしまいますが、テーブル設計の話になったのでもう少し詳しく書いてみます。
テーブル設計は初めてなので一般的でなかったり非効率なテーブルになってしまっているかもしれません。ベテランの方々の意見を聞かせていただければ幸いです。

まず、最初に書いたテーブルは質問のために簡略化したテーブルであり、以下がもう少し実際のテーブルに近いものです (カラム名は上記例に合わせました) 。

コード:
create table t1 (
    pkey int auto_increment primary key,
    id int,
    value int,
    …(ノードの属性が複数)…);



このテーブルは多分木の構造を表したもので、上記の値の例は以下の多分木になります。

◇root (id=1, value=0)
┠┐
┃◆ (id=2, value=1)
┃│
┃◆ (id=3, value=1)
┃├┬┬┐
┃│││◆ (id=4, value=4)
┃││◆ (id=5, value=3)
┃│◆ (id=6, value=2)
┃│├┐
┃││◆ (id=7, value=3)
┃│◆ (id=8, value=2)
┃◆ (id=9, value=1)
◇terminator (id=10, value=0)

上記では terminator を省略していましたが、ノードの挿入や削除に伴うテーブル更新の際に番兵があると便利なことがあるので root と terminator を置いてあります。
ノードの挿入や削除を行うので、pkey と id の順序は対応せず、id でソートする必要があります。
ノードの挿入や削除に伴って id の値も更新します (本当は昇順にさえなっていれば十分なのですが、一応歯抜けがないように維持しています) 。
この構造はあるノードの子孫を一括で取得したり、ある 2 つのノードに先祖-子孫の関係があるかどうかを判断しやすいことを意識して設計しました。

今回の質問に挙げたクエリはあるノードを削除したときの value の更新処理で、サブクエリが削除ノードの親ノードを探す処理に相当します。例では value = value + 1 としていましたが、削除処理なので実際には value = value - 1 を行います。
また、例では id = 6 を指定しましたが、直接の子を複数持つノード (分岐しているノード) の削除は認めない仕様なので、id = 3 と id = 6 は実際には指定されません。
例えば id = 9 のノードを削除した場合、更新後の多分木は以下のようになります。

◇root (id=1, value=0)
┠┐
┃◆ (id=2, value=1)
┃│
┃◆ (id=3, value=1)
┃├┬┐
┃││◆ (id=4, value=3)
┃│◆ (id=5, value=2)
┃◆ (id=6, value=1)
┃├┐
┃│◆ (id=7, value=2)
┃◆ (id=8, value=1)
◇terminator (id=9, value=0)


自分ではかなりシンプルな表し方にできたと思っているのですが、データベースとの親和性に関しては自信がありません。
asahi
会議室デビュー日: 2009/01/10
投稿数: 5
投稿日時: 2009-01-13 23:39
その後考えてみたのですが、親ノードの pkey を表す parent カラムを追加しようと思います。そうすればそもそも value 自体 (とその更新処理) を不要にできそうです。代わりに parent カラムの更新処理が必要になりますが、value に比べて維持が簡単そうです。

自己完結してしまいましたが、再考のきっかけをくれた忠犬さん、どうもありとうございました。

ちなみにこれはデータベースで扱いやすい「データの表現方法」を考えただけで、忠犬さんの仰る「テーブル設計」とは違う次元の話なのかも?
1

スキルアップ/キャリアアップ(JOB@IT)