选择组中的第一个和最后一个值

时间:2012-12-19 16:45:22

标签: mysql select group-by

我有一个MySql表,包含每日股票报价(开盘价,最高价,最低价,收盘价和成交量),我试图将其转换为每周数据。到目前为止,我有以下功能,适用于高,低和音量:

SELECT MIN(_low), MAX(_high), AVG(_volume),
CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
FROM mystockdata
GROUP BY myweek
ORDER BY _date;

我需要在上面的查询中选择_open的第一个实例。因此,例如,如果星期一(在特定的一周)有假期并且星期二开放股票市场,则应该从星期二开始选择_开值,该星期二分组为其周。同样,close值应该是该周的最后一个_close。

是否可以在MySql中选择类似FIRST()和LAST()的内容,以便上述内容可以包含在单个SELECT中,而不是使用嵌套的选择查询?

这是我的表的create语句,用于了解模式:

delimiter $$
CREATE TABLE `mystockdata` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `symbol_id` int(11) NOT NULL,
  `_open` decimal(11,2) NOT NULL,
  `_high` decimal(11,2) NOT NULL,
  `_low` decimal(11,2) NOT NULL,
  `_close` decimal(11,2) NOT NULL,
  `_volume` bigint(20) NOT NULL,
  `add_date` date NOT NULL,
  PRIMARY KEY (`id`),
  KEY `Symbol_Id` (`symbol_id`,`add_date`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8$$

更新:没有空值,无论哪里有假期/周末,该表都没有该日期的任何记录。

4 个答案:

答案 0 :(得分:32)

如果您使用的是MySQL 8,那么首选的解决方案将使用现在可用的窗口函数FIRST_VALUE()和/或LAST_VALUE()。请查看Lukas Eder's answer

但是如果您使用的是旧版本的MySQL,那些功能则不然 支持的。你必须使用某种变通方法来模拟它们, 例如,您可以使用聚合字符串函数GROUP_CONCAT(),该函数为_open_close订购的一周的所有_date_open值创建一组并_date desc_close,并提取集合的第一个元素:

select
  min(_low),
  max(_high),
  avg(_volume),
  concat(year(_date), "-", lpad(week(_date), 2, '0')) AS myweek,
  substring_index(group_concat(cast(_open as CHAR) order by _date), ',', 1 ) as first_open,
  substring_index(group_concat(cast(_close as CHAR) order by _date desc), ',', 1 ) as last_close
from
  mystockdata
group by
  myweek
order by
  myweek
;

另一种解决方案是在LIMIT 1子句中使用SELECT的子查询:

select
  min(_low),
  max(_high),
  avg(_volume),
  concat(year(_date), "-", lpad(week(_date), 2, '0')) AS myweek,
  (
    select _open
    from mystockdata m
    where concat(year(_date), "-", lpad(week(_date), 2, '0'))=myweek
    order by _date
    LIMIT 1
  ) as first_open,
  (
    select _close
    from mystockdata m
    where concat(year(_date), "-", lpad(week(_date), 2, '0'))=myweek
    order by _date desc
    LIMIT 1
  ) as last_close
from
  mystockdata
group by
  myweek
order by
  myweek
;

请注意我已将LPAD()字符串功能添加到myweek,以使周数始终为两位数字,否则将无法正确订购周数。

将substring_index与group_concat()结合使用时也要小心:如果其中一个分组的字符串包含逗号,则该函数可能不会返回预期的结果。

答案 1 :(得分:2)

从MySQL 8开始,您最好使用window functions来完成任务:

WITH 
  t1 AS (
    SELECT _low, _high, _volume, CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
    FROM mystockdata
  ),
  t2 AS (
    SELECT 
      t1.*, 
      FIRST_VALUE(_open) OVER (PARTITION BY myweek ORDER BY _date) AS first_open,
      FIRST_VALUE(_close) OVER (PARTITION BY myweek ORDER BY _date DESC) AS last_close
    FROM t1
  )
SELECT MIN(_low), MAX(_high), AVG(_volume), myweek, MIN(first_open), MAX(last_close)
FROM t2
GROUP BY myweek
ORDER BY myweek;

答案 2 :(得分:1)

您可能需要COALESCE函数才能获得第一个值。但是,在没有数据的情况下,您需要确保没有数据的日期(周末和假日)对_open具有空值。

用法是:

SELECT MIN(_low), MAX(_high), AVG(_volume), COALESCE(_open)
CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
FROM mystockdata
GROUP BY myweek
ORDER BY _date;

对于last()值,我只能想到一个非常hacky的解决方案,即使用GROUP_CONCAT然后使用字符串操作来获取列表中的最后一个值。所以也许是这样的:

SELECT MIN(_low), MAX(_high), AVG(_volume), COALESCE(_open), SUBSTRING_INDEX(GROUP_CONCAT(_close), ',', -1)
CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
FROM mystockdata
GROUP BY myweek
ORDER BY _date;

注意,如果您想要一致的查询,也可以使用GROUP_CONCAT方法代替第一项而不是合并

SELECT MIN(_low), MAX(_high), AVG(_volume), SUBSTRING_INDEX(GROUP_CONCAT(_open), ',', 1), SUBSTRING_INDEX(GROUP_CONCAT(_close), ',', -1)
CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
FROM mystockdata
GROUP BY myweek
ORDER BY _date;

要使GROUP_CONCAT正常工作,您还需要确保_open_close字段中没有值的日期为空。

答案 3 :(得分:-1)

基本上,你需要做什么:

  1. group by PRODUCTID
  2. 在每个组内,按LOCATION排序
  3. 选择LOCATION
  4. 订购的同一产品的FIRST价格

    将它们放在一起,您可以使用以下查询:

    SELECT PRODUCTID, 
       SUBSTRING_INDEX(GROUP_CONCAT(CAST(LOCATION AS CHAR) ORDER BY LOCATION DESC), ',', 1) AS LOCATION,
       SUBSTRING_INDEX(GROUP_CONCAT(CAST(PRICE AS CHAR) ORDER BY LOCATION DESC), ',', 1) AS PRICE
    FROM ProductLocation
    GROUP BY PRODUCTID;
    

    请注意,MySQL没有GROUP BY的FIRST()和LAST()聚合函数,但可以使用GROUP_CONCAT()和SUBSTRING_INDEX()函数模拟这样的FIRST()和LAST()。