2012-08-23 18 views
5

estoy tratando de enumerar el número de usuarios por rango de edad:grupo de usuarios por rango de edad en el rubí

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

Estaba pensando en la creación de una matriz estática de 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} 
] 

y luego usarlo para mi filtro de búsqueda, así como para mi consulta. Pero, no puedo pensar en una manera de obtener el máximo rendimiento de ella.

Mi método en mi modelo únicos grupos por edad:

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 

¿Alguna sugerencia?

+1

http://stackoverflow.com/questions/232387/in-sql-how-can-you-group-by-in-ranges – InternetSeriousBusiness

Respuesta

7

usted quiere construir algunos de SQL que tiene este aspecto:

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 

En términos de ActiveRecord, que sería:

# 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 

que va a dejar con una serie de objetos en age_ranges y aquellos los objetos tendrán los métodos n y age_range. Si quieres un hash de eso, entonces:

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

Eso no incluirá rangos que no tienen ningún pueblo en ellos, por supuesto; Lo dejo como un ejercicio para el lector.

+0

esto resolvió mi problema, gracias. Ahora parece que tengo algunas fallas en los rieles dentro del controlador ... Si agrego 'logger.debug (" items: # {@ ageRange_items.inspect} ")' todo va bien ... Si no, solo establece el 'cases.join ('')' as 'age_range', dando un error natural al decir' column age_range no existe' – MrWater

+0

@itsalltime: Es difícil de diagnosticar sin ver el código final. –

+0

aquí va ... es un poco más complejo que la pregunta que publiqué, ya que tiene uniones de cadena, pero no debería tener este comportamiento 'query = User.joins (tiendas: {recibs: {tag:: user} }) .select ("case # {cases.join ('')} end as age_range, count (*) as number_of_users, sum (total) como total") .where ("users.id =: user_id" , user_id: user) .where ("users_tags.age" => min .. max) .grupo ("AGE_RANGE") ' – MrWater

0

Encuentro que la respuesta aceptada es un poco densa. Rápido pero difícil de entender y escribir. Hoy, se me ocurrió una solución más lenta pero más simple. Dado que estamos agrupando edades en rangos, podemos suponer que no tendremos values over 125

Eso significa que si utiliza un filtro de rubí en un conjunto de resultados agrupado y contado, no repetirá más de 125 elementos. Esto será más lento que un grupo/recuento basado en un rango de sql, pero fue lo suficientemente rápido para mis propósitos al mismo tiempo que dependía de la base de datos para la mayor parte del trabajo pesado. Iterar un hash con menos de 125 elementos no parece ser un gran problema. Especialmente cuando los pares de valores clave son sólo intercepciones así:

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

Aquí está el psudo-código:

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 
} 

Nota: esta solución proporciona el recuento de los usuarios en un rango determinado.

Cuestiones relacionadas