【问题标题】:Negate ActiveRecord query scope否定 ActiveRecord 查询范围
【发布时间】:2015-07-20 23:11:47
【问题描述】:

我的模型范围有点复杂

class Contact < ActiveRecord::Base
  scope :active,       -> { where(inactive: false) }
  scope :groups,       -> { where(contact_type: 2308) }
  scope :group_search, -> (query) do
    active.groups.where("last_name LIKE '%' + ? + '%'", query)
  end
end

出于测试目的,我想确保出于正确的原因排除group_search返回的所有Contactsnot

但要获得该列表,我必须加载

Contact.all - Contact.group_search('query')

它运行两个查询,返回 Array 而不是 Relation,而且比我想要的要慢。

而且由于我正在测试group_search 范围,因此编写 另一个 范围作为它的负数会破坏这一点。我宁愿做类似的事情:

Contact.merge.not(Contact.group_search('query'))

生成以下 SQL 查询:

SELECT * 
FROM contacts 
WHERE NOT (contact_type = 2308 AND inactive = 0 AND last_name LIKE '%' + ? + '%')

有什么办法吗?

【问题讨论】:

  • 稍微快一点的方法是:Contact.where.not(id: Contact.group_search('query').pluck(:id))。仍然是两个查询,但会返回一个关系,并会极大地限制其中一个查询。 AFAIK,没有办法否定范围 atm。
  • 是的......查询本身也可能变得非常大......这个表有大约 100k 条记录,并且 ID 是 UUID,所以如果我想要“不(非常common)" 我可能只针对查询字符串查看兆字节。

标签: ruby-on-rails ruby-on-rails-4 activerecord rails-activerecord ruby-on-rails-4.1


【解决方案1】:

要否定范围,您可以使用:

Contact.where.not(id: Contact.group_search('query'))

这和使用pluck不一样(在其中一个cmets中提出):

Contact.where.not(id: Contact.group_search('query').pluck(:id)) 

没有pluck,它会产生一个查询(有两个选择):

SELECT  `contacts`.* FROM `contacts` WHERE `contacts`.`id` NOT IN (SELECT `contacts`.`id` FROM `contacts` WHERE `contacts`.`group_search` = 'query')

使用pluck,它会产生两个独立的查询:

SELECT `contacts`.`id` FROM `contacts` WHERE `contacts`.`group_search` = 'query'
SELECT  `contacts`.* FROM `contacts` WHERE `contacts`.`id` NOT IN (1, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361)

当查询许多记录时,第一个更有效。当然Contact.where.not(group_search: 'query') 更高效,因为它通过一次选择生成一个查询(但在某些情况下这可能是不可能的):

SELECT `contacts`.`id` FROM `contacts` WHERE `contacts`.`group_search` != 'query'

【讨论】:

  • 是的,comments on the question 也提出了这个建议,但它仍然是两个查询,并且可能需要排除大量 ID。
  • 这与您正在链接的使用 pluck 的不完全相同。这只产生一个查询,效率更高。我更新了答案以包含此内容。 ;)
  • 这实际上要好得多——但它适用于 Rails 4.x 吗?
  • 当然!它是在 Rails 4.0 中引入的
  • @AnaMaríaMartínezGómez,不知道以这种方式工作的方法,非常感谢您的分享)
【解决方案2】:

我认为您正在寻找的称为否定范围,您可以使用where_values(或Rails中的where_values_hash> = 5):

conditions = Contact.group_search('query').where_values
@contacts = Contact.where.not(conditions.reduce(:and))

要让这个解决方案在 Rails 4.x 中工作,您应该在范围内提供值作为数组:

scope :groups, -> { where(contact_type: [2308]) }

我还发现了一个简洁的general implementation for negating the scopes,你可能也会觉得它很有趣。

【讨论】:

  • 谢谢!但是我为什么要使用数组呢?
  • 这看起来应该可以完美运行 - 不幸的是,SQL Server 似乎对此很讨厌:TinyTds::Error: An expression of non-boolean type specified in a context where a condition is expected, near ')'.: EXEC sp_executesql N'SELECT [contact].* FROM [contact] WHERE (NOT ([contact].[contact_type] = 2308 AND [contact].[inactive] = 0 AND N''last_name LIKE ''''%query%''''''))'
  • @Patrick:我不知道为什么,但是如果值不是作为数组提供的,Rails 不会绑定参数,这似乎是 Rails 4.2 中出现的一个已知错误。你用的是哪个版本?
  • 原始链接重定向到pivot.com 我认为这是新位置content.pivotal.io/blog/…
  • where_values 已在 Rails 5 中删除。可以使用 where_values_hash 代替
【解决方案3】:

在 Rails 6 中,在枚举值上添加了负作用域。

你可以这样使用它:

Contact.not_active
Contact.not_groups
Contact.not_group_search

相关pull request

【讨论】:

  • 虽然这对于枚举生成的范围是正确的,但它不适用于您在问题中声明自己的范围。
猜你喜欢
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-07
  • 1970-01-01
  • 1970-01-01
  • 2018-09-10
  • 1970-01-01
相关资源
最近更新 更多