LoginSignup
29
23

More than 5 years have passed since last update.

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

Posted at

事の顛末

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を使わなきゃいけない状況に陥ったときに参考にでもどうぞ

29
23
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
23