如何使用 OR 而不是 AND 链接范围查询?

Posted

技术标签:

【中文标题】如何使用 OR 而不是 AND 链接范围查询?【英文标题】:How to chain scope queries with OR instead of AND? 【发布时间】:2011-04-10 16:53:14 【问题描述】:

我正在使用 Rails3,ActiveRecord

只是想知道如何使用 OR 语句而不是 AND 链接范围。

例如

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

正常返回:

name = 'John' AND lastname = 'Smith'

但我想:

`name = 'John' OR lastname = 'Smith'

【问题讨论】:

【参考方案1】:

我现在在 Rails 6 中工作,现在看来这是可能的。使用来自 OP 的查询:

# in the Person model:
scope :john, ->  where(name: "John") 
scope :smith, ->  where(lastname: "Smith") 
scope :john_or_smith, ->  john.or(self.smith) 

【讨论】:

【参考方案2】:

这是一种非常方便的方式,并且在 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")
  )

【讨论】:

【参考方案3】:

如果您希望提供一个范围(而不是显式处理整个数据集),那么您应该使用 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

【讨论】:

【参考方案4】:

因此,原始问题的答案是,您是否可以使用“或”而不是“和”来加入范围,这似乎是“不,你不能”。但是您可以手动编写一个完全不同的范围或查询来完成这项工作,或者使用与 ActiveRecord 不同的框架,例如MetaWhere 或 Squeel。对我来说没用

我正在对由 pg_search 生成的范围进行“或”运算,它的作用远不止选择,它包括 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 范围联合。

【讨论】:

【参考方案5】:

根据this pull request,Rails 5 现在支持以下用于链接查询的语法:

Post.where(id: 1).or(Post.where(id: 2))

还有通过this gem.将该功能向后移植到Rails 4.2

【讨论】:

【参考方案6】:

另请参阅这些相关问题:here、here 和 here

对于 rails 4,基于 this article 和 this original answer

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) 将在默认范围和您的 哪里。这很糟糕。

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

【讨论】:

【参考方案7】:

对我来说(Rails 4.2.5)它只能这样工作:

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

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

只需发布 same 列 OR 查询的 Array 语法以帮助窥探。

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

【讨论】:

【参考方案10】:

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

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

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

目前,只需坚持以下方法即可:

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

【讨论】:

【参考方案11】:

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 链接命名范围,但没有运气,但这适用于查找带有这些布尔值集的任何内容。生成类似 SQL 的

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

【讨论】:

【参考方案12】:

导轨 4

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

【讨论】:

【参考方案13】:

如果你不能手动写出 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。

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

【讨论】:

【参考方案14】:

squeel gem 提供了一种非常简单的方法来实现这一点(在此之前我使用了类似@coloradoblue 的方法):

names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.wherename.like_any(names)

【讨论】:

【参考方案15】:

如果有人正在寻找对此问题的更新答案,看起来有一个现有的拉取请求将其放入 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')

这不是使用这个的最佳例子,但只是试图将它与问题相匹配。

【讨论】:

【参考方案16】:

你会的

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

目前,新的 AR3 语法没有任何其他 OR 支持(即不使用某些 3rd 方 gem)。

【讨论】:

为了安全起见,值得将其表示为 Person.where("name = ? OR lastname = ?", 'John', 'Smith')【参考方案17】:
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map  |t| "name = '#t'" .join(" OR ")
@people = People.where(sql_string)

【讨论】:

这允许通过#t 构造进行SQL 注入。【参考方案18】:

如果您使用的是 Rails 3.0+,这将是 MetaWhere 的理想选择,但它不适用于 Rails 3.1。您可能想改用squeel。它是同一作者制作的。以下是您如何执行基于 OR 的链:

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

您可以混合和匹配 AND/OR 以及许多其他 awesome things。

【讨论】:

【参考方案19】:

您还可以使用MetaWhere gem 来避免将您的代码与 SQL 内容混淆:

Person.where((:name => "John") | (:lastname => "Smith"))

【讨论】:

【参考方案20】:

使用ARel

t = Person.arel_table

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

【讨论】:

以上是关于如何使用 OR 而不是 AND 链接范围查询?的主要内容,如果未能解决你的问题,请参考以下文章

使用重复参数而不是链接过滤器时,如何使用 Q 对象进行 AND 查找?

如何链接 Django 查询集以保留单个顺序

您如何检查子查询记录是不是在范围外部字段中?

如何仅使用 ForEach 而不是 List 从动态范围中删除

如何优化这个雄辩的查询? (检查日期范围是不是尚未采用)

如何将 Magento 库存设置更改为网站范围而不是全球范围?