选择组中的第一个和最后一个值
Posted
技术标签:
【中文标题】选择组中的第一个和最后一个值【英文标题】:Selecting first and last values in a group 【发布时间】:2012-12-07 01:58:46 【问题描述】:我有一个 mysql 表,其中包含每日股票报价(开盘价、最高价、最低价、收盘价和成交量),我试图将其动态转换为每周数据。到目前为止,我有以下函数,它适用于高点、低点和音量:
SELECT MIN(_low), MAX(_high), AVG(_volume),
CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
FROM mystockdata
GROUP BY myweek
ORDER BY _date;
我需要在上述查询中选择 _open 的第一个实例。因此,例如,如果星期一(在特定的一周内)有假期并且股票市场在星期二开市,则 _open 值应该从分组到其周的星期二中选择。同样,收盘价应该是该周的最后一个_close。
是否可以在 MySql 中选择类似 FIRST() 和 LAST() 的内容,以便将上述内容包含在单个 SELECT 中,而不是使用嵌套的选择查询?
这是我的表的创建语句,以了解架构:
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$$
更新:没有空值,只要有假期/周末,该表就没有该日期的任何记录。
【问题讨论】:
如您所知,您所描述的FIRST
和LAST
在MySQL 中不存在。一个查询可能是可能的,但这取决于您的表架构……更新您的帖子。
谢谢,杰森。请在上面查看我的编辑。
***.com/questions/2739474/…
【参考方案1】:
如果您使用的是 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
;
另一种解决方案是在SELECT
子句中使用带有LIMIT 1
的子查询:
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
;
请注意,我在myweek
中添加了LPAD() 字符串函数,以使周数始终为两位数,否则将无法正确排序周数。
同时使用 substring_index 和 group_concat() 时也要小心:如果分组字符串之一包含逗号,函数可能不会返回预期结果。
【讨论】:
谢谢,fthiella。使用GROUP_CONCAT
的第一种方法可以根据需要完美运行!
美丽。这将我的执行时间从 12 分钟缩短到 12 秒。谢谢!
这个查询(就像 OP 的查询,顺便说一句)在 MySQL 的严格模式打开(或简单的 ONLY_FULL_GROUP_BY
)下不起作用。 _date
列在GROUP_CONCAT
的ORDER BY
子句以及SELECT
查询的ORDER BY
子句中都未定义。
@LukasEder 我已经更新了我的答案,因为最好的解决方案是利用 MySQL8 中现在可用的 windows 功能。感谢您的评论和回答
@LukasEder 谢谢你,这也绝对是一个错误。我已经更新了我的答案(当 MySQL8+ 不可用时它可能仍然有用),我没有使用派生表,但应该修复它。最好在周数上使用 lpad 以便正确排序周。现在应该没问题了:)【参考方案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;
【讨论】:
【参考方案3】:基本上,你需要做什么:
-
按 PRODUCTID 分组
在每个组中,按位置排序
为 LOCATION 所订购的相同产品选择第一价格
将它们放在一起,您可以使用以下查询:
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()。
【讨论】:
OP 的问题中不存在列/表【参考方案4】:您可能需要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() 值,我只能想到一个非常老套的解决方案,那就是使用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
字段中没有值的日期为空。
【讨论】:
以上是关于选择组中的第一个和最后一个值的主要内容,如果未能解决你的问题,请参考以下文章