使用 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_loading
和 includes
并不是为了解决手头的问题。对于这种特殊情况,我认为您比 ActiveRecord 更清楚需要什么。因此,您可以就如何构造查询做出更好的决定。
我正在为看起来像这样的模型制作 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_loading
和 includes
并不是为了解决手头的问题。对于这种特殊情况,我认为您比 ActiveRecord 更清楚需要什么。因此,您可以就如何构造查询做出更好的决定。