2010-10-22 4 views
12

Ich stehe vor einem sehr häufigen Problem bezüglich "Auswahl der obersten N Zeilen für jede Gruppe in einer Tabelle".Auswahl der oberen N Zeilen für jede Gruppe in einer Tabelle

Betrachten Sie eine Tabelle mit id, name, hair_colour, score Spalten.

Ich möchte ein Resultset so, dass für jede Haarfarbe, mich Top 3 Scorernamen bekommen.

das ich genau habe zu lösen, was ich auf Rick Osborne's blogpost "sql-getting-top-n-rows-for-a-grouped-query"

brauchen Diese Lösung nicht wie erwartet funktioniert, wenn meine Noten gleich sind.

Im obigen Beispiel das Ergebnis wie folgt.

id name hair score ranknum 
--------------------------------- 
12 Kit Blonde 10 1 
    9 Becca Blonde 9 2 
    8 Katie Blonde 8 3 
    3 Sarah Brunette 10 1  
    4 Deborah Brunette 9 2 - ------- - - > if 
    1 Kim Brunette 8 3 

Betrachten Sie die Zeile 4 Deborah Brunette 9 2. Wenn das gleiche Ergebnis (10) wie Sarah ist, dann wird ranknum 2,2,3 für "Brunette" Art der Haare sein.

Was ist die Lösung dafür?

+1

Welche RDBMS verwenden Sie? –

+0

Es gibt eine Lösung dafür unter http://stackoverflow.com/questions/3823939/ für den Fall, dass Sie nicht die neueren SQL Server verwenden. –

Antwort

16

Wenn Sie SQL Server 2005 oder höher verwenden, können Sie die Rangfunktionen und einen Wärmeausdehnungskoeffizienten verwenden, um dies zu erreichen:

;WITH HairColors AS 
(SELECT id, name, hair, score, 
     ROW_NUMBER() OVER(PARTITION BY hair ORDER BY score DESC) as 'RowNum' 
) 
SELECT id, name, hair, score 
FROM HairColors 
WHERE RowNum <= 3 

Diese CTE wird „Partition“, um Ihre Daten durch den Wert der Spalte hair , und jede Partition ist dann Reihenfolge für Punktzahl (absteigend) und erhält eine Zeilennummer; die höchste Punktzahl für jede Partition ist 1, dann 2 etc.

Wenn Sie also die TOP 3 jeder Gruppe haben möchten, wählen Sie nur die Zeilen aus dem CTE aus, die eine RowNum von 3 oder weniger haben (1, 2, 3) -> los gehts!

+0

ROW_NUMBER() OVER (PARTITION nach Haare ORDER BY Punktzahl DESC) als 'RowNum') die Klammer in dieser Zeile ist nicht ausgeglichen. ist das kompatibel mit db2 sql grammatik? – zinking

+0

@zinking: danke - da war ein schließender parens zu viele ... gefixt! Ich weiß nicht, ob DB2 dies unterstützt (ich kenne DB2 nicht genug) - aber es ist definitiv ein ANSI/ISO SQL-Standardkonstrukt - kein von Microsoft erfundenes Feature :-) –

+1

Scheiße, das hat meinen Tag einfach gemacht! Was für eine Einführung in CTEs! –

0

Die Art und Weise, wie der Algorithmus den Rang erreicht, besteht darin, die Anzahl der Zeilen im Cross-Produkt mit einer Punktzahl gleich oder größer als die des betreffenden Mädchens zu zählen, um Rang zu generieren. Daher im Problemfall über Sie sprechen, Gitter Sarah würde aussehen wie

a.name | a.score | b.name | b.score 
-------+---------+---------+-------- 
Sarah | 9  | Sarah | 9 
Sarah | 9  | Deborah | 9 

und in ähnlicher Weise für Deborah, weshalb beiden Mädchen einen Rang von 2 hier.

Das Problem ist, dass, wenn es eine Krawatte gibt, alle Mädchen den niedrigsten Wert im gebundenen Bereich aufgrund dieser Zählung nehmen, wenn Sie wollen, dass sie stattdessen den höchsten Wert nehmen. Ich denke, eine einfache Änderung kann das beheben:

Verwenden Sie anstelle eines Größer-als-Gleich-Vergleichs einen strengen Größer-als-Vergleich, um die Anzahl der Mädchen zu zählen, die streng besser sind. Dann füge einen dazu hinzu und du hast deinen Rang (der sich mit Bindungen wie angemessen befassen wird). Also wäre die innere Auswahl:

SELECT a.id, COUNT(*) + 1 AS ranknum 
FROM girl AS a 
    INNER JOIN girl AS b ON (a.hair = b.hair) AND (a.score < b.score) 
GROUP BY a.id 
HAVING COUNT(*) <= 3 

Kann jemand irgendwelche Probleme mit diesem Ansatz sehen, die meiner Aufmerksamkeit entgangen sind?

+0

Läuft das nicht in quadratischer Zeit? – b0fh

0

Verwenden Sie diese Verbindung auswählen, die richtig OP Problem behandelt

SELECT g.* FROM girls as g 
WHERE g.score > IFNULL((SELECT g2.score FROM girls as g2 
       WHERE g.hair=g2.hair ORDER BY g2.score DESC LIMIT 3,1), 0) 

Beachten Sie, dass Sie benötigen IFNULL hier zu verwenden Fall zu behandeln, wenn Tabelle Mädchen weniger Zeilen für irgendeine Art von Haar hat dann wollen wir siehe in SQL-Antwort (in OP-Fall ist es 3 Artikel).