ActiveRecordのincludesとjoinsの使い分け プラスα

まずは前提知識。

ActiveRecordで、テーブルを結合する際にincludesで結合するかjoinsで結合するかの違いは

 

 

と、いう違いがあるけれど。

avg()などの集約関数を使ったときに、何故かincludesを使うとうまくいかない。

でもjoinだとうまくいく。

 

と、いうことがある。

 

includesで結合した場合

Student.includes(:exams).group('students.id').having('avg(exams.score) >= 70')

ActiveRecord::StatementInvalid: PG::Error: ERROR: 列"exams.id"はGROUP BY句で出現しなければならないか、集約関数内で使用しなければなりません

SQLのエラーが発生。

 

joinsで結合した場合

Student.joins(:exams).group('students.id').having('avg(exams.score) >= 70')

=> [#<Student id: 1, name: "Bob", created_at: "2013-01-30 12:37:21", updated_at: "2013-01-30 12:37:21">, #<Student id: 2, name: "John", created_at: "2013-01-30 12:37:27", updated_at: "2013-01-30 12:37
:27">]

期待した結果になる。

 
 

原因は、includesでの結合とjoinsでの結合で、生成されるSQLのSELECT句で選択しようとしている項目の部分が、何故かかなり違ってくるため。

 

includesで結合しようとした場合のSQL

※ちなみに、6行目から10行目を削除すると、エラーが発生しないSQLになる。
 逆に言えば、勝手にSELECT句に余計なものが追加されることが問題の原因。

 

joinsで結合した場合のSQL

 
 

何故こんなにもSQLが異なる結果になるのかは謎。

 

Rails 3 – select with Include?によると

As @Bhavesh_A_P pointed out above, select with includes does not produce consistent behavior. It appears that if the included association returns no results, select will work properly, if it returns results, the select statement will have no effect. In fact, it will be completely ignored, such that your select statement could reference invalid table names and no error would be produced. select with joins will produce consistent behavior.

≒includesとselectをいっしょに使うと一貫した結果にならなず、selectが無視される。

 

多分これと同じ理由で、includesで結合するとSELECT句がおかしくなるんだろうなと思う。

 
 

と、いうことで、includesで結合して何故かうまくいかない場合には、joinsで結合してみたら、案外うまくいくかもしれないよ?というお話。

 
 

ちなみに、データベースエンジンがSQLiteだと文法に寛容なためかエラーは発生しない。

今回の問題は、データベースエンジンにPostgresqlを使っていてハマったため、Postgresqlユーザーは特に注意。

コメントを残す

トラックバック: http://pgnote.net/wp-trackback.php?p=1865