【问题标题】:How to calculate the smallest period of time between consecutive events?如何计算连续事件之间的最小时间段?
【发布时间】:2010-10-28 10:05:03
【问题描述】:

假设我有一个包含记录的时间戳列的表,我想仅使用一个查询来计算两个连续记录之间的最小时间差。

也许像...这样的表

CREATE TABLE `temperatures` (
  `temperature` double,
  `time` timestamp DEFAULT CURRENT_TIMESTAMP
);

【问题讨论】:

    标签: sql mysql timestamp


    【解决方案1】:

    您需要的是分析函数LAGMIN

    MySQL 中缺少它们,但可以使用会话变量轻松模拟。

    此查询返回连续记录之间的所有差异:

    SELECT  (temperature - @r) AS diff,
            @r := temperature
    FROM    (
            SELECT  @r := 0
            ) vars,
            temperatures
    ORDER BY
            time
    

    这个返回最小时差:

    SELECT  (
            SELECT  id,
                    @m := LEAST(@m, TIMEDIFF(time, @r)) AS mindiff,
                    @r := time
            FROM    (
                    SELECT  @m := INTERVAL 100 YEAR,
                            @r := NULL
                    ) vars,
                    temperatures
            ORDER BY
                    time, id
            ) qo
    WHERE   qo.id = 
            (
            SELECT  id
            FROM    temperatures
            ORDER BY
                    time DESC, id DESC
            LIMIT 1
            )
    

    请参阅我的博客中的这篇文章,了解如何在MySQL 中模拟分析函数:

    如果您在表中添加PRIMARY KEY(您应该始终这样做!),那么您可以使用更多SQL-ish 解决方案:

    SELECT  temperature -
            (
            SELECT temperature
            FROM   temperatures ti
            WHERE  (ti.timestamp, ti.id) < (to.timestamp, to.id)
            ORDER BY
                   ti.timestamp DESC, ti.id DESC
            LIMIT 1
            )
    FROM    temperatures to
    ORDER BY
           to.timestamp, to.id
    

    但是,由于bug 20111,此解决方案在MySQL 中效率很低。

    子查询将不使用range 访问路径,但它将使用(timestampid)上的索引进行排序。

    这可以通过创建一个返回先前温度的UDF 来解决,给定当前记录的id

    详情请看我博客中的这篇文章:

    如果你不使用任何过滤条件,那么使用会话变量的解决方案将是最有效的,尽管MySQL 是特定的。

    SQL Server 的类似解决方案如下所示:

    SELECT  temperature -
            (
            SELECT TOP 1 temperature
            FROM   temperatures ti
            WHERE  ti.timestamp < to.timestamp
                   OR (ti.timestamp = to.timestamp AND ti.id < to.id)
            ORDER BY
                   ti.timestamp DESC, ti.id DESC
            )
    FROM    temperatures to
    ORDER BY
           to.timestamp, to.id
    

    SELECT  MIN(mindiff)
    FROM    (
            SELECT  timestamp -
                    (
                    SELECT TOP 1 timestamp
                    FROM   temperatures ti
                    WHERE  ti.timestamp < to.timestamp
                           OR (ti.timestamp = to.timestamp AND ti.id < to.id)
                    ORDER BY
                           ti.timestamp DESC, ti.id DESC
                    ) AS mindiff
            FROM    temperatures to
            ORDER BY
                   to.timestamp, to.id
            ) q
    

    SQL Server 中,只要您在(timestamp, id) 上有一个索引(或者在(timestamp) 上,如果您的PRIMARY KEY 是集群的),这将正常工作

    【讨论】:

    • +1 非常好。在 Sql Server 上尝试过,但“为变量赋值的 SELECT 语句不能与数据检索操作结合使用。”
    • @Andomar:不,这是特定于 MySQL 的。 Oracle 对 LAG 具有本机支持,在 SQL Server 中,您需要使用子查询来模拟它。
    • Andomar 和 Lasse V. Karlsen 也是很好的答案,但对我来说,这个更干净,顺便说一句,感谢博文!
    【解决方案2】:

    假设时间戳有唯一性约束(防止同时有两个录音):

    SELECT MIN(timediff(t1.`time`, t2.`time`)) AS delta_t,
        FROM temperatures t1 JOIN temperatures t2 ON t1.`time` < t2.`time`
    

    这相当准确地回答了问题 - 并且没有传达其他有用的信息(例如哪两个时间戳或温度)。

    【讨论】:

    • 是什么让您认为@op 想要时差?温差在此查询中更有意义。
    【解决方案3】:

    尝试这样的查询:

    select 
        cur.timestamp as CurrentTime,
        prev.timestamp as PreviousTime,
        timediff(cur.timestamp,prev.timestamp) as TimeDifference,
        cur.temperature - prev.temperature as TemperatureDifference
    from temperatures cur
    left join temperatures prev on prev.timestamp < cur.timestamp
    left join temperatures inbetween
        on prev.timestamp < inbetween.timestamp
        and inbetween.timestamp < cur.timestamp
    where inbetween.timestamp is null
    

    第一个连接查找当前(“cur”)行的所有先前行。第二个连接查找第一行和第二行之间的行。 where 语句表示第一行和第二行之间不能有任何行。这样,您将获得一个行列表及其前一行。

    【讨论】:

    • 这会工作,但会很慢,并且不会正确处理重复的时间戳。
    • 您不需要应用于时差的 MIN() 函数和适当的 GROUP BY 子句吗?您不需要检查两者之间是否没有读数,因为如果有,第一个和第二个之间的时间差将小于第一个和第三个之间的时间差,因此 MIN() 将摆脱它。在其他情况下,您必须确保当前和以前之间没有中间读数,这会使事情变得复杂。
    • @Jonathan:INBETWEEN 有一个连接来执行此检查。
    【解决方案4】:

    你可以试试这个:

    SELECT
        T1.*,
        (SELECT MIN(T2.time)
         FROM temperatures T2
         WHERE T2.time > T1.time)-T1.time diff
    FROM
        temperatures T1
    ORDER BY
        T1.time
    

    【讨论】:

    • 在支持子查询的数据库中运行良好——在 MySQL 上测试大约 10,000 行需要很长时间 :( 还会为 diff 生成奇数值,例如 -20090500997993
    猜你喜欢
    • 2017-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多