Obtenga los n primeros registros para cada grupo de resultados agrupados [duplicado]

Resuelto Yarin asked hace 12 años • 12 respuestas

El siguiente es el ejemplo más simple posible, aunque cualquier solución debería poder escalarse a la cantidad de n resultados principales necesarios:

Dada una tabla como la siguiente, con columnas de persona, grupo y edad, ¿cómo obtendrías las 2 personas mayores de cada grupo? (Los empates dentro de los grupos no deben dar más resultados, pero sí dar los 2 primeros en orden alfabético)

+--------+-------+-----+
| Persona | Grupo | Edad |
+--------+-------+-----+
| Bob | 1 | 32 |
| Jill | 1 | 34 |
| Shawn | 1 | 42 |
| Jake | 2 | 29 |
| Pablo | 2 | 36 |
| Laura | 2 | 39 |
+--------+-------+-----+

Conjunto de resultados deseado:

+--------+-------+-----+
| Shawn | 1 | 42 |
| Jill | 1 | 34 |
| Laura | 2 | 39 |
| Pablo | 2 | 36 |
+--------+-------+-----+

NOTA: Esta pregunta se basa en una anterior: Obtener registros con valor máximo para cada grupo de resultados SQL agrupados , para obtener una única fila superior de cada grupo, y que recibió una excelente respuesta específica de MySQL de @Bohemian:

select * 
from (select * from mytable order by `Group`, Age desc, Person) x
group by `Group`

Me encantaría poder aprovechar esto, aunque no veo cómo.

Yarin avatar Aug 25 '12 00:08 Yarin
Aceptado

Aquí hay una forma de hacer esto, usando UNION ALL(consulte SQL Fiddle with Demo ). Esto funciona con dos grupos; si tiene más de dos grupos, deberá especificar el groupnúmero y agregar consultas para cada uno group:

(
  select *
  from mytable 
  where `group` = 1
  order by age desc
  LIMIT 2
)
UNION ALL
(
  select *
  from mytable 
  where `group` = 2
  order by age desc
  LIMIT 2
)

Hay varias formas de hacer esto; consulte este artículo para determinar la mejor ruta para su situación:

http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/

Editar:

Esto también podría funcionar para usted, genera un número de fila para cada registro. Usando un ejemplo del enlace anterior, esto devolverá solo aquellos registros con un número de fila menor o igual a 2:

select person, `group`, age
from 
(
   select person, `group`, age,
      (@num:=if(@group = `group`, @num +1, if(@group := `group`, 1, 1))) row_number 
  from test t
  CROSS JOIN (select @num:=0, @group:=null) c
  order by `Group`, Age desc, person
) as x 
where x.row_number <= 2;

Ver demostración

Taryn avatar Aug 24 '2012 17:08 Taryn

En otras bases de datos puedes hacer esto usando ROW_NUMBER. MySQL no es compatible ROW_NUMBER, pero puedes usar variables para emularlo:

SELECT
    person,
    groupname,
    age
FROM
(
    SELECT
        person,
        groupname,
        age,
        @rn := IF(@prev = groupname, @rn + 1, 1) AS rn,
        @prev := groupname
    FROM mytable
    JOIN (SELECT @prev := NULL, @rn := 0) AS vars
    ORDER BY groupname, age DESC, person
) AS T1
WHERE rn <= 2

Véalo funcionando en línea: sqlfiddle


Editar Me acabo de dar cuenta de que bluefeet publicó una respuesta muy similar: +1 para él. Sin embargo, esta respuesta tiene dos pequeñas ventajas:

  1. Es una única consulta. Las variables se inicializan dentro de la instrucción SELECT.
  2. Maneja vínculos como se describe en la pregunta (orden alfabético por nombre).

Así que lo dejaré aquí por si a alguien le puede ayudar.

Mark Byers avatar Aug 24 '2012 22:08 Mark Byers

Prueba esto:

SELECT a.person, a.group, a.age FROM person AS a WHERE 
(SELECT COUNT(*) FROM person AS b 
WHERE b.group = a.group AND b.age >= a.age) <= 2 
ORDER BY a.group ASC, a.age DESC

MANIFESTACIÓN

snuffn avatar Aug 24 '2012 17:08 snuffn