これまた、データベースの話ですが、長期でアクティブに管理しているプロジェクトでは必ず登場してくる作業です。

どういうときに必要?

例えば、以下のようなデータの会員テーブル(users)があります。

id name rank
1 高橋 美加子 A
2 浜田 太一 B
3 中島 充 A
4 渚 あすか B
5 渚 和也 A

最後の項目は、会員ランクで過去2年間に会員が購入した商品の送金額により会員ランクが決まります。会員ランクには、AとBの2つあり、会員ランクAの会員は、購入の際に5%の割引があるなどの恩恵があります。Bランクの会員は残念ながら恩恵なしです。

さて、会員数も増えてきて、Aランク会員が多すぎて購入額の総額も同じランクでばらつきがあることから、ランクを1つ増やすことにします。つまり、今度はA,B,Cと3つのランクとなります。

もちろん、Artisanコマンドを作成して新たなランク計算式を使い、usersのそれぞれのレコードを更新することも可能ですね。こんな感じで。

User::all()->each(function($user) {
    $user->rank = $user->calcNewRank();
    $user->save();
})

しかし、これではもとのランクの値がなんであったか残りません。更新前に、usersのテーブルをdumpしてファイルとして残してもよいですが、もしかしたら、移行後にも旧のランク値はクレームときのために必要かもしれません。

値の移行のためのテーブルを新規に作成

このようなときは、まず、一時的に使用するテーブルを作成して、そこでusersのそれぞれのレコードに対応する旧と新のランクをあらかじめレコードを作成します。テーブル名は、change_rankとして以下のようなデータとなります。

user_id name old_rank new_rank
1 高橋 美加子 A A
2 浜田 太一 B C
3 中島 充 A B
4 渚 あすか B C
5 渚 和也 A B

change_rankのテーブルとusersのテーブルは、両者のプライマリーキー(users.idとchange_rank.user_id)を介してまったく1対1の関係となり、後の一括更新に使用されます。さらに、この移行のテーブルを残しておけば、旧と新のランク値のスナップショットとなり、何かのときにとても有用なものとなります。

一括更新

通常更新は、1つのテーブルにおいて、例えば以下のように更新します。

UPDATE users SET rank = 'A';

しかし、今回のケースは2つのテーブルが関わる、つまり1つのテーブルの値をもとにもう1つのテーブルの値の更新となるので、joinが必要となります。SQL文では以下のようになります。

UPDATE users
INNER JOIN change_rank ON users.id = change_rank.user_id
SET user.rank = change_rank.new_rank;

さて、これをSQL文でなく、クエリビルダーで行うなら、以下のようなコードとなります。

DB::table('users')
    ->join('change_rank', 'users.id', '=', 'change_rank.user_id')
    ->update(['users.rank' => DB::raw('change_rank.new_rank')]);

ここ以下のように書いてしまいそうですが、DB::raw()がないとエラーとなるので注意を。

DB::table('users')
    ->join('change_rank', 'users.id', '=', 'change_rank.user_id')
    ->update(['users.rank' => change_rank.new_rank]);

By khino