在controller中,调用Review.all时,我还需要获取关联的统计信息:ranks。
它看起来像这样:
class Review
has_many :ranks, as: :rankable, dependent: :destroy
def rank
ranks.average(:score).to_f || 0
end
def ranks_count
ranks.count
end
end
class Rank < ApplicationRecord
belongs_to :rankable, polymorphic: true
end
class ReviewsController < ApplicationController
def index
@reviews = Review.all.page(params[:page])
end
end
# index.htlm.slim:
- @reviews.each do |review|
li= link_to review
span
=> review.title
=> review.rank
=> review.ranks_count
结果,对于@reviews 集合中的每个对象,我得到两个 SQL 子查询:SELECT AVG 和 SELECT COUNT。
我该如何解决这个问题?
@ranks = Rank.select(:score, :rankable_id).where(rankable: @reviews, rankable_id: @reviews.ids).group_by(&:rankable_id)
然后在视图中进一步使用它。
解决这个问题的最好方法是什么,是否可以通过包含很好地解决它?
避免额外查询的一种方法是不使用 average 和 count 实例方法——因为这将需要再次加载所有内容,但让您的查询立即返回平均值和计数;
Review
.joins(:ranks)
.select('avg(score)::float, count(ranks.id)')
.group(:id)
像这样的东西;
-- select * from reviews;
id | title | created_at | updated_at
----+-------+----------------------------+----------------------------
1 | 1st | 2023-03-28 06:48:12.375043 | 2023-03-28 06:48:12.375043
2 | 2nd | 2023-03-28 06:48:14.333537 | 2023-03-28 06:48:14.333537
(2 rows)
-- select * from ranks;
id | review_id | score | created_at | updated_at
----+-----------+-------+----------------------------+----------------------------
1 | 1 | 1 | 2023-03-28 06:48:26.513838 | 2023-03-28 06:48:26.513838
2 | 1 | 2 | 2023-03-28 06:48:28.565594 | 2023-03-28 06:48:28.565594
3 | 1 | 2 | 2023-03-28 06:50:25.135863 | 2023-03-28 06:50:25.135863
4 | 2 | 5 | 2023-03-28 06:52:46.001207 | 2023-03-28 06:52:46.001207
(4 rows)
你会得到这个;
Review.joins(:ranks).select('avg(score)::float, count(ranks.id)').group(:id).as_json
SELECT avg(score)::float, count(ranks.id) FROM "reviews" INNER JOIN "ranks" ON "ranks"."review_id" = "reviews"."id" GROUP BY "reviews"."id"
# => [{"avg"=>5.0, "count"=>1, "id"=>nil}, {"avg"=>1.6666666666666667, "count"=>3, "id"=>nil}]
(
as_json
不是必须的,只是一种表现方式)
您可以进行左连接并从连接的表中选择聚合:
class Review < ApplicationRecord
has_many :ranks, as: :rankable, dependent: :destroy
def self.with_stats
ranks = Rank.arel_table
left_joins(:ranks)
.group(:id)
.select(
arel_table[Arel.star], # everything from the reviews table
ranks[:id].count.as("rank_count")
ranks[:score].avg.as("rank")
)
end
end
如果您使用的是 Postgres,则可以使用
ranks[Arel.star].count
,它比 COUNT("ranks"."id")
稍快。
如果您经常使用 Rank 计数,您也可以使用 counter cache 作为读取优化。