MySQL: seleccione N filas, pero con solo valores únicos en una columna
-
07-07-2019 - |
Pregunta
Dado este conjunto de datos:
ID Name City Birthyear
1 Egon Spengler New York 1957
2 Mac Taylor New York 1955
3 Sarah Connor Los Angeles 1959
4 Jean-Luc Picard La Barre 2305
5 Ellen Ripley Nostromo 2092
6 James T. Kirk Riverside 2233
7 Henry Jones Chicago 1899
Necesito encontrar a las 3 personas más viejas, pero solo una de cada ciudad.
Si solo fueran los tres más antiguos, sería ...
- Henry Jones / Chicago
- Mac Taylor / Nueva York
- Egon Spengler / Nueva York
Sin embargo, dado que tanto Egon Spengler como Mac Taylor se encuentran en Nueva York, Egon Spengler abandonaría y en su lugar entraría la siguiente (Sarah Connor / Los Ángeles).
¿Alguna solución elegante?
Update:
Actualmente, una variación de PConroy es la mejor / más rápida solución:
SELECT P.*, COUNT(*) AS ct
FROM people P
JOIN (SELECT MIN(Birthyear) AS Birthyear
FROM people
GROUP by City) P2 ON P2.Birthyear = P.Birthyear
GROUP BY P.City
ORDER BY P.Birthyear ASC
LIMIT 10;
Su consulta original con " IN " es extremadamente lento con grandes conjuntos de datos (abortados después de 5 minutos), pero mover la subconsulta a JOIN lo acelerará mucho. Tomó alrededor de 0,15 segundos durante aprox. 1 millón de filas en mi entorno de prueba. Tengo un índice en "Ciudad, año de nacimiento" y un segundo solo en "Año de nacimiento".
Nota: Esto está relacionado con ...
Solución
Probablemente no sea la solución más elegante, y el rendimiento de IN
puede verse afectado en tablas más grandes.
La consulta anidada obtiene el Birthyear
mínimo para cada ciudad. Solo los registros que tienen este Birthyear
coinciden en la consulta externa. Ordenar por edad y luego limitarlo a 3 resultados te da las 3 personas más viejas que también son las más antiguas de su ciudad (Egon Spengler abandona ...)
SELECT Name, City, Birthyear, COUNT(*) AS ct
FROM table
WHERE Birthyear IN (SELECT MIN(Birthyear)
FROM table
GROUP by City)
GROUP BY City
ORDER BY Birthyear DESC LIMIT 3;
+-----------------+-------------+------+----+
| name | city | year | ct |
+-----------------+-------------+------+----+
| Henry Jones | Chicago | 1899 | 1 |
| Mac Taylor | New York | 1955 | 1 |
| Sarah Connor | Los Angeles | 1959 | 1 |
+-----------------+-------------+------+----+
Editar : se agregó GROUP BY City
a la consulta externa, ya que las personas con los mismos años de nacimiento devolverían múltiples valores. La agrupación en la consulta externa asegura que solo se devolverá un resultado por ciudad, si más de una persona tiene ese mínimo Birthyear
. La columna ct
mostrará si existe más de una persona en la ciudad con ese Birthyear
Otros consejos
Probablemente esta no sea la solución más elegante y rápida, pero debería funcionar. Estoy deseando ver las soluciones de los gurús de bases de datos reales.
select p.* from people p,
(select city, max(age) as mage from people group by city) t
where p.city = t.city and p.age = t.mage
order by p.age desc
¿Algo así?
SELECT
Id, Name, City, Birthyear
FROM
TheTable
WHERE
Id IN (SELECT TOP 1 Id FROM TheTable i WHERE i.City = TheTable.City ORDER BY Birthyear)
No es bonito, pero también debería funcionar con varias personas con el mismo dob:
Datos de prueba:
select id, name, city, dob
into people
from
(select 1 id,'Egon Spengler' name, 'New York' city , 1957 dob
union all select 2, 'Mac Taylor','New York', 1955
union all select 3, 'Sarah Connor','Los Angeles', 1959
union all select 4, 'Jean-Luc Picard','La Barre', 2305
union all select 5, 'Ellen Ripley','Nostromo', 2092
union all select 6, 'James T. Kirk','Riverside', 2233
union all select 7, 'Henry Jones','Chicago', 1899
union all select 8, 'Blah','New York', 1955) a
Consulta:
select
*
from
people p
left join people p1
ON
p.city = p1.city
and (p.dob > p1.dob and p.id <> p1.id)
or (p.dob = p1.dob and p.id > p1.id)
where
p1.id is null
order by
p.dob
@BlaM
ACTUALIZADO acabo de descubrir que es bueno usar USING en lugar de ON. eliminará columnas duplicadas en el resultado.
SELECT P.*, COUNT(*) AS ct
FROM people P
JOIN (SELECT City, MIN(Birthyear) AS Birthyear
FROM people
GROUP by City) P2 USING(Birthyear, City)
GROUP BY P.City
ORDER BY P.Birthyear ASC
LIMIT 10;
PUBLICACIÓN ORIGINAL
hola, he intentado usar tu consulta actualizada pero estaba obteniendo resultados incorrectos hasta que agregué una condición adicional para unirme (también una columna adicional en la selección de combinación). transferido a su consulta, estoy usando esto:
SELECT P.*, COUNT(*) AS ct
FROM people P
JOIN (SELECT City, MIN(Birthyear) AS Birthyear
FROM people
GROUP by City) P2 ON P2.Birthyear = P.Birthyear AND P2.City = P.City
GROUP BY P.City
ORDER BY P.Birthyear ASC
LIMIT 10;
en teoría no debería necesitar el último GROUP BY P.City, pero lo he dejado allí por ahora, por si acaso. probablemente lo eliminará más tarde.