En SQL, ¿cómo puede "agrupar por" en rangos?

181

Supongamos que tengo una tabla con una columna numérica (vamos a llamarlo "puntaje").

Me gustaría generar una tabla de conteos, que muestre cuántas veces aparecieron los puntajes en cada rango.

Por ejemplo:

rango de puntuación | numero de incidentes
-------------------------------------
   0-9 | 11
  10-19 | 14
  20-29 | 3
   ... | ...

En este ejemplo, hubo 11 filas con puntajes en el rango de 0 a 9, 14 filas con puntajes en el rango de 10 a 19 y 3 filas con puntajes en el rango de 20 a 29.

¿Hay una manera fácil de configurar esto? ¿Que recomiendas?

Hugh
fuente

Respuestas:

143

Ninguna de las respuestas más votadas es correcta en SQL Server 2000. Quizás estaban usando una versión diferente.

Aquí están las versiones correctas de ambos en SQL Server 2000.

select t.range as [score range], count(*) as [number of occurences]
from (
  select case  
    when score between 0 and 9 then ' 0- 9'
    when score between 10 and 19 then '10-19'
    else '20-99' end as range
  from scores) t
group by t.range

o

select t.range as [score range], count(*) as [number of occurrences]
from (
      select user_id,
         case when score >= 0 and score< 10 then '0-9'
         when score >= 10 and score< 20 then '10-19'
         else '20-99' end as range
     from scores) t
group by t.range
Ron Tuffin
fuente
¿Puedo agregar otra columna también (como recuentos de grupo). digamos que quiero agregar la columna de becas para cada rango de puntaje. Lo intenté, pero no lo
hice
Buena respuesta @Ron Tuffin, sin embargo, cuando tienes dos rangos como 10-20, 100-200, entonces el pedido no funciona. usted tendría que ordenar como 10-20, 100-200,20-30 etc. ¿Algún consejo para el pedido?
Zo tiene
2
@ZoHas es un poco un truco, pero esto funciona: orden por len (t.range), t.range
Ron Tuffin
Mejor respuesta en stackoverflow.com/questions/14730380/…
Thunder
1
Si aún tiene problemas de sintaxis, consulte esta respuesta: dba.stackexchange.com/questions/22491/…
Robert Hosking
33

Un enfoque alternativo implicaría almacenar los rangos en una tabla, en lugar de incrustarlos en la consulta. Terminarías con una tabla, llamada Rangos, que se ve así:

LowerLimit   UpperLimit   Range 
0              9          '0-9'
10            19          '10-19'
20            29          '20-29'
30            39          '30-39'

Y una consulta que se ve así:

Select
   Range as [Score Range],
   Count(*) as [Number of Occurences]
from
   Ranges r inner join Scores s on s.Score between r.LowerLimit and r.UpperLimit
group by Range

Esto significa configurar una tabla, pero sería fácil de mantener cuando cambien los rangos deseados. ¡No se necesitan cambios de código!

Walter Mitty
fuente
Hice una pregunta sobre el diseño de la tabla de administradores de bases de datos para datos modelados utilizando rangos de cubos variables que no obtuvo respuesta, pero terminé diseñando un sistema que tiene los rangos que usted mencionó. Amo esta respuesta.
ΩmegaMan
31

Veo respuestas aquí que no funcionarán en la sintaxis de SQL Server. Yo usaría:

select t.range as [score range], count(*) as [number of occurences]
from (
  select case 
    when score between  0 and  9 then ' 0-9 '
    when score between 10 and 19 then '10-19'
    when score between 20 and 29 then '20-29'
    ...
    else '90-99' end as range
  from scores) t
group by t.range

EDITAR: ver comentarios

Ken Paul
fuente
Posiblemente se deba a la versión de SQLServer que estoy usando, pero para que su ejemplo funcione (pruebo las cosas antes de votarlas) tuve que mover el 'puntaje' después del 'caso' a cada 'cuándo'.
Ron Tuffin
3
Tienes razón, y gracias por la corrección. Aparentemente, cuando coloca la variable después de la palabra clave 'case', solo puede hacer coincidencias exactas, no expresiones. Aprendo tanto respondiendo preguntas como preguntándolas. :-)
Ken Paul
23

En postgres (donde ||está el operador de concatenación de cadenas):

select (score/10)*10 || '-' || (score/10)*10+9 as scorerange, count(*)
from scores
group by score/10
order by 1

da:

 scorerange | count 
------------+-------
 0-9        |    11
 10-19      |    14
 20-29      |     3
 30-39      |     2
mhawke
fuente
11

La respuesta de James Curran fue la más concisa en mi opinión, pero el resultado no fue correcto. Para SQL Server, la declaración más simple es la siguiente:

SELECT 
    [score range] = CAST((Score/10)*10 AS VARCHAR) + ' - ' + CAST((Score/10)*10+9 AS VARCHAR), 
    [number of occurrences] = COUNT(*)
FROM #Scores
GROUP BY Score/10
ORDER BY Score/10

