与 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结合查询重复记录

sql:用group by分组后,每组随意取一个记录?

MSSQL 分组后取每组第一条(group by order by)

sql如何取group by 分组的多条记录只取最上面的一条!

无需手动键入所有列即可从 group by 中获取一条记录