日別アーカイブ: 2012 . 12 / 31

Ruby on Rails 3.2 Arel + Postgresql のgroup – having節でエラー

やりたかったのは人物の検索結果を出すことです。
検索条件として、例えば男性で、東京在住であるというような、その人物の「属性」でand検索するというものになります。

いきさつははしょりますが、簡単に言うとsqliteでの開発段階でgroup having節を使っていた部分が、postgresqlに変更してからエラーがでたということが発端になります。

postgresqlでは

must appear in the GROUP BY clause or be used in an aggregate function

というエラーがでて、目的のことをやるには一旦select文を分割せねばならないようでした。

SQLを直接書けばそれ自体は難しくないのですが、なんとかArelをできるだけ活用してできないものかと試行錯誤しました。

ハードルはどのようにサブクエリをarelで組み立てるのかということ。

この問題に関してはこちらの記事が参考になりました。

まず、クラスにたいしarel_tableメソッドでArel::Tableクラスのインスタンスを取り出します。
※pattributes_peopleテーブルに対しPattributePersonクラスです。察しの通り、これはPersonとPattributeクラスの関連クラス

sub = PattributePerson.arel_table

次に条件のひとつとして使用するArel::Attributes::Attributeクラスのインスタンスを作成します
※pattributes は[‘1′,’2’,…] のような属性のIDが入った配列です。
AND検索なのでinにしたいところですが、ここでは一旦in_anyで該当する行をすべて取り出しておきます。

attr = sub[:pattribute_id].in_any(pattributes)

最後にサブクエリの本体となるArel::SelectManagerクラスのインスタンスを取り出します。

select_manager = sub.project(table[:person_id]).where(attr).group(:person_id).having(“COUNT(person_id) >= #{pattributes.length}”)

→pattribute_idがpattributeのリストに一致するものすべてを取り出しておいて、person_idが重複しないように圧縮します。
そのときperson_idがpattributesの数分だけあるもののみ取り出しています。つまり、pattributeが全部該当した者=ANDと同じ。

これを他のArel::Tableでくっつけてひとつの条件(Arel::Attributes::Attributeクラスのインスタンス)を作成します。

person_table = Person.arel_table
attr2 = person_table[:id].in(select_manager)

 あとはこのままpeopleとして取得するなり、他の条件と結合するなりできます。

@result = Person.includes(:user)
@result.where(attr2)

group節がサブクエリになったことでエラーは出なくなりました。