MySql每组中的第二小元素

时间:2011-01-26 20:06:38

标签: mysql group-by aggregate greatest-n-per-group

我有一张类似于以下内容的表格:

    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个最小到期的查询。例如,对于n = 2,我希望:

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

我的麻烦是AFAIK,没有聚合函数返回第n个最大/最小元素,所以我不能使用'GROUP BY'。更具体地说,如果我有一个神奇的MIN()聚合接受第二个参数'offset',我会写:

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

有什么建议吗?

2 个答案:

答案 0 :(得分:10)

一个hack是使用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个元素(对于第n个元素用2替换为n)。

+------------+------------------------------+
| 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,只使用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                                           |
+------------+------------------------------------------------------+

如果要连接的值太多,则可能会超出group_concat_max_len值(默认为1024,但可以设置得更高)。

更新:即使该组中的n个元素较少,上面给出的SQL也会给出第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;  

答案 1 :(得分:0)

我建议您使用n值并使用它来控制返回大小。 例如,假设您想要第三低的价值...... 你真正追求的是底部3值的最大值

所以它将是TOP 1 FROM(TOP n ORDER BY col ASC)

编辑:如@Chad Birch的评论中所述,如果您无法在子查询中使用LIMIT,则此方法可能会出现问题。

EDIT2: 以下是使用JOINLIMIT的有趣解决方法 http://lists.mysql.com/mysql/211239