从 MySQL 中的时间戳排序表中按列选择第一个和最后一个匹配项

Posted

技术标签:

【中文标题】从 MySQL 中的时间戳排序表中按列选择第一个和最后一个匹配项【英文标题】:Select first and last match by column from a timestamp-ordered table in MySQL 【发布时间】:2016-12-15 14:12:56 【问题描述】:

堆栈溢出,

我需要你的帮助!

假设我在 mysql 中有一个如下所示的表:

-------------------------------------------------
 OWNER_ID | ENTRY_ID | VEHICLE | TIME | LOCATION
-------------------------------------------------
1|1|123456|2016-01-01 00:00:00|A
1|2|123456|2016-01-01 00:01:00|B
1|3|123456|2016-01-01 00:02:00|C
1|4|123456|2016-01-01 00:03:00|C
1|5|123456|2016-01-01 00:04:00|B
1|6|123456|2016-01-01 00:05:00|A
1|7|123456|2016-01-01 00:06:00|A
...
1|999|123456|2016-01-01 09:10:00|A
1|1000|123456|2016-01-01 09:11:00|A
1|1001|123456|2016-01-01 09:12:00|B
1|1002|123456|2016-01-01 09:13:00|C
1|1003|123456|2016-01-01 09:14:00|C
1|1004|123456|2016-01-01 09:15:00|B
...

请注意,表模式只是编造的,所以我可以解释一下 我正在努力完成什么......

假设从 ENTRY_ID 6 到 999,LOCATION 列是“A”。我的应用程序需要的基本上是第 1-6 行,然后是第 1000 行。从第 7 行到第 999 行的所有内容都是不必要的数据,不需要进一步处理。我正在努力做的是要么忽略这些行,而不必将数据处理移动到我的应用程序中,或者更好的是,删除它们。

我对此摸不着头脑,因为:

1) 我不能按 LOCATION 排序然后只取第一个和最后一个条目,因为时间顺序对我的应用程序很重要,这会丢失 - 例如,如果我以这种方式处理这些数据,我会以第 1 行和第 1000 行结束,丢失第 6 行。

2) 我不希望将这些数据的处理转移到我的应用程序中,这些数据对我的要求来说是多余的,如果我可以避免的话,保留它根本没有意义。

鉴于上述示例数据,一旦我有了解决方案,我想最终得到的结果是:

-------------------------------------------------
 OWNER_ID | ENTRY_ID | VEHICLE | TIME | LOCATION
-------------------------------------------------
1|1|123456|2016-01-01 00:00:00|A
1|2|123456|2016-01-01 00:01:00|B
1|3|123456|2016-01-01 00:02:00|C
1|4|123456|2016-01-01 00:03:00|C
1|5|123456|2016-01-01 00:04:00|B
1|6|123456|2016-01-01 00:05:00|A
1|1000|123456|2016-01-01 09:11:00|A
1|1001|123456|2016-01-01 09:12:00|B
1|1002|123456|2016-01-01 09:13:00|C
1|1003|123456|2016-01-01 09:14:00|C
1|1004|123456|2016-01-01 09:15:00|B
...

希望我在这里说得通,不要遗漏一些明显的东西!

@Aliester - 有没有办法确定一行不需要 从该行中包含的数据处理?

很遗憾没有。

@O.琼斯 - 听起来你希望确定最早和 表中每个不同的 ENTRY_ID 值的最新时间戳, 然后从与那些匹配的表中检索详细信息行 时间戳。那是对的吗?您的 ENTRY_ID 值是唯一的吗?他们是 保证按时间升序排列?您的查询可以进行 如果这是真的,更便宜。请,如果您有时间,请编辑您的问题 澄清这些观点。

我正在尝试查找某个位置的到达时间,然后是该位置的出发时间。是的,ENTRY_ID 是一个唯一字段,但您不能认为较早的 ENTRY_ID 将等于较早的时间戳 - 传入数据是从车辆上的 GPS 单元发送的,不一定按照它们发送的顺序进行处理到网络限制。

【问题讨论】:

有没有办法根据该行中包含的数据确定该行不需要处理? 听起来您希望为ENTRY_ID 的每个不同值确定表中最早和最新的时间戳,然后从表中检索与这些时间戳匹配的详细信息行。那是对的吗?您的 ENTRY_ID 值是否独一无二?它们是否保证按时间升序排列?如果这是真的,您的查询可以变得更便宜。如果您有时间,请edit 澄清这些问题。 进一步澄清。在您的第一个示例中,是什么让第 1 行和第 6 行与您要保留的数据集相关,以及是什么让第 7 行与该数据集无关? @O.Jones,想想火车和铁路时刻表 :) 我认为这是运输。但是,又是什么让第 7 行不相关而第 1 行和第 6 行都相关?他们似乎都提到了位置A 【参考方案1】:

