我正在使用以下Rails模型。
Artist.rb
has_many: updates
Update.rb
belongs_to: artist
“更新”具有“受欢迎程度”列(整数0-100)
我需要根据过去30天内的人气差异订购艺术家。 (最后一行-更新范围的第一行)
我在控制器中进行了这项工作,方法是遍历歌手列表,计算人气差异,并将该值与歌手ID一起保存在新数组中。然后按增加值对该数组排序,并以正确的顺序重新创建艺术家列表。问题是这会导致我的应用程序发生超时错误,因为在单击“搜索”时发生迭代。
计算差异的方法:
class Update < ApplicationRecord
belongs_to :artist
def self.pop_diff(a)
in_range = joins(:artist).where(artists: {name: a}).where(created_at: 30.days.ago.to_date..Time.now().to_date)
diff = in_range.last.popularity - in_range.first.popularity
return diff
end
end
以正确的顺序在控制器中创建新数组:
@artists = Artist.all
@ordering = Array.new
@artists.each do |a|
@ordering << {"artist" => a, "diff" => Update.pop_diff(a) }
end
@ordering = @ordering.sort_by { |k| k["diff"]}.reverse!
有人知道处理此类情况的最佳做法吗?
这些是我可以想到的三个路径:
答案 0 :(得分:1)
在SQL中执行此操作最有效
form.save()
当然,这不是“铁轨方式”
我要采取的另一种选择是向class Artist < ApplicationRecord
def self.get_popularity_extreme(direction = 'ASC', days_ago = 30)
<<-SQL
SELECT popularity
FROM updates
WHERE updates.created_at BETWEEN (DATEADD(DAY, -#{days_ago.to_i.abs}, NOW()), NOW())
ORDER BY updates.created_at #{direction.presence || 'ASC'}
LIMIT 1
SQL
end
def self.by_popularity_difference
joins(
<<-SQL
LEFT JOIN (
#{get_popularity_extreme}
) earliest_update ON updates.artist_id = artists.id
LEFT JOIN (
#{get_popularity_extreme('DESC')}
) latest_update ON updates.artist_id = artists.id
SQL
).
where('earliest_update.popularity IS NOT NULL').
where('latest_update.popularity IS NOT NULL').
select('artists.*', 'latest_update.popularity - earliest_update.popularity AS popularity_difference').
order('popularity_difference DESC')
end
end
Update
添加触发器,以在父艺术家表中设置一列
after_save
缺点是,如果不是每个艺术家每天都有更新,数字将不准确
如果您要继续使用当前的解决方案,则应指定顺序,不需要明确的回报,您不应该查找已经拥有的艺术家,也不需要加入(而且这也是错误的,因为您传递了整个艺术家,但仍将其过滤为“名称”):
class Update < ApplicationRecord
belongs_to :artist
after_save :set_artist_difference
def self.pop_diff(a)
in_range = where(artist_id: a.id).where(created_at: 30.days.ago.to_date..Time.now().to_date).limit(1)
in_range.order(created_at: :desc).first.popularity - in_range.order(:created_at).first.popularity
end
def set_artist_difference
artist.update(difference: self.class.pop_diff(a))
end
end
也不要按相反的方向排序然后反转,而是按负diff进行排序:
class Update < ApplicationRecord
belongs_to :artist
def self.pop_diff(a)
in_range = where(artist_id: a.id).where(created_at: 30.days.ago.to_date..Time.now().to_date).limit(1)
in_range.order(created_at: :desc).first.popularity - in_range.order(:created_at).first.popularity
end
end
答案 1 :(得分:0)
嗯,您采用的这种方法存在性能下降的问题,部分原因是您在数据库中执行了许多查询。这是一种简单的方法(或非常接近):
artists = Artist.all
pops =
artists.
includes(:updates).
where('updates.created_at' => 30.days.ago..Time.zone.now).
pluck(:id, 'updates.popularity').
group_by {|g| g.first}.
flat_map do |id, list|
diffs = list.map(&:second).compact
{
artist: artists.find { |artist| artist.id == id},
pops: diffs.last - diffs.first
}
end
# => [{:artist=>#<Artist id: 1, name: "1", created_at: "2018-07-10 05:44:29", updated_at: "2018-07-10 05:44:29">, :pops=>[10, 11, 1]}, {:artist=>#<Artist id: 2, name: "2", created_at: "2018-07-10 05:44:32", updated_at: "2018-07-10 05:44:32">, :pops=>[]}, {:artist=>#<Artist id: 3, name: "3", created_at: "2018-07-10 05:44:34", updated_at: "2018-07-10 05:44:34">, :pops=>[]}]
更多表现!但是请注意,这仍然不是执行此工作的最高效方式。尽管如此,它还是非常快的(尽管有点代数-您可以有所改进),并且使用了很多红宝石和Rails技巧来达到您想要的结果。希望能帮助到你! =)