ActiveRecord 中的自定义联接的预加载

问题描述 投票:0回答:3

我有一桌有很多预订的餐厅。现在我想查询所有餐厅,如果他们有 2 人的预订,则应该预加载这些关联。如果没有 2 人的预订,它仍应返回餐厅,但关联为空。

为此,我尝试了以下查询:

Restaurant.eager_load(:reservations).where("number_of_people = ?", 2)

但是,这不起作用,因为它会丢弃所有已预订但不适合 2 人的餐厅。

所以我想做的就是将该条件移至连接条件中。比如:

Restaurant.joins('LEFT OUTER JOIN \"reservations\" ON \"reservations\".\"restaurant_id\" = \"restaurants\".\"id\" AND \"reservations\".\"number_of_people\" = ?', 2)

这将给出我需要的结果,但这不是预加载“预订”关联,而是导致 N+1 问题。

eager_load 似乎不接受自定义查询。我找到了这些线程: https://github.com/rails/rails/issues/12270JOIN ON ... AND 条件当在 ActiveRecord 中急切加载关联时,但没有提供解决方案。

ruby-on-rails activerecord eager-loading
3个回答
1
投票

我认为迪帕克是对的。 试试这个:

 #retaurant.rb
 has_many :reservations_for_two, ->{number_of_people: 2}, class_name: 'Reservation'

然后:

  Restaurant.preload(:reservations_for_two) #(for two separate queries)

   Restaurant.eager_load(:reservations_for_two) #(one join query)

1
投票

尝试

Restaurant.joins(:reservations).includes(:reservations).where("reservations.number_of_people = ?", 2)


0
投票

我一直在努力寻找这一点,从 Rails 7.0 开始,似乎不可能急切地加载关联并将其与动态条件结合起来。它适用于静态条件,或者您可以加入它并使用

where
过滤它(正如其他答案所建议的那样),但在某些用例中,这两种方法都不起作用,我认为没有解决方法。您只能完全跳过使用 ActiveRecord 预加载功能,并发出单独的原始 SQL 请求并直接获取数据,例如:

sql = Restaurant.joins('LEFT OUTER JOIN "reservations" ON "reservations"."restaurant_id" = "restaurants"."id" AND "reservations"."number_of_people" = ?', 2).select('restaurants.*, reservations.id as r_id').to_sql
rows = ActiveRecord::Base.connection.execute(sql)
grouped = rows.group_by {|row| row['id'] }
records = grouped.map do |id, row|
  row_hash = row[0].except('r_id')
  reservations = row.map {|tt| tt['r_id'] }.map {|id| Reservation.new(id:) }

  Restaurant.new(row_hash.merge(reservations: ))
end

这只是一个最小的示例,其中我只设置了

id
预订,因为我不知道您还有哪些其他列,但这种方法也允许您将其他字段添加到
Reservation
实例中。这种方法也有其局限性,例如,这样创建的 AR 实例将具有
persisted?
等于
false
,除非您显式地重新加载它们(这将进行另一个 SQL 查询)。但只要你不关心
persisted?
价值,你就应该没问题。

© www.soinside.com 2019 - 2024. All rights reserved.