LoginSignup
17
25

More than 5 years have passed since last update.

ActiveRecord 4.2以上でunionする方法

Posted at

ActiveRecordを使っていてunionしたいパターンができたので、やってみようと思ったら、思いの外ハマってしまったので情報共有したいと思います。

ActiveRecordにunionというメソッドがあることを知った私は、これでできるんじゃないの?と思って意気揚々と使ったのですが、なぜかエラーがおきました。

union = MyGroup.where(user_id: user).reorder(nil).union(
  MyGroup.limited.where(user_id: user.members).reorder(nil)
)

MyGroup.from(MyGroup.arel_table.create_table_alias(union, :my_groups).to_sql)

MyGroup.limitedは、公開範囲をActiveRecord::Enumを使っていて、限定公開をlimitedというスコープで作っています。

これを実行すると…エラーになります。

PG::ProtocolViolation: ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1
: SELECT "my_groups".* FROM ( SELECT "my_groups".* FROM "my_groups" WHERE "my_groups"."user_id" = 209 UNION SELECT "my_groups".* FROM "my_groups" WHERE "my_groups"."status" = $1 AND "my_groups"."user_id" IN (SELECT "users"."id" FROM "users" WHERE "users"."id" IN (255, 256, 209)) my_groups 

"my_groups"."status" = $1になっていて、limitedに割り当てられた数値になっていません。

unionメソッドがうまく使えなかった原因

原因は上記の通り、to_sqlを使ったタイミングでActiveRecordの検索条件に設定した値が置き換えられていなかったからです。to_sqlメソッドでSQL文を受け取る時には置換されていると思っていたのですが、unionメソッドを使うと戻り値がActiveRecord::Relationではなく、Arel::Nodes::Unionになるようです。そして、ActiveRecord::Relationのto_sqlメソッドとArelのto_sqlメソッドでは動作が違う模様です。

参考URL:to_sql in Rails 4.2 returns parameterized queries instead of full SQL statements

参考URLでリンクされているチケットのどれかに書いてありましたが、『機能の不足はバグではない』、という言葉で終わっていて、とりあえずバグとして対応されるということはなさそうです。

対応方法

ダメだったやつ(unprepared_statement)

参考URLでは、unprepared_statementを使えばいいと書いてありましたが、これを使ってもダメでした。

union_sql =  MyGroup.connection.unprepared_statement do
  MyGroup.where(user_id: user).reorder(nil).union(
    MyGroup.limited.where(user_id: user.members).reorder(nil)
  ).to_sql
end
MyGroup.from("#{union_sql} my_groups")

エラーの内容は変わらず。

うまくいったやつ(arel_tableを使う)

Arelのto_sqlの結果が云々とあったので、該当箇所のwhere文をarel_tableを使ったものに変更すればうまくいくのでは?と思い、やってみたところ、うまくいきました。

union = MyGroup.where(user_id: user).reorder(nil).union(
  # limitedスコープをarel_tableを使って表現するよう変更
  MyGroup.where(user_id: user.members).
  where(MyGroup.arel_table[:status].eq MyGroup.statuses[:limited]).reorder(nil)
)

MyGroup.from(MyGroup.arel_table.create_table_alias(union, :my_groups).to_sql)

まとめ

ArelにはArelをぶつけよう。

17
25
0

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
17
25