2011-01-26 13 views
10

Ho una tabella simile alla seguente:MySql elemento secondo più piccolo in ciascun gruppo

date | expiry 
-------------------------  
2010-01-01 | 2010-02-01 
2010-01-01 | 2010-03-02 
2010-01-01 | 2010-04-04 
2010-02-01 | 2010-03-01 
2010-02-01 | 2010-04-02 

Nella tabella, ogni data può avere più valori 'scadenza. Ho bisogno di una query che restituisca la n-esima scadenza più bassa in ogni data. Ad esempio, per n = 2, mi sarei aspettato:

 date | expiry 
-------------------------  
2010-01-01 | 2010-03-02 
2010-02-01 | 2010-04-02 

Il mio problema è che per quanto ne so, non v'è alcuna funzione di aggregazione che restituisce l'n-esimo elemento più grande/piccolo, quindi non posso usare 'GROUP BY '. Più in particolare, se ho avuto un aggregato magica MIN(), che accetta un secondo parametro 'compensare', avrei scritto:

SELECT MIN(expiry, 1) FROM table WHERE date IN ('2010-01-01', '2010-02-01') GROUP BY date 

Qualche suggerimento?

+0

È necessario eseguire assolutamente all'interno di una singola query? È particolarmente difficile perché MySQL non supporta le clausole 'LIMIT' all'interno delle sottoquery. Potrebbe essere più semplice selezionare tutto e calcolare il record che si desidera al di fuori del database. –

+0

@Chad Birch. Se non ho scelta, farò come suggerito, ma ritengo che il requisito sia abbastanza semplice e utile per poterlo fare con una sola query MySql. Potrei sbagliarmi, difficile :-) – bavaza

+0

Contrassegnato con "greatest-n-per-group".Alcune delle risposte hanno un modo generale di gestire questa caratteristica mancante in MySQL usando trucchi intelligenti; quelli che generano un gruppo completo dovrebbero essere selezionabili. Buona fortuna a trovare il codice magico. –

risposta

9

Una trucco è quello di utilizzare group_concat. Raggruppa per data e concat la data di scadenza in ordine crescente e utilizza la funzione substring_index per recuperare il valore n.

mysql> select * from expiry; 
+------------+------------+ 
| date  | expiry  | 
+------------+------------+ 
| 2010-01-01 | 2010-02-01 | 
| 2010-01-01 | 2010-03-02 | 
| 2010-01-01 | 2010-04-04 | 
| 2010-02-01 | 2010-03-01 | 
| 2010-02-01 | 2010-04-02 | 
+------------+------------+ 
5 rows in set (0.00 sec) 

mysql> SELECT mdate, 
     Substring_index(Substring_index(edate, ',', 2), ',', -1) AS exp_date 
FROM (SELECT `date`    AS mdate, 
       GROUP_CONCAT(expiry order by expiry asc separator ",") AS edate 
     FROM expiry 
     GROUP BY mdate) e1; 
+------------+------------+ 
| mdate  | exp_date | 
+------------+------------+ 
| 2010-01-01 | 2010-03-02 | 
| 2010-02-01 | 2010-04-02 | 
+------------+------------+ 
2 rows in set (0.00 sec) 

Nell'esempio qui il sub-query pronunciato la seguente output:

+------------+----------------------------------+ 
| mdate  | edate       | 
+------------+----------------------------------+ 
| 2010-01-01 | 2010-02-01,2010-03-02,2010-04-04 | 
| 2010-02-01 | 2010-03-01,2010-04-02   | 
+------------+----------------------------------+ 

substring_index (EDATE, '', 2) va 2 elementi in avanti (per ennesimo elemento sostitutivo 2 per n) .

+------------+------------------------------+ 
| mdate  | substring_index(edate,',',2) | 
+------------+------------------------------+ 
| 2010-01-01 | 2010-02-01,2010-03-02  | 
| 2010-02-01 | 2010-03-01,2010-04-02  | 
+------------+------------------------------+ 

corriamo un'altra substring_index sull'uscita sopra per ottenere solo il 2 ° elemento (l'ultimo elemento del risultato intermedio) utilizzando substring_index (substring_index (EDATE, '', 2), '', - 1)

+------------+------------------------------------------------------+ 
| mdate  | substring_index(substring_index(edate,',',2),',',-1) | 
+------------+------------------------------------------------------+ 
| 2010-01-01 | 2010-03-02           | 
| 2010-02-01 | 2010-04-02           | 
+------------+------------------------------------------------------+ 

Se ci sono troppi valori per concat si potrebbe correre su value group_concat_max_len (default 1024, ma può essere impostato più alto).

AGGIORNAMENTO: L'SQL sopra riportato fornisce l'ennesimo elemento anche quando sono presenti meno n elementi per il gruppo. Per evitare che sql possa essere modificato come:

SELECT mdate, 
     IF(cnt >= 2,Substring_index(Substring_index(edate, ',', 2), ',', -1),NULL) AS exp_date 
FROM (SELECT `date`    AS mdate, 
       count(expiry) as cnt, 
       GROUP_CONCAT(expiry order by expiry asc separator ",") AS edate 
     FROM expiry 
     GROUP BY mdate) e1; 
0

Suggerisco di prendere il valore n e usarlo per controllare la dimensione del reso. Per esempio, dicono che si voleva il terzo valore più basso ... cosa si sta effettivamente dopo è il più grande valore dai 3 valori inferiori

Quindi sarebbe TOP 1 DA (TOP n ORDER BY col ASC)

EDIT: come notato nei commenti di @Chad Birch, questo approccio può essere problematico se non si è in grado di utilizzare LIMIT all'interno delle sottoquery.

EDIT2: Ecco un interessante soluzione utilizzando JOIN s con LIMIT http://lists.mysql.com/mysql/211239

+0

L'equivalente di MySQL di 'TOP' di MSSQL è una clausola' LIMIT', ma non supporta questo nelle sottoquery, quindi questa non è un'opzione se deve essere eseguita in una query. –

Problemi correlati