2008-10-10 18 views
34

diesen Datensatz Gegeben:MySQL: Wählen Sie N Zeilen, aber nur mit eindeutigen Werten in einer Spalte

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 

Ich brauche die drei ältesten Personen zu finden, aber nur eine von jeder Stadt.

Wenn es wäre nur die drei ältesten, wäre es ...

  • Henry Jones/Chicago
  • Mac Taylor/New York
  • Egon Spengler/New York

Da sowohl Egon Spengler als auch Mac Taylor in New York ansässig sind, würde Egon Spengler ausfallen und der nächste (Sarah Connor/Los Angeles) würde stattdessen kommen.

Irgendwelche eleganten Lösungen?

Update:

Zeit eine Variation von PConroy ist die beste/schnellste Lösung:

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; 

Seine ursprüngliche Abfrage mit "IN" ist extrem langsam mit großen Datenmengen (abgebrochen nach 5 Minuten) , aber die Unterabfrage zu einem JOIN zu verschieben, wird es viel schneller machen. Es dauerte etwa 0,15 Sekunden für ca. 1 Mio. Zeilen in meiner Testumgebung. Ich habe einen Index für "City, Birthyear" und einen zweiten für "Birthyear".

Hinweis: Dies bezieht sich auf ...

+0

Hinweis: Dies ist nicht das eigentliche Problem, das ich lösen muss, aber ein Beispiel. Ich brauche die Lösung für zwei verschiedene Jobs: a) Finde den teuersten Gegenstand in jedem Zimmer - wenn mehrere zum selben Preis: Hol dir den neuesten. b) 10 Jobs aus einer Warteschlange holen (geordnet nach Priorität), aber nur eine pro Kunde. – BlaM

Antwort

18

Wahrscheinlich nicht die eleganteste Lösung, und die Leistung von IN kann auf größere Tabellen leiden.

Die verschachtelte Abfrage erhält das Minimum Birthyear für jede Stadt. Nur Datensätze, die diese Birthyear haben, werden in der äußeren Abfrage abgeglichen. Sortiert nach Alter dann auf 3 Ergebnisse Begrenzung bekommt man die drei ältesten Menschen, die auch die älteste in der Stadt sind (Egon Spengler fällt ab ..)

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

bearbeiten - hinzugefügt GROUP BY City zur äußeren Abfrage, wie Menschen mit gleiche Geburtsjahre würden mehrere Werte zurückgeben. Gruppierung auf der äußeren Abfrage stellt sicher, dass nur ein Ergebnis pro Stadt zurückgegeben wird, wenn mehr als eine Person dieses Minimum Birthyear hat.Die ct Spalte wird angezeigt, wenn mehr als eine Person mit diesem Birthyear in der Stadt existiert

+0

Dies würde mit den angegebenen Beispieldaten funktionieren, aber "im wirklichen Leben" könnten zwei Personen mit demselben Geburtsjahr in der Tabelle sein. Diese Abfrage würde alle von ihnen zurückgeben. :( – BlaM

+0

Guter Punkt, Gruppierung nach Stadt sollte sortieren, Antwort jetzt aktualisiert. Wenn Sie 2 Personen in der gleichen Stadt mit dem gleichen Geburtsjahr haben, sehen Sie nur eine von ihnen - wahrscheinlich die erste Eingabe, abhängig von der Standardeinstellung Sortierung, die mysql in Ihrer Tabelle verwendet. – ConroyP

+0

Eigentlich werden Sie nicht. Sie erhalten eine Fehlermeldung, weil Sie GROUP BY ohne Aggregatfunktionen in den "nicht gruppierten" Spalten verwenden können. – BlaM

2

So etwas wie das?

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

Das ist T-SQL, ich weiß. Sollte leicht an MySQL oder jeden anderen Dialekt angepasst werden können. – Tomalak

+0

MySQL unterstützt TOP/LIMIT in "IN" -Subqueries nicht – BlaM

3

Dies ist wahrscheinlich nicht die eleganteste und schnellste Lösung, aber es sollte funktionieren. Ich freue mich darauf, die Lösungen von echten Datenbank-Gurus zu sehen.

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

Dies ist am nächsten, was ich getan hätte, obwohl ich versuchen würde, die ID irgendwo einzuführen, weil es zwei Leute mit demselben Alter in der gleichen Stadt geben könnte . – BlaM

+0

BTW: In diesem Fall ist es egal, welche der beiden Personen gewählt wird. Es sollte nur "nur eins" sein. – BlaM

1

Nicht hübsch, aber soll auch mit dem gleichen dob mit mehreren Personen arbeiten:

Testdaten:

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 

Abfrage :

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

Dieser arbeitete auch, aber mit vielen Reihen in der Tabelle, es wird wirklich slooooow :) – BlaM

+0

@BlaM, warum langsam? Erläuterung? – Green

1

@BlaM

AKTUALISIERT gerade gefunden, dass es gut ist, USING anstelle von ON zu verwenden. Es entfernt doppelte Spalten im Ergebnis.

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

Hallo, ich habe versucht, die aktualisierte Abfrage zu verwenden, aber ich war immer zu falschen Ergebnissen, bis ich zusätzliche Bedingung hinzugefügt haben beizutreten (auch zusätzliche Spalte in beitreten wählen). auf Ihre Anfrage übertragen, diese i'am mit:

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; 

in der Theorie sollten Sie zuletzt GROUP BY P.City nicht brauchen, aber ich habe jetzt dort nach links, nur für den Fall. wird es wahrscheinlich später entfernen.