MySQL: sélectionnez N lignes, mais avec des valeurs uniques dans une colonne
-
07-07-2019 - |
Question
Étant donné cet ensemble de données:
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
Je dois trouver les 3 personnes les plus âgées, mais une seule par ville.
Si ce ne sont que les trois plus anciens, ce serait ...
- Henry Jones / Chicago
- Mac Taylor / New York
- Egon Spengler / New York
Cependant, puisque Egon Spengler et Mac Taylor sont tous deux situés à New York, Egon Spengler abandonnerait et le prochain (Sarah Connor / Los Angeles) entrerait à la place.
Des solutions élégantes?
Mise à jour:
Actuellement, une variante de PConroy est la solution la meilleure / la plus rapide:
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;
Sa requête initiale avec " IN " est extrêmement lent avec de grands ensembles de données (abandonné au bout de 5 minutes), mais le déplacement de la sous-requête vers un JOIN accélérera beaucoup. Il a fallu environ 0,15 seconde pour env. 1 mio de lignes dans mon environnement de test. J'ai un index sur "Ville, année de naissance". et un deuxième juste sur "Birthyear".
Remarque: cela est lié à ...
La solution
Ce n’est probablement pas la solution la plus élégante, et les performances de IN
risquent de pâtir des tables plus volumineuses.
La requête imbriquée obtient le Année de naissance
minimum pour chaque ville. Seuls les enregistrements ayant ce Birthyear
sont mis en correspondance dans la requête externe. En classant par âge puis en limitant les 3 résultats obtenus, vous obtenez les 3 personnes les plus âgées qui sont aussi les plus âgées de leur ville (Egon Spengler abandonne ..)
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 |
+-----------------+-------------+------+----+
Modifier : ajout de GROUP BY City
à la requête externe, car les personnes ayant la même année de naissance renverraient plusieurs valeurs. Le regroupement sur la requête externe garantit qu'un seul résultat sera renvoyé par ville, si plusieurs personnes ont ce minimum Année de naissance
. La colonne ct
indiquera si plus d'une personne existe dans la ville avec ce Birthyear
Autres conseils
Ce n’est probablement pas la solution la plus élégante et la plus rapide, mais cela devrait fonctionner. Je suis impatient de voir les solutions de vrais gourous de la base de données.
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
Quelque chose comme ça?
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)
Ce n’est pas beau, mais vous devriez aussi travailler avec plusieurs personnes ayant le même dob:
Données de test:
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
Requête:
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
MISE À JOUR Je viens de constater qu'il est bon d'utiliser USING au lieu de ON. cela supprimera les colonnes en double dans le résultat.
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
salut, j’ai essayé d’utiliser votre requête mise à jour, mais j’obtenais des résultats erronés jusqu’à ce que j’ai ajouté une condition supplémentaire pour rejoindre (également une colonne supplémentaire dans la sélection de jointure). transféré à votre requête, je l'utilise:
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 théorie, vous n’auriez pas besoin de la dernière version de GROUP BY P.City, mais je l’ai laissée là pour le moment, juste au cas où. le supprimera probablement plus tard.