ActiveRecord返回或条件



如何使用逻辑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'

但是你怎么能产生下面的查询有两个不同的Arel关系已经可用?

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

看起来(根据Arel自述):

不支持OR操作符

但我希望它不适用于这里,并期望写这样的:

(admins.or authors).to_sql

ActiveRecord查询是ActiveRecord::Relation对象(令人恼火的是不支持or),而不是Arel对象(这样做)。

[UPDATE:从Rails 5开始,ActiveRecord::Relation支持"or";参见https://stackoverflow.com/a/33248299/190135]

但幸运的是,他们的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'))

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

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

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

我来晚了一点,但这是我能想到的最好的建议:

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'))"

从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')

从实际的arel页面:

OR操作符是这样工作的:

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

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

@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'

要同时使用字符串和散列,可以这样:

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://gist.github.com/oelmekki/5396826

它允许这样做:

> 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来帮助构建OR查询。

为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的答案(这实际上是一个很棒的解决方案,帮助了我):

你可以像这样应用作用域

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])

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

和hack看起来像这样,这是相当低效的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)

生成子选择集。

一个更好的hack(从SQL的角度)看起来像这样:

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部分。

我选择了第一个,直到我看到它的性能如何。

在任何情况下,您都必须非常小心,并观察生成的SQL

相关内容

  • 没有找到相关文章

最新更新