如何为每个键值选择具有最新时间戳的行?

Posted

技术标签:

【中文标题】如何为每个键值选择具有最新时间戳的行?【英文标题】:How can I select rows with most recent timestamp for each key value? 【发布时间】:2013-06-24 00:05:27 【问题描述】:

我有一张传感器数据表。每行都有一个传感器 ID、一个时间戳和其他字段。我想为每个传感器选择一个带有最新时间戳的单行,包括其他一些字段。

我认为解决方案是按传感器 id 分组,然后按 max(timestamp) 排序,如下所示:

SELECT sensorID,timestamp,sensorField1,sensorField2 
FROM sensorTable 
GROUP BY sensorID 
ORDER BY max(timestamp);

这给了我一个错误,提示“sensorField1 必须出现在 group by 子句中或在聚合中使用。”

解决这个问题的正确方法是什么?

【问题讨论】:

你使用的是什么数据库引擎? 虽然下面在 Max(timestamp) 值上使用 JOIN 的答案应该有效,但如果您在 sensorTable 上有一个 SensorReadingId,我建议您加入 SensorReadingId。 【参考方案1】:

为了完整起见,这是另一种可能的解决方案:

SELECT sensorID,timestamp,sensorField1,sensorField2 
FROM sensorTable s1
WHERE timestamp = (SELECT MAX(timestamp) FROM sensorTable s2 WHERE s1.sensorID = s2.sensorID)
ORDER BY sensorID, timestamp;

我认为这很不言自明,但如果您愿意,here's 可以提供更多信息,以及其他示例。它来自 mysql 手册,但上面的查询适用于每个 RDBMS(实现 sql'92 标准)。

【讨论】:

【参考方案2】:

在 Postgres 中,这可以使用 SELECT DISTINCT 以相对优雅的方式完成,如下所示:

SELECT DISTINCT ON (sensorID)
sensorID, timestamp, sensorField1, sensorField2 
FROM sensorTable
ORDER BY sensorID, timestamp DESC;

更多信息here。我怀疑它也适用于其他 SQL 风格,但显然不是 MySQL(link - 感谢@silentsurfer 的提示)

如果不明显,它的作用是按传感器 ID 和时间戳(从最新到最旧)对表进行排序,然后返回每个唯一传感器 ID 的第一行(即最新时间戳)。

在我的用例中,我从 ~1K 传感器获得了 ~10M 读数,因此尝试在基于时间戳的过滤器上将表与自身连接起来非常耗费资源;以上需要几秒钟。

【讨论】:

这个解决方案真的很快。 快速且易于理解。也感谢您解释用例,因为我的用例非常相似。 不幸的是,这不适用于 MySQL (link)【参考方案3】:

您只能选择组中的列或聚合函数中使用的列。您可以使用联接来完成这项工作

select s1.* 
from sensorTable s1
inner join 
(
  SELECT sensorID, max(timestamp) as mts
  FROM sensorTable 
  GROUP BY sensorID 
) s2 on s2.sensorID = s1.sensorID and s1.timestamp = s2.mts

【讨论】:

...或select * from sensorTable where (sensorID, timestamp) in (select sensorID, max(timestamp) from sensorTable group by sensorID) 我认为“LEFT JOIN”也适用,而不仅仅是“INNER JOIN”;恕我直言,“and s1.timestamp = s2.mts”的一部分不是必需的。然而,我建议在两个字段上创建索引:sensorID+timestamp - 查询速度大大提高!【参考方案4】:

您可以将表与自身连接(在传感器 ID 上),并添加 left.timestamp < right.timestamp 作为连接条件。然后选择行,其中right.idnull。瞧,你得到了每个传感器的最新条目。

http://sqlfiddle.com/#!9/45147/37

SELECT L.* FROM sensorTable L
LEFT JOIN sensorTable R ON
L.sensorID = R.sensorID AND
L.timestamp < R.timestamp
WHERE isnull (R.sensorID)

但请注意,如果您有少量的 id 和许多值,这将非常耗费资源!因此,我不建议将其用于某种测量材料,因为每个传感器每分钟都会收集一个值。但是在用例中,您需要跟踪“有时”更改的某些内容的“修订”,这很容易。

【讨论】:

这比其他答案更快,至少在我的情况下。 @rain_ 这真的取决于用例。因此,这个问题没有“通用答案”。【参考方案5】:
WITH SensorTimes As (
   SELECT sensorID, MAX(timestamp) "LastReading"
   FROM sensorTable
   GROUP BY sensorID
)
SELECT s.sensorID,s.timestamp,s.sensorField1,s.sensorField2 
FROM sensorTable s
INNER JOIN SensorTimes t on s.sensorID = t.sensorID and s.timestamp = t.LastReading

八年后,这刚刚获得了支持,所以我需要指出这是旧方法。新方法使用row_number() 窗口函数或APPLY 横向连接。

【讨论】:

【参考方案6】:

我还没有在这里看到一个常见的答案,那就是窗口函数。如果您的数据库支持,它可以替代相关子查询。

SELECT sensorID,timestamp,sensorField1,sensorField2 
FROM (
    SELECT sensorID,timestamp,sensorField1,sensorField2
        , ROW_NUMBER() OVER(
            PARTITION BY sensorID
            ORDER BY timestamp
        ) AS rn
    FROM sensorTable s1
WHERE rn = 1
ORDER BY sensorID, timestamp;

我实际上比相关的子查询更多地使用它。随意在 cmets 中击败我的效率,我不太确定它在这方面是如何叠加的。

【讨论】:

【参考方案7】:

我遇到了几乎相同的问题,但最终得到了一个不同的解决方案,使得这类问题的查询变得微不足道。

我有一张传感器数据表(来自大约 30 个传感器的 1 分钟数据)

SensorReadings->(timestamp,value,idSensor)

我有一个传感器表,其中包含许多关于传感器的大部分静态内容,但相关字段如下:

Sensors->(idSensor,Description,tvLastUpdate,tvLastValue,...)

tvLastupdate 和 tvLastValue 在插入到 SensorReadings 表时的触发器中设置。我总是可以直接访问这些值,而无需进行任何昂贵的查询。这确实会稍微反规范化。查询很简单:

SELECT idSensor,Description,tvLastUpdate,tvLastValue 
FROM Sensors

我将这种方法用于经常查询的数据。在我的情况下,我有一个传感器表和一个大型事件表,其中包含分钟级别的数据,并且数十台机器正在使用该数据更新仪表板和图表。在我的数据场景中,trigger-and-cache 方法效果很好。

【讨论】:

【参考方案8】:

还想使用not exists 子句给出答案:

SELECT sensorID,timestamp,sensorField1,sensorField2 
FROM sensorTable t1
where not exists
( select * from sensorTable t2 where t1.sensorId=t2.sensorId
  and t1.timestamp < t2.timestamp );

根据您的 DBMS/SQL 优化器,这可能是一个高效且不错的选择。

【讨论】:

以上是关于如何为每个键值选择具有最新时间戳的行?的主要内容,如果未能解决你的问题,请参考以下文章

如何为每个 ID 获取具有 max(TIMESTAMP) 的行? [复制]

用于从表中选择具有最新时间戳的行的 JOOQ 代码

如何选择具有当天时间戳的行?

如何选择具有当天时间戳的行?

如何从具有最后时间戳的数据框中选择不同的记录

效率:删除具有相同时间戳的行,同时仍然具有该时间戳的第二列的中值