首页 > 解决方案 > 分组后如何在 Rails 中排名

问题描述

在我使用 Postgres 的 Rails 6 应用程序中,我有一个名为UserCategories.

| id | user_id | category_id | points| rank |

我想做的是:

  1. 按 category_id 对记录进行分组
  2. 按点对每个 category_id 的记录进行排序 (desc)
  3. 根据 category_id 的记录顺序更新排名字段

示例(由每个 category_id 的点数确定的所需排名):

| id | user_id | category_id | points| rank |
| 1  |    1    |    1        |   2   |     |  #  I want rank to be 1
| 2  |    2    |    1        |   1   |     |  #  I want rank to be 2
| 3  |    1    |    2        |   3   |     |  #  I want rank to be 1
| 4  |    2    |    2        |   3   |     |  #  I want rank to be 1

我的模型方法:

  def self.calculate_user_category_ranks
    @user_categories = UserCategory.select(:id, :points, :user_id, :category_id, :rank).all.order(points: :desc).group_by(&:category_id)
    # returns: 
    #   {2=>[#<UserCategory:0x000000000de8be00 id: 2, user_id: 1, category_id: 2, points: 3, rank: 0>, #<UserLeague:0x000000000de8bce8 id: 4, user_id: 2, category_id: 2, points: 3, rank: 0>],
         1=>[#<UserCategory:0x000000000de8bbf8 id: 1, user_id: 1, category_id: 1, points: 2, rank: 0>, <UserLeague:0x000000000de8bb30 id: 3, user_id: 2, category_id: 1, points: 1, rank: 0>]}

    rank = 0
    points_counter = 0

    @user_categories.each do |id, points|
      uc = UserCategory.find(id)
      
      if points != point_counter
        rank += 1
        point_counter = points
      end

      uc.rank = rank
      uc.save
    end
  end

执行此代码后:

| id | user_id | category_id | points| rank |
| 1  |    1    |    1        |   2   |  2  |  #  I want rank to be 1
| 2  |    2    |    1        |   1   |  0  |  #  I want rank to be 2
| 3  |    1    |    2        |   3   |  1  |  #  I want rank to be 1
| 4  |    2    |    2        |   3   |  0  |  #  I want rank to be 1

有人可以帮我确定我做错了什么吗?

标签: ruby-on-railsrubypostgresql

解决方案


出于效率的原因,您可能选择将rank其作为数据库列,但数据库规范化的原则表明,拥有一个可以从表中的其他列计算出其值的列是“不好的做法”。因此,认识到出于效率原因您可能不接受此解决方案,让我建议,对于您的任何特定实例,UserCategory您都可以确定其在 Ruby 中的排名:

class UserCategory < ApplicationRecord
  scope :in_the_same_category, ->(category_id) { where("category_id = ?", category_id }

  def in_my_category
    UserCategory.in_the_same_category(category_id)
  end

  def rank
    in_my_category.
      sort_by(&:points).
      reverse.
      map(&:points).
      uniq.
      index(points) + 1
  end
end

推荐阅读