1

如何includes在保持即时加载的同时向活动记录中生成的 ON 子句添加条件?

假设我有这些课程:

class Car
   has_many :inspections
end

class Inspection
   belongs_to :car
end

现在我可以这样做:

Car.includes(:inspections)

Select * from cars LEFT OUTER JOIN inspections ON cars.id = inspections.car_id

但我想生成这个sql:

Select * from cars LEFT OUTER JOIN inspections ON cars.id = inspections.car_id 
    AND inspections.month = '2013-04-01'

(这不起作用):

Car.includes(:inspections).where("inspections.month = 2013-04-01")

Select * from cars LEFT OUTER JOIN inspections ON cars.id = inspections.car_id
    WHERE inspections.month = '2013-04-01'
4

4 回答 4

1

我不确切地知道这一点,但您可能不推荐您尝试做的事情,违反了 Rails 的约定之一。根据相关问题中的这个答案,此类查询的默认行为是使用两个查询,例如:

SELECT "cars".* FROM "cars";
SELECT "inspections".* FROM "inspections" WHERE "inspections"."car_id" IN (1, 2, 3, 4, 5);

出于性能原因做出此决定。这让我猜想查询的确切类型(JOIN 或多个查询)是您无法指望的实现细节。按照这种思路,ActiveRecord::Relation 可能不是为您的用例设计的,可能无法ON在查询中添加条件。

沿着这一系列的猜测,如果你真的相信你的用例是独一无二的,最好的办法可能是你制作自己的 SQL 查询,如下所示:

Car.joins(sanitize_sql_array(["LEFT OUTER JOIN inspections ON inspections.car_id = cars.id AND inspections.month = ?", "2013-04-01"])

(更新:这是去年提出的,但没有得到很好的答案。)

备选方案 1

正如卡洛斯·德鲁建议的那样,

@cars   = Cars.all
car_ids = @cars.map(&:id) 
@inspections = Inspection.where(inspections: {month: '2013-04-01', car_id: car_ids})
# with scopes: Inspection.for_month('2013-04-01').where(car_id: car_ids)

但是,为了防止car.inspections触发不必要的 SQL 调用,还需要做

# app/models/car.rb
has_many :inspections, inverse_of: :car

# app/models/inspection.rb
belongs_to :car, inverse_of: :inspections

备选方案 2

也许您可以找到一种方法来缓存当月的检查,然后不用担心急切加载。这可能是最好的解决方案,因为缓存可以在不同的地方重复使用。

@cars = Cars.all
@cars.each do |car|
  car.inspections.where(month: '2013-04-01')
end
于 2013-07-19T03:52:57.913 回答
0

这应该有效:

Car.includes(:inspections).where( inspections: { month: '2013-04-01' })
于 2013-03-28T20:41:15.550 回答
0

我已经更广泛地重新考虑了你的问题。我认为您正面临代码设计问题以及(而不是?)ActiveRecord 查询问题。

您要求返回.inspections已重新定义为表示与特定日期匹配的检查的汽车关系。ActiveRecord 不允许您根据查询动态重新定义模型关联。

如果您不要求检查日期的动态条件,我会告诉您使用 ahas_many :through和 a :condition

has_many :passed_inspections, :through => :inspections, :conditions => {:passed => true}

@cars = Cars.includes(:passed_inspections)

显然,如果您需要即时提供检查日期,那将是行不通的。

所以,最后,我会告诉你做这样的事情:

@cars = Cars.all
@inspections = Inspection.where(inspections: {month: '2013-04-01', car_id: @cars.pluck(:id)})

(确切的,最好的实现car_id条件是有待商榷的。然后你需要对@inspectionsby进行分组car_id以在给定的时刻获得正确的子集。)

或者,在生产环境中,您可能能够依赖一些相当好的/聪明的 ActiveRecord 缓存。我不确定这一点。

def inspections_dated(month)
  inspections.where(month: month)
end
Car.includes(:inspections).each{|car| car.inspections_dated(month).each.etc. }

交替地,交替地

您可以通过手动 SQL 欺骗 ActiveRecord 为您提供具有不清楚接口的扩展 Car 对象:

@cars_with_insp = Car.join("LEFT OUTER JOIN inspections ON inspections.car_id = cars.id AND inspections.month = '2013-04-01'").select("cars.*, inspections.*")
@cars_with_insp.each{|c| puts c.name; puts c.inspection_month}

您将看到,.each您可以直接在 上使用检查的属性car,因为您已经说服 ActiveRecord 通过连接将一个类的两条记录作为单行返回。Rails 会告诉你它的类是 Car,但它不仅仅是 Car。对于没有匹配的检查,您将获得每辆汽车一次,或者每次匹配的检查获得多次。

于 2013-07-15T20:51:13.180 回答
0

Rails 的作者没有将这个功能构建到 ActiveRecord 中,大概是因为使用 WHERE 返回相同的结果集,他们觉得没有必要有替代方案。

在文档和代码中,我们找到了向包含模型添加条件的两种“官方”方法。

在实际源代码中:https ://github.com/rails/rails/blob/5245648812733d2c31f251de3e05e78e68bfa3a5/activerecord/lib/active_record/relation/query_methods.rb我们发现他们使用 WHERE 来完成此操作:

我引用:“

=== 条件

#
# If you want to add conditions to your included models you'll have
# to explicitly reference them. For example:
#
# User.includes(:posts).where('posts.name = ?', 'example')
#
# Will throw an error, but this will work:
#
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)

_END_QUOTE_

文档提到了另一种方法:http ://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html在标题“Eager loading of associations”下

引用:

如果您确实希望只加载关联的某些成员,通常更自然地包含一个定义了条件的关联:

class Post < ActiveRecord::Base has_many :approved_comments, -> { where approved: true }, class_name: 'Comment' end

Post.includes(:approved_comments)

这将加载帖子并急切加载approved_comments 关联,该关联仅包含那些已被批准的评论。

结束报价

您可以在技术上使用这种方法,但在您的情况下,如果您使用动态月份值,它可能不是那么有用。

这些是唯一的选项,它们在任何情况下都返回与基于 AND 的查询相同的结果。

于 2013-07-19T03:49:46.383 回答