[英]Preventing N+1 in Rails query with associated model
I have these models:我有这些模型:
class KlassOccurrence < ApplicationRecord
belongs_to :klass
end
class Klass < ApplicationRecord
has_many :klass_occurrences
belongs_to :user
def next_occurrence_date
self.klass_occurrences.order(scheduled_at: :desc).first
end
end
And I have this in a controller:我在 controller 中有这个:
@klasses = Klass.includes(:klass_occurrences).where(user_id: current_user.id)
And in a view I have this:在一个视图中,我有这个:
<% @klasses.each do |klass| %>
<tr>
<td><%= klass.next_occurrence_date %></td>
</tr>
<% end %>
I would expect that because of the includes(:klass_occurrences)
I wouldn't be seeing an N+1, but this is what I see in the logs:我希望由于
includes(:klass_occurrences)
我不会看到 N+1,但这是我在日志中看到的:
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 3], ["LIMIT", 1]]
↳ app/controllers/klasses_controller.rb:4:in `index'
Rendering klasses/index.html.erb within layouts/dashboard_layout
Klass Load (0.2ms) SELECT "klasses".* FROM "klasses" WHERE "klasses"."user_id" = $1 [["user_id", 3]]
↳ app/views/klasses/index.html.erb:13
KlassOccurrence Load (4.3ms) SELECT "klass_occurrences".* FROM "klass_occurrences" WHERE "klass_occurrences"."klass_id" IN ($1, $2) [["klass_id", 1], ["klass_id", 5]]
↳ app/views/klasses/index.html.erb:13
KlassOccurrence Load (1.8ms) SELECT "klass_occurrences".* FROM "klass_occurrences" WHERE "klass_occurrences"."klass_id" = $1 ORDER BY "klass_occurrences"."scheduled_at" DESC LIMIT $2 [["klass_id", 1], ["LIMIT", 1]]
↳ app/models/klass.rb:6:in `next_occurrence_date'
KlassOccurrence Load (0.3ms) SELECT "klass_occurrences".* FROM "klass_occurrences" WHERE "klass_occurrences"."klass_id" = $1 ORDER BY "klass_occurrences"."scheduled_at" DESC LIMIT $2 [["klass_id", 5], ["LIMIT", 1]]
↳ app/models/klass.rb:6:in `next_occurrence_date'
Why is it doing three queries for klass occurrences?为什么它对 klass 事件进行三个查询? I would expect just one query to load all the occurrences for all the klasses, but then I see two extra queries.
我希望只有一个查询来加载所有类的所有事件,但随后我看到了两个额外的查询。
If you just need the highest value of that column you can also just select an aggregate off the join table:如果您只需要该列的最大值,您也可以只 select 连接表中的一个聚合:
class Klass < ApplicationRecord
has_many :klass_occurrences
belongs_to :user
def self.with_next_occurrence_date
self.select(
'klass.*',
'MAX(klass_occurrences.scheduled_at) AS next_occurrence_date'
)
.left_joins(:klass_occurrences)
.group(:id)
end
end
@klasses = Klass.with_next_occurrence_date.where(user_id: current_user.id)
<% @klasses.each do |klass| %>
<tr>
<td><%= klass.next_occurrence_date %></td>
</tr>
<% end %>
If you really need a model object you can do it by sorting the records in Ruby:如果您真的需要 model object,您可以通过对 Ruby 中的记录进行排序来实现:
class Klass < ApplicationRecord
has_many :klass_occurrences
belongs_to :user
def next_occurrence_date
self.klass_occurrences.sort(&:scheduled_at).last
end
end
And there are also a bunch of novel tricks to get ActiveRecord to load just one record off the association that are somewhat out of scope for this question.还有一些新的技巧可以让 ActiveRecord 从关联中加载一条记录,这些记录在这个问题上有点超出 scope。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.