一覧の並び順の変更方法について

システム開発でよく、一覧表示の並び順を任意に変更したいといった要望が出たりします。
簡単な対応方法としては並び順の数値フィールドを設けて、数値の小さい順に表示するようにしておいて、
並び順の数値を設定してもらうなどがあると思います。
ただ、それだと一度連番を振った後に、間に新しい内容を持ってきたいときに、並び順の修正に手間がかかってしまうなど、
利用者側が並び順の数値を意識する必要があって大変です。
そこで、並びを上下に移動させられるようなボタンを一覧に表示させると便利なんですが、
どのように処理すればいいか悩むところです。
すべての行の並び順を見ながら数値を調整していくのがベタなやり方ですが、
全行数分UPDATE文を発行したりすることになって、いまいちスマートではありません。
そこで、もっといい方法はないかと考えてみて、うまくいきそうな方法があったので、忘れないうちに記録しておきます。
[考え方]
並び順の数値の初期値は0で昇順で並び、同じ値の場合は主キーの昇順で表示される一覧を想定します。
3行目を下げて4行目と入れ替えたい場合の処理は、

  1. 4行目の並び順の数値を取得しaとする。
  2. 3行目の並び順の数値-aをbとする。
  3. 5行目以降の並び順の数値をそれぞれからb+2を足した値に更新する。
  4. 3行目の並び順の数値をb+1を足した値に更新する。

とすると、行数にかかわらず1行を取得する2つのSELECT文と2つのUPDATE文だけで並び順を変更できます。
PHPぽく書くと、

    $table = [対象テーブル(id, sorter列を持つ)];
    $id = [下げたい行の主キー];
    if($sorter = get_one("SELECT sorter FROM $table WHERE id = $id")) {
        $where = "sorter > $sorter OR (sorter = $sorter AND id > $id)";
        if($near = get_row("SELECT id, sorter FROM $table WHERE $where ORDER BY sorter, id")) {
            $offset = $sorter - $near['sorter'];
            query("UPDATE $table SET sorter = sorter + ($offset + 2) WHERE $where AND id <> $near[id]");
            query("UPDATE $table SET sorter = sorter + ($offset + 1) WHERE id = $id");
        }
    }

※get_oneはSELECT結果の1行1列目の値を取得、get_rowは1行目の値を列名との連想配列で取得、queryはSQLを実行するような関数

逆に、4行目を上げて3行目と入れ替えたい場合は、

  1. 3行目の並び順の数値を取得しaとする。
  2. a-4行目の並び順の数値をbとする。
  3. 3行目より上の行の並び順の数値をそれぞれからb+2を引いた値に更新する。
  4. 3行目の並び順の数値をb+1を引いた値に更新する。
    $table = [対象テーブル(id, sorter列を持つ)];
    $id = [上げたい行の主キー];
    if($sorter = get_one("SELECT sorter FROM $table WHERE id = $id")) {
        $where = "sorter < $sorter OR (sorter = $sorter AND id < $id)";
        if($near = get_row("SELECT id, sorter FROM $table WHERE $where ORDER BY sorter DESC, id DESC")) {
            $offset = $near['sorter'] - $sorter;
            query("UPDATE $table SET sorter = sorter - ($offset + 2) WHERE $where AND id <> $near[id]");
            query("UPDATE $table SET sorter = sorter - ($offset + 1) WHERE id = $id");
        }
    }