MySQL: Selecione n linhas, mas com apenas valores exclusivos em uma coluna
-
07-07-2019 - |
Pergunta
Dado este conjunto de dados:
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
Eu preciso encontrar as três pessoas mais velhas, mas apenas uma de todas as cidades.
Se fosse apenas os três mais antigos, seria ...
- Henry Jones / Chicago
- Mac Taylor / Nova York
- Egon Spengler / Nova York
No entanto, como Egon Spengler e Mac Taylor estão localizados em Nova York, Egon Spengler abandonaria e o próximo (Sarah Connor / Los Angeles) entraria em frente.
Alguma soluções elegantes?
Atualizar:
Atualmente, uma variação do PConroy é a melhor/mais rápida solução:
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;
Sua consulta original com "In" é extremamente lenta com grandes conjuntos de dados (abortados após 5 minutos), mas mover a subconsência para uma junção acelerará muito. Demorou cerca de 0,15 segundos por aprox. 1 mio linhas no meu ambiente de teste. Eu tenho um índice em "City, BirthYear" e um segundo apenas em "BirthYear".
Nota: Isso está relacionado a ...
Solução
Provavelmente não é a mais elegante das soluções e o desempenho de IN
pode sofrer em tabelas maiores.
A consulta aninhada recebe o mínimo Birthyear
para cada cidade. Apenas registros que têm isso Birthyear
são pareados na consulta externa. O pedido por idade e depois a limitação de 3 resultados oferece as três pessoas mais antigas que também são as mais antigas da cidade (Egon Spengler desiste ..)
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 - adicionado GROUP BY City
Para a consulta externa, como pessoas com os mesmos anos de nascimento retornariam vários valores. O agrupamento na consulta externa garante que apenas um resultado seja devolvido por cidade, se mais de uma pessoa tiver esse mínimo Birthyear
. o ct
A coluna mostrará se mais de uma pessoa existir na cidade com isso Birthyear
Outras dicas
Provavelmente não é a solução mais elegante e rápida, mas deve funcionar. Estou ansioso para ver as soluções de gurus do banco de dados reais.
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 parecido?
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)
Não é bonito, mas deve funcionar também com várias pessoas com o mesmo DOB:
Dados de teste:
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
ATUALIZADAAcabei de descobrir que é bom usar em vez de em vez de. Ele removerá colunas duplicadas no 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;
Postagem original
Olá, tentei usar sua consulta atualizada, mas estava obtendo resultados errados até adicionar condições extras para participar (também coluna extra no seleção de junção). transferido para sua consulta, estou usando isso:
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;
Em teoria, você não deve precisar do último grupo por P.City, mas eu o deixei lá por enquanto, apenas por precaução. Provavelmente o removerá mais tarde.