laravelのクエリビルダでJOINしたら、同名カラムの値が上書きされるという事案発生


事の顛末

LaravelのクエリビルダでJOINしてテーブルの値を持ってきたときに、JOINで結びついた2つのテーブルが同じ名前のカラムを持っていた場合、従テーブルの値で上書きされて死んだ。
みなさんもお気をつけてください。

もっと詳しく説明

例えば、多対多の関係でUserテーブルとGroupテーブル、その関係を表す中間テーブル(User_Groupテーブル)があるとしましょう。
Userは色んなグループに所属でき、グループには複数のUserがいるって感じですね。
テーブルはこんな感じ。

■Userテーブル

id name is_deleted
1 太郎くん 1
2 次郎くん 0

■Groupテーブル

id name is_deleted
1 図書委員 0
2 保険委員 0
3 体育委員 0

■User_Groupテーブル(中間テーブル)

id user_id group_id
1 1 1
2 2 2

※id_deltedは論理削除用の適当なフラグだとでも考えてください。

こんなときに、図書委員という名前のグループに所属していて、かつis_deletedが1のUserを全て持ってきたいと思ったときにみなさんどうします?
やり方はパッと2通りあるかと思います!

その1、 whereHas関数を使う
Userモデルクラスを作成して、その中で下記のようにwhereHasを使って子テーブルのカラムを指定してあげればOKです。1対多でも多対多でも大丈夫みたいですね。

UserModel.php
$userModel
->where('is_deleted',1)
->whereHas('group', function ($query) use () {
    $query->where('name', 'like', '%図書委員%');
})->get();

これが一番わかりやすくて良いと思うのですが、どうやらwhereHasは処理が遅いらしく、whereHasの使用を禁じてJOINを使って解決しましょうというルールを今回敷かれました。(Laravel公式でこれを使えと言ってるのですがそれは)
そこで次です。

その2、JOINで頑張る。

UserModel.php
return DB::table('User')
->join('User_Group', 'User.id', '=', 'User_Group.user_id')
->join('Group', 'User_Group.group_id', '=', 'Group.id')
->where('User.is_deleted', '=', 1)
->where('Group.name', 'like', '%図書委員%')
->get();

Userテーブルに対して中間テーブルをJOINし、続けてGroupテーブルもJOINします。
その上でwhere条件で絞ってあげれば大丈夫でしょう。

・・・と思って返却されたコレクションの値を見たら

■返却されたコレクション

返ってきたコレクションの中身
#items: Collection {#2384 ▼
  #items: array:1 [▼
    0 => {#2380 ▼
      +"id": 1
      +"is_deleted": 0
      +"created_at": "2018-04-17 19:22:00"
      +"updated_at": "2018-04-17 19:22:00"
      +"group_id": 1
    }
  ]
}

あれ!?where条件でis_deletedに1を指定して取ってきたのに、返却されてきたコレクションにはis_deletedが0で入ってる!!

どうやらGroupテーブルにある同一カラム(is_deleted)の値で上書きされてしまっているようです。こんなん引っ掛かるよ...
これの詳しい仕組み知っている人いたら教えてください〜〜〜!!

解決策

selectする値を明示的に指定する

UserModel.php
return DB::table('User')
->join('User_Group', 'User.id', '=', 'User_Group.user_id')
->join('Group', 'User_Group.group_id', '=', 'Group.id')
->where('User.is_deleted', '=', 1)
->where('Group.name', 'like', '%図書委員%')
->select('User.is_deleted') //これを追加
->get();

のように、selectを追加してどのテーブルのカラムかを明示的に指定してあげればOKでした。

whereHasがスロークエリだということですがどれくらい遅いんでしょうかねえ。。。
まあJOINを使わなきゃいけない状況に陥ったときに参考にでもどうぞ