web-dev-qa-db-fra.com

MySQL: sélectionnez N lignes, mais avec uniquement des valeurs uniques dans une colonne

Compte tenu de 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

J'ai besoin de trouver les 3 personnes les plus âgées, mais seulement une de chaque 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 d'origine avec "IN" est extrêmement lente avec les grands ensembles de données (abandonnée après 5 minutes), mais le déplacement de la sous-requête vers un JOIN l'accélérera beaucoup. Cela a pris environ 0,15 seconde pendant env. 1 mio de lignes dans mon environnement de test. J'ai un index sur "City, Birthyear" et un second sur "Birthyear".

Remarque: Ceci est lié à ...

34
BlaM

Probablement pas la plus élégante des solutions, et les performances de IN peuvent souffrir sur des tables plus grandes.

La requête imbriquée obtient le minimum Birthyear pour chaque ville. Seuls les enregistrements qui ont ce Birthyear sont mis en correspondance dans la requête externe. Ordonner par âge puis limiter à 3 résultats vous permet d'obtenir 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é GROUP BY City à la requête externe, car les personnes ayant les mêmes années de naissance renverraient plusieurs valeurs. Le regroupement sur la requête externe garantit qu'un seul résultat sera retourné par ville, si plus d'une personne a ce minimum Birthyear. La colonne ct indiquera si plus d'une personne existe dans la ville avec cette Birthyear

18
ConroyP

Ce n'est probablement pas la solution la plus élégante et la plus rapide, mais cela devrait fonctionner. J'attends avec impatience les solutions des 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
3
Tamas Czinege

Quelque chose comme ca?

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)
2
Tomalak

@BlaM

MIS À JOUR vient de trouver qu'il est bon d'utiliser USING au lieu de ON. il supprimera le résultat des colonnes en double.

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;

ORIGINAL POST

salut, j'ai essayé d'utiliser votre requête mise à jour mais j'obtenais des résultats erronés jusqu'à ce que j'ajoute une condition supplémentaire pour rejoindre (également une colonne supplémentaire dans la sélection de jointure). transféré à votre requête, j'utilise ceci:

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 ne devriez pas avoir besoin du dernier GROUP BY P.City, mais je l'ai laissé là pour le moment, juste au cas où. le supprimera probablement plus tard.

1
gondo

Pas joli mais devrait aussi fonctionner avec plusieurs personnes avec 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

Requete:

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
1
kristof