ActiveRecord Arel OR条件

你怎么能结合2个不同的条件使用逻辑OR而不是AND?

注意: 2个条件是作为rails范围生成的,不能直接更改为where("x or y")

简单的例子:

 admins = User.where(:kind => :admin) authors = User.where(:kind => :author) 

应用AND条件很容易(对于这个特殊的情况是毫无意义的):

 (admins.merge authors).to_sql #=> select ... from ... where kind = 'admin' AND kind = 'author' 

但是,怎样才能生成以下具有2种不同的Arel关系的查询呢?

 #=> select ... from ... where kind = 'admin' OR kind = 'author' 

看来( 根据Arel自述 ):

OR运算符尚不支持

但我希望它不适用于此,并希望写下如下内容:

 (admins.or authors).to_sql 

我对派对有点迟,但这是我能想到的最好的build议:

 admins = User.where(:kind => :admin) authors = User.where(:kind => :author) admins = admins.where_values.reduce(:and) authors = authors.where_values.reduce(:and) User.where(admins.or(authors)).to_sql # => "SELECT \"users\".* FROM \"users\" WHERE ((\"users\".\"kind\" = 'admin' OR \"users\".\"kind\" = 'author'))" 

ActiveRecord查询ActiveRecord::Relation对象(这疯狂地不支持or ),而不是Arel对象(这样做)。 幸运的是,他们的where方法接受ARel查询对象。 所以,如果User < ActiveRecord::Base

 users = User.arel_table query = User.where(users[:kind].eq('admin').or(users[:kind].eq('author'))) 

query.to_sql现在显示了令人放心的:

 SELECT "users".* FROM "users" WHERE (("users"."kind" = 'admin' OR "users"."kind" = 'author')) 

为了清楚起见,您可以提取一些临时的部分查询variables:

 users = User.arel_table admin = users[:kind].eq('admin') author = users[:kind].eq('author') query = User.where(admin.or(author)) 

自然,一旦你有查询,你可以使用query.all来执行实际的数据库调用。

从实际的页面 :

OR运算符如下所示:

 users.where(users[:name].eq('bob').or(users[:age].lt(25))) 

至于Rails 5,我们有ActiveRecord::Relation#or ,可以这样做:

 User.where(kind: :author).or(User.where(kind: :admin)) 

…它被翻译成你所期望的SQL:

 >> puts User.where(kind: :author).or(User.where(kind: :admin)).to_sql SELECT "users".* FROM "users" WHERE ("users"."kind" = 'author' OR "users"."kind" = 'admin') 

我遇到了同样的问题寻找替代mongoid的#any_of的activerecord。

@jswanner的答案是好的,但只有在where参数是Hash的情况下才有效:

 > User.where( email: 'foo', first_name: 'bar' ).where_values.reduce( :and ).method( :or ) => #<Method: Arel::Nodes::And(Arel::Nodes::Node)#or> > User.where( "email = 'foo' and first_name = 'bar'" ).where_values.reduce( :and ).method( :or ) NameError: undefined method `or' for class `String' 

为了能够使用string和散列,你可以使用这个:

 q1 = User.where( "email = 'foo'" ) q2 = User.where( email: 'bar' ) User.where( q1.arel.constraints.reduce( :and ).or( q2.arel.constraints.reduce( :and ) ) ) 

的确,这很丑陋,你不想每天都这样使用。 下面是我做的一些#any_of实现: https : #any_of

它让那样做:

 > q1 = User.where( email: 'foo1' ); true => true > q2 = User.where( "email = 'bar1'" ); true => true > User.any_of( q1, q2, { email: 'foo2' }, "email = 'bar2'" ) User Load (1.2ms) SELECT "users".* FROM "users" WHERE (((("users"."email" = 'foo1' OR (email = 'bar1')) OR "users"."email" = 'foo2') OR (email = 'bar2'))) 

编辑:从那以后,我发布了一个gem来帮助构buildOR查询 。

只要为您的OR条件制定一个范围:

 scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin']) 

使用SmartTuple它会看起来像这样:

 tup = SmartTuple.new(" OR ") tup << {:kind => "admin"} tup << {:kind => "author"} User.where(tup.compile) 

要么

 User.where((SmartTuple.new(" OR ") + {:kind => "admin"} + {:kind => "author"}).compile) 

您可能认为我有偏见,但我仍然认为传统的数据结构操作在这种情况下比方法链更加清晰和方便。

为了扩大jswanner的答案 (这实际上是很棒的解决scheme,并帮助我)谷歌search的人:

你可以像这样应用范围

 scope :with_owner_ids_or_global, lambda{ |owner_class, *ids| with_ids = where(owner_id: ids.flatten).where_values.reduce(:and) with_glob = where(owner_id: nil).where_values.reduce(:and) where(owner_type: owner_class.model_name).where(with_ids.or( with_glob )) } User.with_owner_ids_or_global(Developer, 1, 2) # => ...WHERE `users`.`owner_type` = 'Developer' AND ((`users`.`owner_id` IN (1, 2) OR `users`.`owner_id` IS NULL)) 

这个方法怎么样: http : //guides.rubyonrails.org/active_record_querying.html#hash-conditions (和检查2.3.3)

 admins_or_authors = User.where(:kind => [:admin, :author]) 

不幸的是,它不是本地支持的,所以我们需要在这里进行攻击。

而黑客看起来像这样,这是非常低效的SQL(希望DBA不看它:-)):

 admins = User.where(:kind => :admin) authors = User.where(:kind => :author) both = User.where("users.id in (#{admins.select(:id)}) OR users.id in (#{authors.select(:id)})") both.to_sql # => where users.id in (select id from...) OR users.id in (select id from) 

这生成子小程序。

而从SQLangular度来看,更好的黑客看起来像这样:

 admins_sql = admins.arel.where_sql.sub(/^WHERE/i,'') authors_sql = authors.arel.where_sql.sub(/^WHERE/i,'') both = User.where("(#{admins_sql}) OR (#{authors_sql})") both.to_sql # => where <admins where conditions> OR <authors where conditions> 

这会产生适当的OR条件,但显然它只考虑了范围的WHERE部分。

我select了第一个,直到我看到它的performance。

无论如何,你必须非常小心,并观察生成的SQL。