Group Users by Age Range in ruby

activerecord, group-by, range, ruby, ruby-on-rails

Solution

You want to build some SQL that looks like this:

select count(*),
       case
           when age between 10 and 14 then '10 - 14'
           when age between 15 and 21 then '15 - 21'
           -- ...
       end as age_range
from users
where age between 10 and 120
group by age_range

In ActiveRecord terms, that would be:

# First build the big ugly CASE, we can also figure out the
# overall max and min ages along the way.
min   = nil
max   = nil
cases = AGE_RANGES.map do |r|
    min = [r[:min], min || r[:min]].min
    max = [r[:max], max || r[:max]].max
    "when age between #{r[:min]} and #{r[:max]} then '#{r[:min]} - #{r[:max]}'"
end

# Then away we go...
age_ranges = Users.select("count(*) as n, case #{cases.join(' ')} end as age_range")
                  .where(:age => min .. max)
                  .group('age_range')
                  .all

That will leave you with an array of objects in `age_ranges` and those objects will have `n` and `age_range` methods. If you want a Hash out of that, then:

age_ranges = Hash[age_ranges.map { |r| [r.age_range, r.n] }]

That won't include ranges that don't have any people in them of course; I'll leave that as an exercise for the reader.

Problem

I'm trying to list the number of users by age-range: ``` Range : #Users 10-14 : 16 15-21 : 120 22-29 : 312 30-40 : 12131 41-70 : 612 71-120 : 20 ``` I was thinking of creating a static array of hashes: ``` AGE_RANGES = [ {label:"10 - 14", min:10, max:14}, {label:"15 - 21", min:15, max:21}, {label:"22 - 29", min:22, max:29}, {label:"30 - 40", min:30, max:40}, {label:"41 - 70", min:41, max:70}, {label:"71 - 120", min:71, max:120} ] ``` and then use it for my search filter, as well as for my query. But, I cannot think of a way of getting the most performance out of it. My method in my model only groups by age: ``` def self.group_by_ageRange(minAge, maxAge) query = User.group("users.age") .where("users.age BETWEEN minAge and maxAge ") .select("users.age, count(*) as number_of_users") end ``` Any suggestions?

Original source

Related problems