2008-10-10 24 views
34

Dado este conjunto de datos:MySQL: SELECT N filas, pero con sólo valores únicos en una columna

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 las 3 personas más antiguos, pero sólo uno de cada ciudad.

Si no sería más que los tres mayores, 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 se retiraría y el siguiente (Sarah Connor/Los Angeles) entraría en su lugar.

¿Alguna solución elegante?

Actualización:

Actualmente una variación de PConroy es la mejor solución/más rápido:

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 búsqueda original con "IN" es extremadamente lento con grandes conjuntos de datos (abortada después de 5 minutos) , pero mover la subconsulta a un JOIN acelerará mucho. Tomó aproximadamente 0.15 segundos por aprox. 1 mio filas en mi entorno de prueba. Tengo un índice en "Ciudad, año de nacimiento" y un segundo justo en "Año de nacimiento".

Nota: Esto se relaciona con ...

+0

Nota: Este no es el problema real que tengo que resolver, sino un ejemplo. Necesito la solución para dos trabajos diferentes: a) encontrar el artículo con el precio más alto en cada habitación; si hay varios con el mismo precio: obtener el más nuevo. b) obtener 10 trabajos de una cola (ordenados por prioridad), pero solo uno por cliente. – BlaM

Respuesta

18
Probablemente no

el más elegante de soluciones, y el rendimiento de IN puede sufrir en las mesas más grandes.

La consulta anidada obtiene el mínimo Birthyear para cada ciudad. Solo los registros que tienen este Birthyear coinciden en la consulta externa. Ordenar por edad, entonces limitar a 3 resultados que las 3 personas más ancianas que son también las más antiguas de la ciudad recibe (Egon Spengler se retira ..)

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 - añadió GROUP BY City a consulta externa, como las personas con los mismos años de nacimiento devolverían valores múltiples. Agrupar en la consulta externa garantiza que solo se devolverá un resultado por ciudad, si más de una persona tiene ese mínimo Birthyear.La columna ct mostrará si más de una persona que existe en la ciudad con ese Birthyear

+0

Esto funcionaría con los datos de ejemplo brindados, pero "en la vida real" podría haber dos personas con el mismo año de nacimiento en la tabla. Esta consulta los devolverá a todos. :( – BlaM

+0

Buen punto, agrupando por ciudad, debería ordenarlo, responda actualizado ahora. Si tiene 2 personas en la misma ciudad con el mismo año de nacimiento, solo verá una de ellas, probablemente la primera ingresada, dependiendo del valor predeterminado tipo mysql está utilizando en su tabla. – ConroyP

+0

En realidad no lo hará. Recibirá un mensaje de error, porque no puede usar GROUP BY sin tener funciones agregadas en las columnas "no agrupadas" – BlaM

2

Algo por el estilo?

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) 
+0

Esto es T-SQL, lo sé. Debería ser fácilmente adaptable a MySQL o cualquier otro dialecto. – Tomalak

+0

MySQL no admite TOP/LIMIT en "IN" -Subqueries – BlaM

3

Probablemente esta no sea la solución más elegante y rápida, pero debería funcionar. Estoy ansioso por 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 
+0

Esto es lo más parecido a lo que habría hecho, aunque intentaría introducir el ID en alguna parte porque podría haber dos personas con la misma edad en la misma ciudad. . – BlaM

+0

BTW: En ese caso, no importa cuál de las dos personas es elegida. Simplemente debe ser "solo uno". – BlaM

1

No es bonita, pero debería funcionar también con varias personas con la misma fecha de nacimiento: datos

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 

de consultas :

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 
+0

Este también funcionó. Pero con muchas filas en la tabla, se vuelve realmente lento :) – BlaM

+0

@BlaM, ¿por qué lento? ¿Explicación? – Green

1

@BlaM

ACTUALIZADO acaba de encontrar que es bueno usar USING en lugar de ON. eliminará las 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; 

Post original

Hola, he tratado de usar su consulta actualizada pero yo estaba recibiendo resultados erróneos hasta que he añadido condición adicional para unirse a (también la columna adicional en unirse a select). trasladado a su consulta, I'am 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 ser necesario última GROUP BY P.City, pero lo he dejado ahí por ahora, por si acaso. probablemente lo elimine más tarde.

Cuestiones relacionadas