这是在 SQL 中解决的一个棘手问题,因为 SQL 是关于数据集,而不是数据序列。这在 MySQL 中更加棘手,因为其他 SQL 变体具有合成 ROWNUM 函数,而 MySQL 截至 2016 年底还没有。

这里需要两组数据的并集。

    在位置更改之前的数据库行集。 位置更改后的行集。

为此,您需要从生成所有行的子查询开始,按VEHICLE 然后TIME 排序,并带有行号。 (http://sqlfiddle.com/#!9/6c3bc7/2/0) 请注意,Sql Fiddle 中的示例数据与您的示例数据不同。

       SELECT (@rowa := @rowa + 1) rownum,
               loc.*
          FROM loc
          JOIN (SELECT @rowa := 0) init
         ORDER BY VEHICLE, TIME

然后您需要自联接该子查询,使用 ON 子句排除同一位置的连续行,并在位置更改之前获取行。比较连续的行由ON ... b.rownum = a.rownum+1 完成。就是这个查询。 (http://sqlfiddle.com/#!9/6c3bc7/1/0)

SELECT a.*
FROM (
            SELECT (@rowa := @rowa + 1) rownum,
                   loc.*
              FROM loc
              JOIN (SELECT @rowa := 0) init
             ORDER BY VEHICLE, TIME
) a 
 JOIN (
             SELECT (@rowb := @rowb + 1) rownum,
                   loc.*
              FROM loc
              JOIN (SELECT @rowb := 0) init
             ORDER BY VEHICLE, TIME
 ) b   ON a.VEHICLE = b.VEHICLE
      AND b.rownum = a.rownum + 1
      AND a.location <> b.location

这个子查询的一个变体,你说SELECT b.*,在位置更改后立即获取行 (http://sqlfiddle.com/#!9/6c3bc7/3/0)

最后,您对这两个查询进行设置UNION,对其进行适当的排序,然后您的行集将删除重复的连续位置。请注意,这在 MySQL 中变得非常冗长,因为用于生成行号的讨厌的 @rowa := @rowa + 1 hack 必须在子查询的每个副本中使用不同的变量(@rowa@rowb 等)。 (http://sqlfiddle.com/#!9/6c3bc7/4/0)

SELECT a.*
  FROM (
        SELECT (@rowa := @rowa + 1) rownum,
               loc.*
          FROM loc
          JOIN (SELECT @rowa := 0) init
         ORDER BY VEHICLE, TIME
) a 
 JOIN (
         SELECT (@rowb := @rowb + 1) rownum,
               loc.*
          FROM loc
          JOIN (SELECT @rowb := 0) init
         ORDER BY VEHICLE, TIME
 ) b ON a.VEHICLE = b.VEHICLE AND b.rownum = a.rownum + 1  AND a.location <> b.location

 UNION

 SELECT d.*
  FROM (
        SELECT (@rowc := @rowc + 1) rownum,
               loc.*
          FROM loc
          JOIN (SELECT @rowc := 0) init
         ORDER BY VEHICLE, TIME
) c 
 JOIN (
         SELECT (@rowd := @rowd + 1) rownum,
               loc.*
          FROM loc
          JOIN (SELECT @rowd := 0) init
         ORDER BY VEHICLE, TIME
 ) d ON c.VEHICLE = d.VEHICLE AND c.rownum = d.rownum - 1  AND c.location <> d.location
 order by VEHICLE, TIME

而且,在下一代 MySQL 中,现在在 MariaDB 10.2 中提供测试版,这要容易得多。新一代作为通用表表达式和行编号。

 with loc as
     (
            SELECT  ROW_NUMBER() OVER (PARTITION BY VEHICLE ORDER BY time) rownum,
                   loc.*
              FROM loc
)

select a.* 
 from loc a
 join loc b ON a.VEHICLE = b.VEHICLE
           AND b.rownum = a.rownum + 1
           AND a.location <> b.location
 union 
select b.* 
 from loc a
 join loc b ON a.VEHICLE = b.VEHICLE
           AND b.rownum = a.rownum + 1
           AND a.location <> b.location
order by vehicle, time

【讨论】:

以上是关于从 MySQL 中的时间戳排序表中按列选择第一个和最后一个匹配项的主要内容,如果未能解决你的问题,请参考以下文章

使用 ActiveRecord 语法从 Rails 中的连接表中选择或按列排序

在C#中按列对二维数组进行排序

如何在 Laravel 的嵌套 2 级关系中按列排序?

如何在pyspark中按列合并多个数据框?

在每个表,mysql,经典asp中按相同字段排序2个表

窗口函数,尝试从连接表中的列中按 created_at 排序而不分组