2011-01-26 1 views
10

У меня есть таблица, подобный следующему:MySql второй наименьший элемент в каждой группе

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 

В таблице, каждая дата может иметь несколько значений «истечением срока действия». Мне нужен запрос, который возвращает n-е наименьшее количество истечений в каждую дату. Например, при п = 2, я бы ожидать:

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

Моя беда в том, что AFAIK, нет агрегатная функция, которая возвращает n'th самый большой/маленький элемент, поэтому я не могу использовать «GROUP BY ». Более конкретно, если бы я имел волшебную МИН() агрегат, который принимает второй параметр «смещение», я хотел бы написать:

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

Есть предложения?

+0

Абсолютно необходимо выполнить внутри одного запроса? Это особенно сложно, потому что MySQL не поддерживает предложения 'LIMIT' внутри подзапросов. Это может оказаться самым простым, просто выбрать все и разработать, какую запись вы действительно хотите за пределами базы данных. –

+0

@Chad Birch. Если у меня нет выбора - я сделаю то, что вы предложили, но я считаю, что это требование просто и полезно для меня, чтобы иметь возможность сделать это с помощью одного запроса MySql. Возможно, я ошибаюсь, жестко :-) – bavaza

+0

С меткой «наибольший-на-группу».В некоторых ответах есть общий способ справиться с этой недостающей функцией в MySQL, используя умные трюки; те, которые генерируют полный набор групп, должны выбираться против. Удачи в поиске магического кода. –

ответ

9

Один хак использовать GROUP_CONCAT. Группируйте по дате и укажите дату истечения срока в порядке возрастания и используйте функцию substring_index для получения 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) 

В примере здесь суб-запрос дает следующий результат:

+------------+----------------------------------+ 
| 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) проходит 2 элемента вперед (для п-го элемента замены 2 на п) ,

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

мы запустим еще SUBSTRING_INDEX на выводе выше, чтобы получить только 2-й элемент (последний элемент промежуточного результата) с использованием 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           | 
+------------+------------------------------------------------------+ 

Если есть слишком много значений для Concat вы можете запустить из group_concat_max_len значения (по умолчанию 1024, но могут быть установлены выше).

UPDATE: SQL, приведенный выше, даст n-й элемент, даже если для группы th меньше n элементов. Чтобы избежать этого, sql может быть изменен как:

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

Я предлагаю вам принять ваше значение n и использовать его для управления вашим размером возврата. Например, скажем, вы хотите третье самое низкое значение ... Что вы на самом деле после того, как это наибольшее значение из придонных 3 значений

Так было бы TOP 1 ОТ (TOP п ORDER BY Col ASC)

EDIT: как отмечено в комментариях @Chad Birch, этот подход может быть проблематичным, если вы не можете использовать LIMIT внутри подзапросов.

EDIT2: Вот интересный обходной путь с помощью JOIN с с LIMIT http://lists.mysql.com/mysql/211239

+0

Обоснование MySQL в «TOP» - это предложение «LIMIT», но оно не поддерживает это в подзапросах, поэтому это не вариант, если это нужно сделать в одном запросе. –