问题
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