2010-10-22 22 views
12

Sto affrontando un problema molto comune relativo a "Selezione delle prime N righe per ogni gruppo in una tabella".selezionando le prime N righe per ogni gruppo in una tabella

Prendere in considerazione una tabella con colonne id, name, hair_colour, score.

Desidero un set di risultati tale che, per ogni colore di capelli, ottenere i primi 3 nomi degli autori.

per risolvere questo ho ottenuto esattamente che cosa ho bisogno in Rick Osborne's blogpost "sql-getting-top-n-rows-for-a-grouped-query"

Questa soluzione non funziona come previsto quando i miei punteggi sono uguali.

Nell'esempio precedente il risultato è il seguente.

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 

Prendere in considerazione la riga 4 Deborah Brunette 9 2. Se anche questo ha lo stesso punteggio (10) uguale a quello di Sarah, allora il ranknum sarà 2,2,3 per il tipo di capelli "Bruna".

Qual è la soluzione a questo?

+1

Che tipo di RDBMS stai utilizzando? –

+0

Una soluzione è disponibile all'indirizzo http://stackoverflow.com/questions/3823939/ nel caso in cui non si stiano utilizzando i nuovi SQL Server. –

risposta

16

Se stai usando SQL Server 2005 o più recente, è possibile utilizzare le funzioni di classificazione e di una CTE per raggiungere questo obiettivo:

;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 

Questa CTE sarà "partizione" di dati per il valore della colonna hair e ogni partizione è quindi ordinata per punteggio (decrescente) e ottiene un numero di riga; il punteggio più alto per ogni partizione è 1, quindi 2 ecc.

Quindi, se si desidera il TOP 3 di ciascun gruppo, selezionare solo quelle righe dal CTE che hanno un RowNum di 3 o meno (1, 2, 3) -> ecco qua!

+0

ROW_NUMBER() OVER (PARTITION BY hair ORDER BY score DESC) come 'RowNum') il bracket in questa linea non è bilanciato. è compatibile con la grammatica db2 sql? – zinking

+0

@zinking: grazie - c'era un parente di chiusura troppo alto .. risolto! Non so se DB2 supporta questo (non so DB2 abbastanza) - ma è sicuramente un costrutto standard ANSI/ISO SQL - non una caratteristica inventata da Microsoft :-) –

+1

Merda, questo ha reso la mia giornata! Che introduzione alle CTE! –

0

Il modo in cui l'algoritmo ottiene il rango consiste nel contare il numero di righe nel prodotto incrociato con un punteggio uguale o maggiore della ragazza in questione, al fine di generare un rango. Quindi nel caso problema si sta parlando, la griglia di Sarah sarebbe simile

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

e allo stesso modo per Deborah, che è il motivo per cui entrambe le ragazze ottengono un rango di 2 qui.

Il problema è che quando c'è un pareggio, tutte le ragazze prendono il valore minimo più basso nell'intervallo legato a causa di questo conteggio, quando invece si desidera che assumano il valore più alto. Penso che un semplice cambiamento possa risolvere questo problema:

Invece di un confronto più o meno uguale, utilizzare un confronto più stretto rispetto al numero delle ragazze che sono strettamente migliori. Quindi, aggiungi uno a quello e ottieni il tuo grado (che si occuperà dei legami appropriati). Quindi la selezione interna sarebbe:

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 

Qualcuno può vedere problemi con questo approccio che sono sfuggiti alla mia comunicazione?

+0

Non funziona in quadratic time? – b0fh

0

Utilizzare questo composto selezionare che gestisce problema OP correttamente

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) 

Si noti che è necessario utilizzare IFNULL qui per gestire caso in cui tavolo ragazze ha meno righe per un certo tipo di capelli poi vogliamo vedi in sql answer (nel caso OP è 3 articoli).

Problemi correlati