否定 ActiveRecord 查询范围
Negate ActiveRecord query scope
我的模型范围有点复杂
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
出于测试目的,我想确保 all Contacts
not 由 group_search
返回出于正当理由被排除在外。
但要获取该列表,我必须加载
Contact.all - Contact.group_search('query')
它运行两个查询,returns 一个 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 '%' + ? + '%')
有什么办法吗?
我认为你要找的是取反范围,你可以使用where_values
(或where_values_hash
in Rails >= 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,你可能也会觉得有趣。
要否定范围,您可以使用:
Contact.where.not(id: Contact.group_search('query'))
这与使用pluck
不一样(在其中一条评论中提出):
Contact.where.not(id: Contact.group_search('query').pluck(:id))
如果没有 pluck
,它会生成一个查询(有两个 select):
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 生成一个查询(但在某些情况下这可能是不可能的):
SELECT `contacts`.`id` FROM `contacts` WHERE `contacts`.`group_search` != 'query'
在Rails6中,在枚举值上添加了负范围。
你可以这样使用它:
Contact.not_active
Contact.not_groups
Contact.not_group_search
我的模型范围有点复杂
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
出于测试目的,我想确保 all Contacts
not 由 group_search
返回出于正当理由被排除在外。
但要获取该列表,我必须加载
Contact.all - Contact.group_search('query')
它运行两个查询,returns 一个 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 '%' + ? + '%')
有什么办法吗?
我认为你要找的是取反范围,你可以使用where_values
(或where_values_hash
in Rails >= 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,你可能也会觉得有趣。
要否定范围,您可以使用:
Contact.where.not(id: Contact.group_search('query'))
这与使用pluck
不一样(在其中一条评论中提出):
Contact.where.not(id: Contact.group_search('query').pluck(:id))
如果没有 pluck
,它会生成一个查询(有两个 select):
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 生成一个查询(但在某些情况下这可能是不可能的):
SELECT `contacts`.`id` FROM `contacts` WHERE `contacts`.`group_search` != 'query'
在Rails6中,在枚举值上添加了负范围。
你可以这样使用它:
Contact.not_active
Contact.not_groups
Contact.not_group_search