MySQL: выберите N строк, но только с уникальными значениями в одном столбце - PullRequest
34 голосов
/ 10 октября 2008

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

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 самых старых людей, но только по одному в каждом городе.

Если бы это были только три самых старых, это было бы ...

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

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

Какие-нибудь элегантные решения?

Обновление:

На данный момент вариант 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 млн строк в моей тестовой среде. У меня есть индекс "Город, год рождения", а второй только "Год рождения".

Примечание: это относится к ...

Ответы [ 5 ]

18 голосов
/ 10 октября 2008

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

Вложенный запрос получает минимум Birthyear для каждого города. Только записи с этим Birthyear сопоставляются во внешнем запросе. Если вы упорядочите по возрасту, а затем ограничитесь тремя результатами, вы получите 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

3 голосов
/ 10 октября 2008

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

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
2 голосов
/ 10 октября 2008

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

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)
1 голос
/ 14 августа 2010

@ Бламу

ОБНОВЛЕНО только что обнаружил, что хорошо использовать 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, но я оставил его там пока, на всякий случай. возможно, удалит его позже.

1 голос
/ 10 октября 2008

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

Данные испытаний:

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

Запрос:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...