3

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).

user2985898
  • 1,173
  • 1
  • 8
  • 21

1 Answers1

0

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
Myxoh
  • 93
  • 9
  • I've done it with both = and == with the same result. I'll try your solution when I get a chance. – user2985898 Jul 26 '17 at 15:26
  • I'm using SQLite for development and MySQL for production. – user2985898 Jul 26 '17 at 15:28
  • I'm 100% sure the COUNT(property = value) doesn't work in MySQL, and I think it doesn't work in SQLite either. If the IF syntax doesn't work for you in SQLite, the one that should work for sure is: SUM(IF(students.awards = 1, 1, 0)) I'm pretty sure that's where your problem lies. (You should be able to check if Gold is actually giving back the proper results) Also, quick suggestion: Use MySQL locally as well, it may be more of a pain to set it up, but the amount of times that different syntax screw me when pushing to production is uncanny. – Myxoh Jul 26 '17 at 16:30