如何用OR而不是AND链接作用域查询?


Answers:


107

你会做

Person.where('name=? OR lastname=?', 'John', 'Smith')

目前,新的AR3语法不提供任何其他OR支持(即不使用某些第三方宝石)。


21
为了安全起见,值得将其表示为Person.where(“ name =?or lastname =?”,'John','Smith')
CambridgeMike

6
这仍然适用于Rails 4吗?Rails 4没什么比这更好的了吗?
多纳托2015年

11
Rails 4不变,但Rails 5 .or内置
2015年

3
这不是范围,仅是2个where子句,或者是非常特殊的范围情况。如果我想链接更复杂的作用域,例如连接,该怎么办?
miguelfg,2015年

2
使用Rails 5,您还可以执行以下操作:Person.where(“ name ='John'”)。or(Person.where(“ lastname ='Smith'”))
shyam


48

使用ARel

t = Person.arel_table

results = Person.where(
  t[:name].eq("John").
  or(t[:lastname].eq("Smith"))
)

1
任何人都可以评论在Postgres中这种查询是否中断吗?
奥利维尔·拉坎

ARel应该与数据库无关。
Simon Perepelitsa 2012年

尽管这似乎是一个好主意,但是ARel并不是公共API,使用它可能会以意想不到的方式破坏您的应用程序,因此我建议您不要使用它,除非您密切关注ARel API的发展。
奥利维尔·拉坎

7
@OlivierLacan Arel是公共API,或更准确地说,它公开了一个。Active Record只是提供了在幕后使用它的便捷方法。单独使用它完全有效。它遵循语义版本控制,因此,除非您要更改主要版本(3.xx => 4.xx),否则无需担心破坏更改。
2014年

41

只需发布同一列或查询的Array语法即可帮助窥视。

Person.where(name: ["John", "Steve"])

2
问题检查约翰反对姓氏,史密斯反对姓氏
steven_noble

11
@steven_noble-这就是为什么我用or查询将“同一列”加粗。我可以完全删除该评论,但是认为这对阅读“或”查询SO问题的人很有帮助。
daino3

38

Rails4的更新

不需要第三方宝石

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

旧答案

不需要第三方宝石

Person.where(
    Person.where(:name => "John").where(:lastname => "Smith")
      .where_values.reduce(:or)
)

5
就资源和方法而言,这确实是OP问题的唯一纯答案。其他答案(a)要求第三方API或(b)要求or在文本块内进行插值或其他运算符,而这两个都不反映在OP的代码中。
allenwlee 2015年

6
这里有一个像样的文章,解释它coderwall.com/p/dgv7ag/...
allenwlee

4
...where_values.join(' OR ')在我的情况
腰带

2
.tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }如果您的范围没有任何需要绑定的变量,则可以省略该部分。
fatuhoku


22

如果有人在寻找对此的更新答案,则似乎已经存在将其放入Rails的拉取请求:https : //github.com/rails/rails/pull/9052

感谢@ j-mcnally的ActiveRecord猴子补丁(https://gist.github.com/j-mcnally/250eaaceef234dd8971b),您可以执行以下操作:

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

然后,您可以找到所有具有名字或姓氏的人,或者其父母是姓氏的人

Person.first_or_last_name('John Smith').or.parent_last_name('Smith')

这不是使用此方法的最佳示例,而只是尝试使其适合问题。


2
您需要哪个版本或Rails?
2015年

3
最新的讨论以及将其放入Rails核心的请求都可以在这里找到:github.com/rails/rails/pull/16052 Rails 5.0的目标是正式发布.or链功能。我能够使用在Rails> = 3.2上提到的猴子补丁;但是,您可能需要等待Rails 5对代码进行任何长期的更改。
codenamev

1
是的,它被合并,并发布了Rails的5
amoebe

10

对我来说(Rails 4.2.5)只能像这样:

{ where("name = ? or name = ?", a, b) }

8

如果您使用的是Rails 3.0+,那么这将是MetaWhere的一个很好的选择,但不适用于Rails 3.1。您可能想尝试squeel。它是由同一位作者制作的。这是您如何执行基于OR的链的方法:

Person.where{(name == "John") | (lastname == "Smith")}

您可以混合和匹配AND / OR,以及其他许多很棒的东西


8

Rails / ActiveRecord的更新版本可能会原生支持此语法。它看起来类似于:

Foo.where(foo: 'bar').or.where(bar: 'bar')

如该拉取请求所述https://github.com/rails/rails/pull/9052

现在,只需坚持以下工作即可:

Foo.where('foo= ? OR bar= ?', 'bar', 'bar')

5
Rails 4.2.5不支持
fatuhoku '16

1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')不起作用。应该是Foo.where(foo:'bar1')。或.where(Foo.where(bar:'bar2'))
AnaMaríaMartínezGómez,

6

Rails 4 +范围+ 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链接命名的作用域,但是没有运气,但是这对于用这些布尔值集查找任何东西都是有用的。像生成SQL

SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))

4

滑轨4

scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }

4

如果您想提供一个范围(而不是显式地处理整个数据集),那么您应该对Rails 5进行以下操作:

scope :john_or_smith, -> { where(name: "John").or(where(lastname: "Smith")) }

要么:

def self.john_or_smith
  where(name: "John").or(where(lastname: "Smith"))
end

这是正确的,但是比stackoverflow.com/a/42894753/1032882从技术上讲的相同,这是一个更真实的答案。
RudyOnRails

3

如果您无法手动写出where子句以包含“ or”语句(即,您想合并两个作用域),则可以使用union:

Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}")

(来源:ActiveRecord Query Union

这将返回与任一查询匹配的所有记录。但是,这将返回一个数组,而不是一个arel。如果您真的想退回竞技场,请查看以下要点:https ://gist.github.com/j-mcnally/250eaaceef234dd8971b 。

只要您不介意猴子修补滑轨,就可以完成此工作。


2

另请参阅以下相关问题:此处此处此处

对于Rails 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结果中,并且inject(:or)将在默认范围和您的位置之间添加或声明。那很糟。

为了解决这个问题,您只需要取消范围查询。


1

这是一种非常方便的方法,并且在Rails 5中可以正常工作:

Transaction
  .where(transaction_type: ["Create", "Correspond"])
  .or(
    Transaction.where(
      transaction_type: "Status",
      field: "Status",
      newvalue: ["resolved", "deleted"]
    )
  )
  .or(
    Transaction.where(transaction_type: "Set", field: "Queue")
  )

0

squeel创业板提供了一个非常容易的方法来做到这一点(在此之前,我用类似@ coloradoblue的方法):

names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}

0

因此,对于原始问题的答案是,您可以将合并范围用“或”而不是“和”似乎是“不可以吗”。但是您可以手工编写一个完全不同的作用域或查询来完成工作,或者使用与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中使用“反对:[:name,:code]”,我想这样做,但是'name'字段是一个hstore,pg_search无法处理然而。因此,按名称命名的范围必须手工制作,然后与pg_search范围合并。


-2
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.