¿Cómo contar las ocurrencias de un valor de columna de manera eficiente en SQL?

166

Tengo una mesa de alumnos:

id | age
--------
0  | 25
1  | 25
2  | 23

Quiero consultar a todos los estudiantes, y una columna adicional que cuenta cuántos estudiantes son de la misma edad:

id | age | count
----------------
0  | 25  | 2
1  | 25  | 2
2  | 23  | 1

¿Cuál es la forma más eficiente de hacer esto? Me temo que una subconsulta será lenta, y me pregunto si hay una mejor manera . ¿Esta ahí?

Assaf Lavie
fuente

Respuestas:

255

Esto debería funcionar:

SELECT age, count(age) 
  FROM Students 
 GROUP by age

Si necesita la identificación también, puede incluir lo anterior como una subconsulta de esta manera:

SELECT S.id, S.age, C.cnt
  FROM Students  S
       INNER JOIN (SELECT age, count(age) as cnt
                     FROM Students 
                    GROUP BY age) C ON S.age = C.age
Mike Dinescu
fuente
2
para la segunda consulta, la selección externa debe estar en C.cnt porque no hay S.cnt; de lo contrario, obtendrá un error: Nombre de columna no válido 'cnt'
KM.
1
me da un error cuando estoy usando select case_id, count (pgm_code) del grupo pgm por pgm_code; dice que no es un grupo por expresión
Rishabh Agarwal
26

Si está utilizando Oracle, entonces una característica llamada análisis hará el truco. Se parece a esto:

select id, age, count(*) over (partition by age) from students;

Si no está utilizando Oracle, deberá volver a unirse a los recuentos:

select a.id, a.age, b.age_count
  from students a
  join (select age, count(*) as age_count
          from students
         group by age) b
    on a.age = b.age
Jeremy Bourque
fuente
2
Para su información, en SQL Server 2005, la segunda consulta se ejecuta con casi la mitad del coste de ejecución (utilizando SET SHOWPLAN_ALL EN ) como el primero. Pensé que el primero hubiera sido mejor, pero la unión de la vieja escuela lo superó.
KM.
1
"old school join beat it" simplemente porque el TOTAL ROW COUNT a procesar es diferente. En la segunda consulta, hay un grupo integrado que potencialmente reduce en gran medida el número de filas. Intente agregar DISTINCT a la primera consulta: "seleccione DISTINCT id, age, count (*) over (particion by age) from students" - eso debería ser comparable
quetzalcoatl
19

Aquí hay otra solución. este usa una sintaxis muy simple. El primer ejemplo de la solución aceptada no funcionó en versiones anteriores de Microsoft SQL (es decir, 2000)

SELECT age, count(*)
FROM Students 
GROUP by age
ORDER BY age
Damian
fuente
1
Sin embargo, si agrupa por edad, solo obtendría una entrada para los 25 años con un recuento de 2 (cuando en realidad quieren 2 entradas con un recuento de 2 e identificaciones separadas para el ejemplo dado).
Ian
1
Ian, gracias por los comentarios. ¿Ejecutó su reclamo contra una base de datos MS SQL 2000?
Damian
7

Haría algo como:

select
 A.id, A.age, B.count 
from 
 students A, 
 (select age, count(*) as count from students group by age) B
where A.age=B.age;
quosoo
fuente
4
select s.id, s.age, c.count
from students s
inner join (
    select age, count(*) as count
    from students
    group by age
) c on s.age = c.age
order by id
RedFilter
fuente
1

y si los datos en la columna "edad" tienen registros similares (es decir, muchas personas tienen 25 años, muchas otras tienen 32 y así sucesivamente), causa confusión al alinear el conteo correcto con cada estudiante. Para evitarlo, también me uní a las tablas de identificación del estudiante.

SELECT S.id, S.age, C.cnt
FROM Students S 
INNER JOIN (SELECT id, age, count(age) as cnt  FROM Students GROUP BY student,age) 
C ON S.age = C.age *AND S.id = C.id*
afii_palang
fuente