Rails:如何链接范围查询与OR而不是AND?
我正在使用Rails3,ActiveRecord
只是想知道如何用OR语句而不是AND连接范围。
例如
Person.where(:name => "John").where(:lastname => "Smith")
通常返回name ='John'和lastname ='Smith',但是我想:
name ='John'OR lastname ='Smith'
你会做的
Person.where('name=? OR lastname=?', 'John', 'Smith')
现在,新的AR3语法(没有使用第三方gem)没有任何其他的OR支持。
使用ARel
t = Person.arel_table results = Person.where( t[:name].eq("John"). or(t[:lastname].eq("Smith")) )
更新Rails4
不需要第三方gem
a = Person.where(name: "John") # or any scope b = Person.where(lastname: "Smith") # or any scope Person.where([a, b].map{|s| s.arel.constraints.reduce(:and) }.reduce(:or))\ .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }
老答案
不需要第三方gem
Person.where( Person.where(:name => "John").where(:lastname => "Smith") .where_values.reduce(:or) )
您也可以使用MetaWhere创业板不要混淆你的代码与SQL的东西:
Person.where((:name => "John") | (:lastname => "Smith"))
如果有人正在寻找这个更新的答案,看起来有一个现有的拉取请求到这个Rails: https : //github.com/rails/rails/pull/9052 。
感谢@ j-mcnally的ActiveRecord猴子补丁( https://gist.github.com/j-mcnally/250eaefef234dd8971b ),你可以这样做:
Person.where(name: 'John').or.where(last_name: 'Smith').all
更有价值的是能够连接OR
范围:
scope :first_or_last_name, ->(name) { where(name: name.split(' ').first).or.where(last_name: name.split(' ').last) } scope :parent_last_name, ->(name) { includes(:parents).where(last_name: name) }
然后你可以find所有名字的姓氏或名字的父母
Person.first_or_last_name('John Smith').or.parent_last_name('Smith')
不是使用这个的最好的例子,只是试图使它符合这个问题。
只需发布相同列OR查询的Array语法即可帮助查看。
Person.where(name: ["John", "Steve"])
Rails / ActiveRecord的更新版本可能本机支持这种语法。 它看起来类似于:
Foo.where(foo: 'bar').or.where(bar: 'bar')
正如在这个拉请求https://github.com/rails/rails/pull/9052
就目前而言,只要坚持下面的工作很好:
Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
如果您使用的是Rails 3.0+,这将是MetaWhere的一个很好的select,但是它不适用于Rails 3.1。 你可能想尝试一下squeel 。 这是由同一个作者。 以下是你如何执行一个基于OR的链:
Person.where{(name == "John") | (lastname == "Smith")}
你可以混合搭配和/或其他许多令人敬畏的东西 。
Rails 4 + Scope + Arel
class Creature < ActiveRecord::Base scope :is_good_pet, -> { where( arel_table[:is_cat].eq(true) .or(arel_table[:is_dog].eq(true)) .or(arel_table[:eats_children].eq(false)) ) } end
我尝试使用.or命名作用域,并且没有运气,但是这个工作可以用这些布尔值来find任何东西。 像生成SQL一样
SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))
根据这个拉取请求 ,Rails 5现在支持链接查询的以下语法:
Post.where(id: 1).or(Post.where(id: 2))
还有一个通过这个gem的function到Rails 4.2的backport 。
对我来说(Rails 4.2.5)只能这样工作:
{ where("name = ? or name = ?", a, b) }
Rails 4
scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }
如果你不能手动写出where子句来包含“或”语句(即你想合并两个范围),你可以使用union:
Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}")
(来源: ActiveRecord查询联盟 )
这将返回匹配任一查询的所有logging。 但是,这将返回一个数组,而不是一个arel。 如果你真的想要返回一个arel,你签出这个要点: https ://gist.github.com/j-mcnally/250eaefef234dd8971b。
只要你不介意猴子修补栏杆,这将做这项工作。
也看到这些相关的问题: 在这里 , 这里和这里
对于铁轨4,基于这篇文章和这个原始的答案
Person .unscoped # See the caution note below. Maybe you want default scope here, in which case just remove this line. .where( # Begin a where clause where(:name => "John").where(:lastname => "Smith") # join the scopes to be OR'd .where_values # get an array of arel where clause conditions based on the chain thus far .inject(:or) # inject the OR operator into the arels # ^^ Inject may not work in Rails3. But this should work instead: .joins(" OR ") # ^^ Remember to only use .inject or .joins, not both ) # Resurface the arels inside the overarching query
注意文章最后的警告:
Rails 4.1+
Rails 4.1将default_scope视为一个常规范围。 默认范围(如果有的话)包含在where_values结果中,注入(:或者)将在默认范围和您的wheres之间添加或声明。 那很糟。
为了解决这个问题,你只需要查看查询。
squeel
gem提供了一个非常简单的方法来实现这个(在此之前,我使用了@ coloradoblue的方法):
names = ["Kroger", "Walmart", "Target", "Aldi"] matching_stores = Grocery.where{name.like_any(names)}
因此,对原始问题的答案,你可以用“或”而不是“和”来连接作用域,似乎是“不可以”。 但是您可以手动编写一个完全不同的作用域或查询来完成这个工作,或者使用ActiveRecord中的不同框架,例如MetaWhere或Squeel。 在我的情况下没有用
我'或者'是由pg_search生成的作用域,它比select要多一点,它包括ASC的顺序,这使得一个干净的工会混乱。 我想'或'与手工作用的范围,做我不能做的东西在pg_search。 所以我必须这样做。
Product.find_by_sql("(#{Product.code_starts_with('Tom').to_sql}) union (#{Product.name_starts_with('Tom').to_sql})")
即把范围变成sql,把括号括起来,将它们联合在一起,然后使用sql生成的find_by_sql。 这有点垃圾,但确实有效。
不,不要告诉我我可以在pg_search中使用“against:[:name,:code]”,我想这样做,但是'name'字段是一个hstore,pg_search无法处理然而。 所以名称范围必须手工制作,然后与pg_search范围结合。
names = ["tim", "tom", "bob", "alex"] sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ") @people = People.where(sql_string)
- LEFT OUTERjoinRails 3
- rails覆盖关系的默认getter(belongs_to)
- 在Rails迁移中将一列更新为另一列的值
- 获取CodeIgniter Active Record的当前SQL代码
- Rails创build或更新魔法?
- 如何用ActiveRecord / RailsexpressionNOT IN查询?
- Post.all.map(&:id)是什么意思?
- 我怎么知道什么时候在Rails中“刷新”我的模型对象?
- 如何处理Ruby on Rails错误:“请安装postgresql适配器:`gem install activerecord-postgresql-adapter'”