41

这可能是一个简单的问题,但我似乎正在拔头发来在这里找到一个优雅的解决方案。我有两个 ActiveRecord 模型类,它们之间有一个 has_one 和 belongs_to 关联:

class Item < ActiveRecord::Base
  has_one :purchase
end

class Purchase < ActiveRecord::Base
  belongs_to :item
end

我正在寻找一种优雅的方式来查找所有没有与之关联的购买对象的 Item 对象,最好不要is_purchased在 Item 上使用布尔值或类似属性。

现在我有:

purchases = Purchase.all
Item.where('id not in (?)', purchases.map(&:item_id))

哪个有效,但对我来说似乎效率低下,因为它正在执行两个查询(并且购买可能是一个巨大的记录集)。

运行 Rails 3.1.0

4

4 回答 4

38

这是很常见的任务,SQL OUTER JOIN 通常可以正常工作。看看这里,例如。

在你的情况下尝试使用类似的东西

not_purchased_items = Item.joins("LEFT OUTER JOIN purchases ON purchases.item_id = items.id").where("purchases.id IS null")
于 2012-09-20T15:12:19.577 回答
33

找到了另外两种 railsey 方法:

Item.includes(:purchase).references(:purchase).where("purchases.id IS NULL")

Item.includes(:purchase).where(purchases: { id: nil })

从技术上讲,第一个示例在没有 'references' 子句的情况下可以工作,但 Rails 4 在没有它的情况下会发出弃用警告。

于 2013-11-06T19:15:52.080 回答
16

@dimuch 解决方案的更简洁版本是使用left_outer_joinsRails 5 中引入的方法:

Item.left_outer_joins(:purchase).where(purchases: {id: nil})

请注意,在left_outer_joins调用:purchase中是单数(它是has_one声明创建的方法的名称),而在where子句:purchases中是复数(这里是id字段所属的表的名称。)

于 2018-01-30T08:56:39.033 回答
1

Rails 6.1 has added a query method called missing in the ActiveRecord::QueryMethods::WhereChain class.

It returns a new relation with a left outer join and where clause between the parent and child models to identify missing relations.

Example:

Item.where.missing(:purchase)
于 2021-09-30T09:14:37.960 回答