【问题标题】:Group Users by Age Range in ruby在 ruby​​ 中按年龄范围对用户进行分组
【发布时间】:2012-08-23 18:24:22
【问题描述】:

我正在尝试按年龄范围列出用户数量:

Range  : #Users
10-14  : 16
15-21  : 120
22-29  : 312
30-40  : 12131
41-70  : 612
71-120 : 20

我正在考虑创建一个静态哈希数组:

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}
]

然后将它用于我的搜索过滤器以及我的查询。但是,我想不出一种方法来获得最大的性能。

我的模型中的方法仅按年龄分组:

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

有什么建议吗?

【问题讨论】:

标签: ruby-on-rails ruby activerecord group-by range


【解决方案1】:

您想要构建一些如下所示的 SQL:

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

在 ActiveRecord 术语中,这将是:

# 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

这将在age_ranges 中留下一组对象,这些对象将具有nage_range 方法。如果你想要一个哈希,那么:

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

这当然不包括没有任何人的范围;我将把它作为练习留给读者。

【讨论】:

  • 这解决了我的问题,谢谢。我现在似乎在控制器中有一些rails错误...如果我添加logger.debug("items: #{@ageRange_items.inspect}")一切正常...如果没有,它只是将cases.join(' ')设置为age_range,给出一个自然错误说@987654331 @
  • @itsalltime:如果没有看到最终代码,这很难诊断。
  • 这里...它比我发布的问题要复杂一些,因为它有链连接,但它不应该有这种行为query = User.joins(shops: {receipts: {tag: :user}}) .select("case #{cases.join(' ')} end as age_range, count(*) as number_of_users, sum(total) as total") .where("users.id= :user_id ", user_id: user) .where("users_tags.age" => min .. max) .group("age_range")
  • @ageRange_items 进入你的视野时,它可能仍然是一个关系而不是一个数组。我在任何地方都看不到.all.to_a,所以你可能不会得到你认为的any?。无论如何if @ageRange_items.present? 可能会更惯用。
  • ActiveRecord 不会接触数据库,直到您通过调用.all.to_a.each、...当您说x = M.where(...) 时,您会得到一个关系对象返回,以便您可以链接方法来构建您的查询:M.where(...).where(...).order(...).where(...)。当您x.all 时,您与数据库对话并获得一些结果。我猜x.inspect 会得到结果并缓存它们。你会注意到 any? 是由 ActiveRecord::Relation 直接实现的,所以你需要 all 或者你得到 AR 的 any?
【解决方案2】:

我发现接受的答案有点密集。快速但难以理解和编写。今天,我想出了一个更慢但更简单的解决方案。由于我们将年龄分组到范围中,我们可以假设我们不会有 values over 125

这意味着,如果您对分组和计数的结果集使用 ruby​​ 过滤器,您将不会迭代超过 125 个项目。这将比基于 sql 范围的组/计数慢,但它对于我的目的来说已经足够快了,同时仍然依赖数据库来完成大部分繁重的工作。迭代少于 125 个项目的哈希似乎没什么大不了的。特别是当键值对只是这样的整数时:

{
  0 => 0,
  1 => 1,
  3 => 5,
  25 => 3,
  99 => 3
}

这是伪代码:

users = User
  .where(age: (min..max))
  .group(:age)
  .count(:age)
group = Hash.new(0)
users.each{|age, count|
      case
      when age <= 10
        group['under 10'] += count
      when age <= 25
        group['11-25'] += count
      when age <= 40
        group['26-40'] += count
      else
        group['41+'] += count
      end
}

注意:此解决方案提供给定范围内的用户数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-11-24
    • 1970-01-01
    • 1970-01-01
    • 2012-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多