Подтвердить что ты не робот

MySQL: выберите N строк, но с уникальными значениями в одном столбце

Учитывая этот набор данных:

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

Мне нужно найти 3 самых старых человека, но только один из каждого города.

Если бы это были три самые старые, это было бы...

  • Генри Джонс/Чикаго
  • Мак Тейлор/Нью-Йорк
  • Эгон Шпенглер/Нью-Йорк

Однако, поскольку и Эгон Шпенглер, и Мак Тейлор находятся в Нью-Йорке, Эгон Шпенглер выпадет, а следующая (Сара Коннор/Лос-Анджелес) войдет вместо этого.

Любые изящные решения?

Update:

В настоящее время вариант PConroy - лучшее/быстрое решение:

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;

Его исходный запрос с "IN" чрезвычайно медленный с большими наборами данных (прерывается через 5 минут), но перемещение подзапроса в JOIN ускорит его. Это заняло около 0,15 секунды прибл. 1 mio строк в моей тестовой среде. У меня есть указатель на "Город, рождение", а второй - на "Рождение".

Примечание. Это связано с...

4b9b3361

Ответ 1

Вероятно, это не самые элегантные решения, а производительность IN может пострадать в больших таблицах.

Вложенный запрос получает минимум Birthyear для каждого города. Только записи, которые имеют этот Birthyear, сопоставляются во внешнем запросе. Заказывая по возрасту, затем ограничивая 3 результата, вы получаете 3 самых старых человека, которые также являются самыми старыми в своем городе (Эгон Шпенглер выпадает..)

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

Изменить - добавлен GROUP BY City к внешнему запросу, так как люди с одинаковыми годами рождения возвратят несколько значений. Группировка по внешнему запросу гарантирует, что на один город будет возвращен только один результат, если более одного человека имеет минимум Birthyear. Столбец ct покажет, существует ли в городе более одного человека с этим Birthyear

Ответ 2

Это, вероятно, не самое элегантное и быстрое решение, но оно должно работать. Я с нетерпением жду решения реальных гуру базы данных.

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

Что-то вроде этого?

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)

Ответ 4

Не очень, но должен работать и с несколькими людьми с одним и тем же добом:

Данные теста:

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

Query:

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

Ответ 5

@BlaM

ОБНОВЛЕНО просто обнаружил, что его полезно использовать USING вместо ON. он удалит дубликаты столбцов в результате.

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;

ОРИГИНАЛЬНАЯ ПОЧТА

Привет, я пытался использовать ваш обновленный запрос, но я получал неправильные результаты, пока не добавил дополнительные условия для присоединения (также дополнительный столбец для выбора соединения). переданный по вашему запросу, я воспользуюсь этим:

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;

в теории вам не понадобится последняя GROUP BY P.City, но я оставил ее там сейчас, на всякий случай. вероятно, удалит его позже.