与 GROUP BY 结合的第一条记录
Posted
技术标签:
【中文标题】与 GROUP BY 结合的第一条记录【英文标题】:First record combined with GROUP BY 【发布时间】:2020-02-17 00:36:56 【问题描述】:假设我有一个包含字段的表“值” 标识(整数) 名称(varchar) 值(浮点数) 时间戳(整数)
现在我想计算整个值表中每个名称的最高最低值和第一个值(基于时间戳)。
这是否可以在一个单一的高性能查询中实现?我偶然发现了“first_value”函数,但那个函数似乎不起作用。我尝试了以下查询,使用连接,但也没有成功。
SELECT
a.name,
b.value as open,
MIN(a.value) as low,
MAX(a.value) as high
FROM values a
LEFT JOIN values b
ON a.name = b.name AND b.id = MIN(a.id)
GROUP BY a.name;
难道没有某种功能可以使类似的事情成为可能吗?
SELECT
name,
FIRST_VALUE(value) as open,
MIN(value) as low,
MAX(value) as high
FROM values
GROUP BY name
ORDER BY timestamp ASC;
示例数据
id name value timestamp
1 USD 3 16540
2 EUR 5 16540
3 GBP 4 16540
4 EUR 2 16600
5 USD 4 16600
6 GBP 5 16600
7 USD 6 16660
8 EUR 7 16660
9 GBP 6 16660
10 USD 5 16720
11 EUR 5 16720
12 GBP 7 16720
13 EUR 8 16780
14 USD 7 16780
15 GBP 8 16780
示例输出
name open low high
USD 3 3 7
EUR 5 2 8
GBP 4 4 8
我正在使用 mysql 客户端版本:5.6.39 平局应该是不可能的,如果是的话,我不在乎选择哪个值。
【问题讨论】:
您使用的是哪个版本的 MySQL? 您要做什么不是很清楚。您能否提供示例数据和预期结果来澄清您的问题? 即使在使用 ORDER BY 时定义“第一”,在值关联的情况下,排序也不会固定。此外,此 ORDER BY 对 ANSI/ISO SQL GROUP BY 规则无效。如@GMB建议我们需要查看示例数据和预期结果。见Why should I provide a Minimal Reproducible Example for a very simple SQL query? 你试过UNION命令吗? 我用示例数据编辑了帖子 【参考方案1】:如果您运行的是 MySQL 8.0,这可以通过窗口函数轻松解决:
select name, value open, low, high
from (
select
name,
value,
min(value) over(partition by name) low,
max(value) over(partition by name) high,
row_number() over(partition by name order by timestamp) rn
from mytable
) x
where rn = 1
Demo on DB Fiddle:
| name | open | low | high |
| ---- | ---- | --- | ---- |
| EUR | 5 | 2 | 8 |
| GBP | 4 | 4 | 8 |
| USD | 3 | 3 | 7 |
在早期版本中,您可以:
使用相关子查询过滤每个名称的第一条记录 使用聚合查询连接表,计算每个名称的最小值和最大值查询:
select
t.name,
t.value open,
t0.low,
t0.high
from
mytable t
inner join (
select name, min(value) low, max(value) high from mytable group by name
) t0 on t0.name = t.name
where t.timestamp = (
select min(t1.timestamp) from mytable t1 where t1.name = t.name
);
Demo on MySQL 5.6 DB Fiddle:与上述结果相同
这也可以使用内联子查询来实现(实际上可能性能更好):
select
t.name,
t.value open,
(select min(value) from mytable t1 where t1.name = t.name) low,
(select max(value) from mytable t1 where t1.name = t.name) high
from
mytable t
where timestamp = (
select min(t1.timestamp) from mytable t1 where t1.name = t.name
)
Demo on MySQL 5.6 DB Fiddle
【讨论】:
我使用的是 5.6.39 版本。也许我应该考虑简单地更新我的 mysql。 @GillesLesire:是的,你应该考虑升级:MySQL 5.6 于 2013 年发布,自 2018 年起不再支持......无论如何,我用这个旧版本的解决方案更新了我的答案。 @GillesLesire:我的(更新的)答案是否正确回答了您的问题? 是的,但我暂时找到了一个性能更高的解决方案。我目前正在考虑将我的 mysql 版本升级到版本 8,看看您的解决方案是否能提供更好的结果。【参考方案2】:在一个单一的高性能查询中
按逻辑执行,让 DBMS 担心性能。如果这还不够快,请检查您的索引。
与第一个时间戳关联的值需要连接。您可以很容易地找到第一个时间戳。从与给定行关联的行中获取值:这就是连接的用途。
所以,我们有:
SELECT
name,
value as open,
v1.low
v1.high
FROM values as v join (
select name,
min(timestamp) as timestamp,
min(value) as low,
max(value) as high
FROM values
GROUP BY name
) as v1
on v.name = v1.name and v.timestamp = v1.timestamp
【讨论】:
我假设“vi”是一个错字,应该是“v1”? 子查询也需要一个 FROM 语句才能使其工作。【参考方案3】:此解决方案似乎具有最佳性能。
SELECT
name,
CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(value AS CHAR) ORDER BY TIMESTAMP ASC), ',', 1) AS DECIMAL(10, 6)) AS open,
MIN(value) AS low,
MAX(value) AS high
FROM mytable
GROUP BY name
ORDER BY name ASC
【讨论】:
以上是关于与 GROUP BY 结合的第一条记录的主要内容,如果未能解决你的问题,请参考以下文章
php mysql Group By获取最新记录,而不是第一条记录
SQL重复记录查询-count与group by having结合查询重复记录
MSSQL 分组后取每组第一条(group by order by)