Esto supone una tabla temporal #Scores que usé para probarla, acabo de llenar 100 filas con un número aleatorio entre 0 y 99.

Timothy Walters
fuente
1
Ah ... Existe la ventaja de tomarse el tiempo para crear la tabla. (Usé una tabla existente con muy pocas filas en un rango demasiado pequeño)
James Curran
5
create table scores (
   user_id int,
   score int
)

select t.range as [score range], count(*) as [number of occurences]
from (
      select user_id,
         case when score >= 0 and score < 10 then '0-9'
         case when score >= 10 and score < 20 then '10-19'
         ...
         else '90-99' as range
     from scores) t
group by t.range
tvanfosson
fuente
¡Gracias! Intenté esto y la idea básica funciona muy bien, aunque la sintaxis que tuve que usar es ligeramente diferente. Solo se necesita la primera palabra clave "case" y luego, después de la última condición, antes de "as range", necesita la palabra clave "end". Aparte de eso, funcionó muy bien, ¡gracias!
Hugh
5
select cast(score/10 as varchar) + '-' + cast(score/10+9 as varchar), 
       count(*)
from scores
group by score/10
James Curran
fuente
Me gusta esto, pero tienes que arreglar los rangos fuera de la consulta si vas a mostrarlo.
tvanfosson
En caso de que decida corregir su respuesta, debe cambiar su puntaje / 10 en la primera línea para que sea (puntaje / 10) * 10 para ambos, de lo contrario obtendrá 3 - 12 en lugar de 30-39, etc. Según mi publicación a continuación, puede agregar un pedido para obtener los resultados en el orden correcto.
Timothy Walters,
5

Esto le permitirá no tener que especificar rangos y debe ser independiente del servidor SQL. Math FTW!

SELECT CONCAT(range,'-',range+9), COUNT(range)
FROM (
  SELECT 
    score - (score % 10) as range
  FROM scores
)
trevorgrayson
fuente
3

Lo haría de manera un poco diferente para que se escale sin tener que definir cada caso:

select t.range as [score range], count(*) as [number of occurences]
from (
  select FLOOR(score/10) as range
  from scores) t
group by t.range

No probado, pero tienes la idea ...

JoshNaro
fuente
2
declare @RangeWidth int

set @RangeWidth = 10

select
   Floor(Score/@RangeWidth) as LowerBound,
   Floor(Score/@RangeWidth)+@RangeWidth as UpperBound,
   Count(*)
From
   ScoreTable
group by
   Floor(Score/@RangeWidth)
Aheho
fuente
1
select t.blah as [score range], count(*) as [number of occurences]
from (
  select case 
    when score between  0 and  9 then ' 0-9 '
    when score between 10 and 19 then '10-19'
    when score between 20 and 29 then '20-29'
    ...
    else '90-99' end as blah
  from scores) t
group by t.blah

Asegúrese de utilizar una palabra que no sea 'rango' si está en MySQL, o recibirá un error al ejecutar el ejemplo anterior.

Danny Hui
fuente
1

Porque la columna que se está ordenando en (Range ordenando ) es una cadena, se usa la clasificación de cadenas / palabras en lugar de la ordenación numérica.

Siempre y cuando las cadenas tengan ceros para rellenar las longitudes de los números, la clasificación debería ser semánticamente correcta:

SELECT t.range AS ScoreRange,
       COUNT(*) AS NumberOfOccurrences
  FROM (SELECT CASE
                    WHEN score BETWEEN 0 AND 9 THEN '00-09'
                    WHEN score BETWEEN 10 AND 19 THEN '10-19'
                    ELSE '20-99'
               END AS Range
          FROM Scores) t
 GROUP BY t.Range

Si el rango es mixto, simplemente rellene un cero extra:

SELECT t.range AS ScoreRange,
       COUNT(*) AS NumberOfOccurrences
  FROM (SELECT CASE
                    WHEN score BETWEEN 0 AND 9 THEN '000-009'
                    WHEN score BETWEEN 10 AND 19 THEN '010-019'
                    WHEN score BETWEEN 20 AND 99 THEN '020-099'
                    ELSE '100-999'
               END AS Range
          FROM Scores) t
 GROUP BY t.Range
Kevin Hogg
fuente
1

Tratar

SELECT (str(range) + "-" + str(range + 9) ) AS [Score range], COUNT(score) AS [number of occurances]
FROM (SELECT  score,  int(score / 10 ) * 10  AS range  FROM scoredata )  
GROUP BY range;
Stubo
fuente
3
Sería útil si pudiera agregar alguna explicación sobre cómo su consulta resuelve el problema.
Devlin Carnate
-1

Tal vez estás preguntando sobre cómo mantener esas cosas ...

Por supuesto, invocará un escaneo completo de la tabla para las consultas y si la tabla que contiene los puntajes que se deben contar (agregaciones) es grande, es posible que desee una solución con mejor rendimiento, puede crear una tabla secundaria y usar reglas, como on insert - podrías investigarlo.

¡Sin embargo, no todos los motores RDBMS tienen reglas!

Richard T
fuente