フレームワークでデータをORMがらみでjoinするときのネタ。
自分の場合はLaravel。他のフレームワークでも考え方は通じるものあるかと・・
通常のjoin
select句に何も指定せずクエリビルダーでjoin句を使うとプロパティがフラットになってしまいます。これだとカラム名が競合した時に片方が消されてしまいます。
例えばorderからorderitemをjoinしようと思った時にorderの1プロパティの中にorderitemがあり、この中にorderitemの全てのプロパティがあるというのが理想的です。
ループの中で取得
親(order)の数×子供の数(orderitem)となってしまい計算量的にありえないのでNG。
hasManyなどを動的に使用
Laravelだとリレーションを以下のように表現することでプロパティの一部として取得できます。User-Postの関係が1:Nとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { public function posts() { return $this->hasMany('App\Post'); } } |
1 2 |
$user = User::find(1); $posts = $user->posts; |
https://readouble.com/laravel/5.1/ja/eloquent-relationships.html
上記のようなリレーションを動的に組む方法がベストっぽいです。
これだと親のレコードの中にプロパティとして格納されるため、通常のjoinと違ってカラムが消されたりってことはありません。
ただこの手法だとjoin自体が実行されるのは親自体(order)をループする時になり、DBへのアクセス数が親の数×子供の数となってしまいます。(これが俗にいうN+1問題と言われるものらしいです。)
通常の取得をlazy loadingというようですね。
eager loading
これを解決するのがeager loadingという手法のようでクエリを発行した瞬間にDBへのアクセスも終わります。
こうすることで
- 親のプロパティの中に子供のオブジェクトを格納し、
- かつ計算量も抑える
ということができます。Laravelですと、以下のようにwithを付与することで解決します。
1 2 3 4 |
$users = User::with('posts')->get(); foreach($users as $user) { $user->posts; } |
えらい単純なことですが、これに行きつくまでにそこそこ時間がかかりました・・・(汗)
【Laravel】動的プロパティとリレーションメソッド。そしてN+1問題を真剣に調べてみた
参考リンク
laravelじゃないですが、参考になりそう。