我有一桌有很多预订的餐厅。现在我想查询所有餐厅,如果他们有 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/12270 和 JOIN ON ... AND 条件当在 ActiveRecord 中急切加载关联时,但没有提供解决方案。
我认为迪帕克是对的。 试试这个:
#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)
尝试
Restaurant.joins(:reservations).includes(:reservations).where("reservations.number_of_people = ?", 2)
我一直在努力寻找这一点,从 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?
价值,你就应该没问题。