使用 activerecord 和 RoR3 获取没有相关数据的记录?

Get records with no related data using activerecord and RoR3?

我正在为看起来像这样的模型制作 scopes

class PressRelease < ActiveRecord::Base
  has_many :publications
end

我想要得到的是所有 press_releases 没有 publications,但是来自 scope 方法,所以它可以与其他范围链接。有什么想法吗?

谢谢!

注意:我知道有 present?any? 等方法,但这些方法不像范围那样 return 和 ActiveRecord::Relation

注意:我使用的是 RoR 3

您可以在 PressRelease 中删除以下内容:

scope :your_scope, -> { where('id NOT IN(select press_release_id from publications)') }

这将 return 所有没有出版物的 PressRelease 记录。

有几种方法可以做到这一点,第一种需要两个数据库查询:

PressRelease.where.not(id: Publications.uniq.pluck(:press_release_id))

或者如果您不想硬编码关联外键:

PressRelease.where.not(id: PressRelease.uniq.joins(:publications).pluck(:id))

另一种方法是进行左连接并选择那些没有关联元素的元素 - 您会得到一个关系对象,但使用它会很棘手,因为它已经有一个连接:

PressRelease.eager_load(:publications).where(publications: {id: nil})

另一种是使用counter_cache功能。您需要将 publication_count 列添加到 press_releases table.

class Publications < ActiveRecord::Base
  belongs_to :presss_release, counter_cache: true
end

Rails 将使该列与与给定模式关联的许多记录保持同步,因此您只需执行以下操作:

PressRelease.where(publications_count: [nil, 0])

如果不需要,请避免使用 eager_loading(它会增加开销)。此外,不需要子选择语句。

scope :without_publications, -> { joins("LEFT OUTER JOIN publications ON publications.press_release_id = press_releases.id").where(publications: { id: nil }) }

对评论的解释和回应

我对预加载开销的最初想法是 ActiveRecord 将为每个新闻稿实例化所有子记录(出版物)。然后我意识到查询永远不会 return 新闻发布记录与出版物。所以这是一个有争议的问题。

关于 ActiveRecord 的工作方式,有一些要点和观察要点。有些是我以前从经验中学到的,有些是我在探索你的问题时学到的。

来自includes(:publications).where(publications: {id: nil})的查询实际上与我的示例不同。除了 press_releases 中的列之外,它还将 return publications table 中的所有列。发布列是完全没有必要的,因为它们总是空的。但是,这两个查询最终都会产生同一组 PressRelease 对象。

使用 includes 方法,如果您添加任何类型的限制,例如链接 .first.last.limit(),则 ActiveRecord (4.2.4) 将求助于执行两个查询。第一个查询 returns ID,第二个查询使用这些 ID 来获取结果。使用 SQL 片段方法,ActiveRecord 可以只使用一个查询。这是我的一个应用程序的示例:

Profile.includes(:positions).where(positions: { id: nil }).limit(5)
# SQL (0.8ms)  SELECT  DISTINCT "profiles"."id" FROM "profiles" LEFT OUTER JOIN "positions" ON "positions"."profile_id" = "profiles"."id" WHERE "positions"."id" IS NULL LIMIT 5
# SQL (0.8ms)  SELECT "profiles"."id" AS t0_r0, ..., "positions"."end_year" AS t1_r11 FROM "profiles" LEFT OUTER JOIN "positions" ON "positions"."profile_id" = "profiles"."id" # WHERE "positions"."id" IS NULL AND "profiles"."id" IN (107, 24, 7, 78, 89)

Profile.joins("LEFT OUTER JOIN positions ON positions.profile_id = profiles.id").where(positions: { id: nil }).limit(5)
# Profile Load (1.0ms)  SELECT  "profiles".* FROM "profiles" LEFT OUTER JOIN positions ON positions.profile_id = profiles.id WHERE "positions"."id" IS NULL LIMIT 5

最重要的是

eager_loadingincludes 并不是为了解决手头的问题。对于这种特殊情况,我认为您比 ActiveRecord 更清楚需要什么。因此,您可以就如何构造查询做出更好的决定。