Rails order by count based on values of column

允我心安 提交于 2019-12-10 10:21:15

问题


Rails 5.1.2

I have a two models Student and Award this way:

class Student < ApplicationRecord
  has_many :awards
end

class Award < Application Record
  belongs_to :students

  # Categories
  scope :attendance, -> { where(category: 0) }
  #...(other award categories)
  scope :talent,     -> { where(category: 8) }
  # Ranks
  # Gold
  scope :rank_1, -> { where(rank: 1) }
  # Silver
  scope :rank_2, -> { where(rank: 2) }
  # Bronze
  scope :rank_3, -> { where(rank: 3) }
end

Award has these columns: rank and category.

Now, I want to get the top student for a given category. The criteria for this is, order by count of "gold" awards (rank 1), then order by count of "silver" (rank 2) awards, and then order by count of "bronze" (rank 3) awards.

So, if I were to get the Student who meets the top criteria for category 0 (which is handled by the attendance scope as described in the model above), this is what I thought the query should look like:

Student.joins(:awards).where(awards: { category: 0 }).group('students.id').order('COUNT(awards.rank == 1) DESC', 'COUNT(awards.rank == 2) DESC', 'COUNT(awards.rank == 3) DESC').take

However, this returns the Student with the highest count of awards, regardless of rank. So for example, if I remove take, the order looks like this:

# |St.ID | Gold  | Slvr. | Brnz. |
----------------------------------
1 |  12  |   4   |   12  |   8   |
----------------------------------
2 |   1  |   9   |   0   |   4   |
----------------------------------
3 |   6  |   9   |   1   |   0   |
----------------------------------
4 |  18  |   5   |   2   |   2   |
----------------------------------
 ...

So, the order I'm getting are IDs 12, 1, 6, 18, ..., when it should be IDs 6, 1, 18, 12, ....

I realize the order('COUNT(awards.rank == 1) DESC', 'COUNT(awards.rank == 2) DESC', 'COUNT(awards.rank == 3) DESC') part is simply ordering by the count of awards total (rather than count of a awards with a particular value in column rank).

I can easily solve this by adding a counter cache for each category of awards, but that isn't an elegant nor flexible solution.

As a bonus, after this query returns a successful result, I will search the database again to find all students who have the same score (as there could be ties). I'm not aware of a way to do all this in one query (perhaps by means of subqueries after getting the values for each rank).


回答1:


I think your problem might be the double equal??

EDIT: This is a more proper way (Assuming MySQL):

 Student.joins(:awards).where(awards: { category: 0 }).group('students.id').order('COUNT(if(awards.rank = 1)) DESC', 'COUNT(if(awards.rank = 2)) DESC', 'COUNT(if(awards.rank = 3)) DESC').take

First try your code without it, if that doesn't work, you might want to try this:

SELECT students.id, gold, silver, bronze) FROM students 
JOIN ON 
  (SELECT students.id as id COUNT(awards) as bronze
  FROM students JOIN awards ON students.id = awards.student_id
  WHERE awards.rank = 1
  GROUP BY students.id) q1 q1.id = students.id
JOIN ON 
  (SELECT students.id as id COUNT(awards) as silver
  FROM students JOIN awards ON students.id = awards.student_id
  WHERE awards.rank = 2
  GROUP BY students.id) q2 q2.id = students.id
JOIN ON 
  (SELECT students.id as id COUNT(awards) as gold
  FROM students JOIN awards ON students.id = awards.student_id
  WHERE awards.rank = 3
  GROUP BY students.id) q3 q3.id = students.id
ORDER BY gold, silver, bronze


来源:https://stackoverflow.com/questions/45318204/rails-order-by-count-based-on-values-of-column

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!