通过关联表中的计算进行Rails排序

时间:2018-07-10 00:48:00

标签: ruby-on-rails

我正在使用以下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!

有人知道处理此类情况的最佳做法吗?

这些是我可以想到的三个路径:

  1. 调整上述解决方案以提高工作效率
  2. 使用虚拟列(attr_accessor)并将增量存储在那里。我从未做过此事,不确定是否可行
  3. 构建一个后端脚本,该脚本每天可以在数据库中保存增加的价值。

2 个答案:

答案 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技巧来达到您想要的结果。希望能帮助到你! =)