【问题标题】:Get row before SELECT query在 SELECT 查询之前获取行
【发布时间】:2014-04-30 06:44:21
【问题描述】:

我有一个名为“mytable”的表。列是

Time_Stamp (datetime) PK
Time_stamp_ms (int) PK
data1 (int)
data2 (int)
data3 (int)
data4 (int) 
data5 (int)
data6 (int)
cycle (int)
name (varstring)

我想按 Time_Stamp 和 Time_stamp_ms 排序(我知道如何从另一个问题做到这一点)然后每个时间周期达到 1,我想从上一行获取 Time_Stamp 和 Time_Stamp_ms。 Cycle 是 1,2,3,4......n 表示它总是递增 1。

这个表可能会有数百万行。

也没有 PHP。

我的桌子有一个样本:

Time_Stamp              Time_Stamp_ms   d1      d2      d3      d4      d5      d6      cycle   name    

2014-04-24 09:09:37         765         5555    4444    3333    2222    1111    123     1       name
2014-04-24 09:09:37         845         5555    4444    3333    2222    1111    123     2       name
2014-04-24 09:09:37         925         5555    4444    3333    2222    1111    123     3       name
2014-04-24 09:09:38         5           5555    4444    3333    2222    1111    123     4       name
2014-04-24 09:09:38         85          5555    4444    3333    2222    1111    123     5       name
2014-04-24 09:09:38         165         5555    4444    3333    2222    1111    123     6       name
2014-04-24 09:09:38         245         5555    4444    3333    2222    1111    123     7       name
2014-04-24 09:09:38         325         5555    4444    3333    2222    1111    123     8       name
2014-04-24 09:09:38         405         5555    4444    3333    2222    1111    123     9       name
2014-04-24 09:09:38         485         5555    4444    3333    2222    1111    123     10      name
2014-04-24 09:09:38         565         5555    4444    3333    2222    1111    123     11      name
2014-04-24 09:09:38         645         5555    4444    3333    2222    1111    123     12      name
2014-04-24 09:09:38         725         5555    4444    3333    2222    1111    123     13      name
2014-04-24 09:09:38         805         5555    4444    3333    2222    1111    123     1       name
2014-04-24 09:09:38         885         5555    4444    3333    2222    1111    123     2       name
2014-04-24 09:09:38         965         5555    4444    3333    2222    1111    123     3       name
2014-04-24 09:09:39         45          5555    4444    3333    2222    1111    123     4       name
2014-04-24 09:09:39         125         5555    4444    3333    2222    1111    123     5       name
2014-04-24 09:09:39         205         5555    4444    3333    2222    1111    123     6       name
2014-04-24 09:09:39         285         5555    4444    3333    2222    1111    123     1       name
2014-04-24 09:09:39         365         5555    4444    3333    2222    1111    123     2       name
2014-04-24 09:09:39         445         5555    4444    3333    2222    1111    123     3       name
2014-04-24 09:09:39         525         5555    4444    3333    2222    1111    123     4       name
2014-04-24 09:09:39         605         5555    4444    3333    2222    1111    123     5       name
2014-04-24 09:09:39         685         5555    4444    3333    2222    1111    123     6       name
2014-04-24 09:09:39         765         5555    4444    3333    2222    1111    123     1       name
2014-04-24 09:09:39         845         5555    4444    3333    2222    1111    123     2       name
2014-04-24 09:09:39         925         5555    4444    3333    2222    1111    123     3       name

应该还给我:

    Time_Stamp              Time_Stamp_ms   d1      d2      d3      d4      d5      d6      cycle   name    


2014-04-24 09:09:38         725         5555    4444    3333    2222    1111    123     13      name
2014-04-24 09:09:39         205         5555    4444    3333    2222    1111    123     6       name
2014-04-24 09:09:39         685         5555    4444    3333    2222    1111    123     6       name

【问题讨论】:

  • 表没有主键吗?
  • 1.您没有主键。 2.数据的行顺序不应该重要。如果您希望对这些行进行排序,则需要一个字段来帮助解决此问题。 THEN,你可以要求某一行之前的行。
  • 例如,ID 和 value 都是主键。这不是我的实际表格,因为它有更多字段。我只是想展示一个更简单的例子。
  • 我用真实的生活情况更新了帖子。
  • 我添加了更多数据和更多示例。

标签: mysql


【解决方案1】:

正如 cmets 中所说,您确实需要一个指示行顺序的字段。 pkey int primary key auto_increment 字段并不能保证最新的行总是具有最大的 id,所以严格来说,这不是 100% 的时间。包含确切插入时间的列就可以了。

假设(我错误地知道)您的值字段是一个可以排序的字段,此查询将为您获取 id=1 之前的每一行。要获得正确的结果,请按顺序创建一个字段,并在两个 order by 子句中将 value 替换为该字段

更新查询:http://sqlfiddle.com/#!2/9cf7d1/1/0

SELECT Time_Stamp, Time_stamp_ms, cycle FROM (
    SELECT 
        COALESCE((@preVal=1), 0) AS afterOne, 
        m.*, 
        @preVal:=m.cycle  
    FROM mytable as m, 
    (SELECT @preVal:=NULL) AS d 
    ORDER BY Time_Stamp desc, Time_stamp_ms desc
) t 
WHERE afterOne = 1 
ORDER BY Time_Stamp, Time_stamp_ms;

一个附加说明。如果您正在处理一个大数据集,您可能会发现通过将内部查询插入到临时表中,索引 afterOne,然后选择最终结果可以显着提高性能。 MySQL 因子查询速度慢而臭名昭著。

PS。嗯,我现在看到我可能选错了,afterOne 真的意味着在升序之前。哦,好吧,反正它是一个占位符,可以命名任何有意义的东西。

【讨论】:

  • 您的查询给出了错误的结果。 ypu 可能需要改进。请在这里查看sqlfiddle.com/#!2/ffa4d5/1
  • 我知道,我说过确实如此,因为 value 字段不是可以排序的字段,并且包含有关如何修复结果的说明 - 创建一个可以可靠的附加列 (insert_time)排序与
  • 我的真实表确实有一个时间戳列和一个时间戳毫秒列。我只是出于示例目的发布此内容。
  • 太好了,那么实施应该很简单吧?
  • 查询确实需要一段时间。我正在做的这个测试数据库有 5000 行,但真正的数据库有数百万行。
【解决方案2】:

正如mcalex所说的

您没有主键。 2.数据的行顺序不应该重要。如果您希望对这些行进行排序,则需要一个字段来帮助解决此问题。 THEN,你可以查询某行之前的那一行

试试这个

SELECT * from
(
  Select @prev As previous,@pid as `Previous id`,@pid := e.id As `id` ,@prev := e.value As current
  From
  (
    Select @prev := null,@pid := 0
  ) As i,tbl As e
) x
Where id=1 And Previous is not null;

Fiddle Demo


输出

+---------------------------------------------------+
|   PREVIOUS |  PREVIOUS_ID |  Current_ID | CURRENT |
+---------------------------------------------------+
|   C        |       3      |        1    |  D      |
|   F        |       3      |        1    |  G      |
|   X        |       4      |        1    |  J      |
+---------------------------------------------------+

【讨论】:

  • 对于这个表,ID和Value都是PK。我做这个是为了举例,只是为了一切。在我的实际表中,我有一个时间戳列和一个时间戳 ms 列作为我的 PK。
【解决方案3】:

我的第一选择可能是使用上述建议之一来生成序列号。但是,如果有大量记录构建这样的序列可能会很慢(尤其是如果您随后忽略更多记录)。

不过,另一种选择是加入。这更麻烦,因为您有 2 列来确定哪个是上一条记录。

未测试,但类似:-

SELECT a.*, b.Time_Stamp, b.Time_stamp_ms
FROM
(
    SELECT a.Time_Stamp, a.Time_stamp_ms, a.cycle, MAX(DATE_ADD(b.Time_Stamp, INTERVAL b.Time_stamp_ms MICROSECONDS)) AS latest_prev_record
    FROM mytable a
    INNER JOIN mytable b
    ON DATE_ADD(a.Time_Stamp, INTERVAL a.Time_stamp_ms MICROSECONDS) > DATE_ADD(b.Time_Stamp, INTERVAL b.Time_stamp_ms MICROSECONDS)
    WHERE a.cycle = 1
    GROUP BY a.Time_Stamp, a.Time_stamp_ms, a.cycle
) Sub1
INNER JOIN mytable a
ON a.Time_Stamp = Sub1.Time_Stamp, 
AND a.Time_stamp_ms = Sub1.Time_stamp_ms, 
AND a.cycle = Sub1.cycle
INNER JOIN mytable b
ON DATE_ADD(b.Time_Stamp, INTERVAL b.Time_stamp_ms MICROSECONDS) = Sub1.latest_prev_record

如果您只需要时间戳而不需要其他数据,并且如果您有一个组合的日期/时间/毫秒字段(那时您可以只使用子查询),这可以大大简化。如果您只是让所有记录都有一个连续的 id 字段(即,保证按该顺序),那就更容易了。

编辑 - 如果您只想在周期 1 之前返回最后一条记录,则简化:-

SELECT z.*
FROM
(
    SELECT a.Time_Stamp, a.Time_stamp_ms, MAX(DATE_ADD(b.Time_Stamp, INTERVAL b.Time_stamp_ms MICROSECOND)) AS latest_prev_record
    FROM mytable a
    INNER JOIN mytable b
    ON DATE_ADD(a.Time_Stamp, INTERVAL a.Time_stamp_ms MICROSECOND) > DATE_ADD(b.Time_Stamp, INTERVAL b.Time_stamp_ms MICROSECOND)
    WHERE a.cycle = 1
    GROUP BY a.Time_Stamp, a.Time_stamp_ms
) Sub1
INNER JOIN mytable z
ON DATE_ADD(z.Time_Stamp, INTERVAL z.Time_stamp_ms MICROSECOND) = Sub1.latest_prev_record

再次编辑。

您可以为组合时间戳添加一个小数字段(为其添加一个索引)并使用以下内容填充它:-

update `mytable` set `timestamp_full` =  UNIX_TIMESTAMP(`Time_Stamp`) + (`Time_stamp_ms` / 1000)

那么你可以使用下面的 SQL 来获取你想要的记录:-

SELECT z.*
FROM
(
    SELECT a.timestamp_full, MAX(b.timestamp_full) AS latest_prev_record
    FROM mytable a
    INNER JOIN mytable b
    ON a.timestamp_full > b.timestamp_full
    WHERE a.cycle = 1
    GROUP BY a.timestamp_full
) Sub1
INNER JOIN mytable z
ON z.timestamp_full = Sub1.latest_prev_record

【讨论】:

  • 由于程序读取日期/时间的方法,日期/时间必须在oe列中,毫秒必须在另一个列中。
  • 我添加了更多数据和更多示例。
  • 修改了我的答案以符合您的要求。简化了一点。但是,鉴于您的数据使用其他人建议的序列可能会更快。取决于您是否可以修改表(如果您可以添加一个包含日期/时间/毫秒组合的索引的额外列,可能通过触发器更新,那么这将大大改善此查询),以及如何每个 1 的循环有很多循环。
  • 我可以添加一个“Time_Stamp_Combined”列,但这需要是一个字符串,因为我使用的 MySQL 版本是 5.5(或更低)
  • 可能会有所帮助。您可以将其存储为包含日期/时间的 unix 时间戳加上毫秒作为小数点后部分的小数。
【解决方案4】:

如果您正在使用的只是一张小表(少于 10.000 行),我认为最好的解决方案是获取整个数据并“手动”选择行(= 在 PHP 循环中)。考虑到您只获取 ID 和主键来选择结果行,它肯定会比任何基于 SQL 的解决方案快得多。

说到严格基于 SQL 的解决方案,您需要一个存储过程和一个游标来沿着结果集单步执行(这可以让您后退一步)——但这不是很有效,因为您需要查询整个表和一一进行匹配。 基于索引的查询无法做到这一点,因此您获得的任何 SQL 解决方案都会遍历整个表(执行“完整扫描”),因此不会很快。

是的,前面的答案是正确的,因为行的顺序无关紧要。它们有点“随机”,或者至少您应该像看待它们一样看待它们。 (即使您执行 ALTER TABLE ... ORDER BY,您也无法确定在修改单行的下一个操作之后。)

【讨论】:

  • 这张表会很大。数百万行。
  • 好吧。告诉我为什么你需要这个前排的东西,所以我可以提供帮助。
  • 我需要它,因为我需要知道一个完整的周期何时结束,显然它何时开始一个新的周期 (1)。这是针对工业类型的情况。
  • 请再解释一下。在这种情况下,什么是“循环”?你围着桌子转吗?你的行按什么顺序排列有关系吗?您如何确保它们的正确顺序?您有订购的钥匙吗?
  • 好的,我现在看到你的更新了。在这种情况下,您正在寻找每次传递中的最高循环值,其中一次传递似乎是通过时间戳彼此接近的后续行。对吗?
【解决方案5】:

由于它变得越来越复杂,因此作为另一个答案发布,这更像是通过一个额外的选项进行讨论。

如果有一个索引列来检查最新记录,或者检查最新的一组循环,那么进行连接会更容易。

如果您为循环编号添加一列,那么您最初可以使用以下内容填充它:-

SET @cycle_no = 0;
UPDATE mytable
SET cycle_no=@cycle_no:=@cycle_no + 1
WHERE cycle = 1
ORDER BY time_stamp, time_stamp_ms;

然后

UPDATE mytable a
SET a.cycle_no = (SELECT MAX(b.cycle_no) FROM mytable b WHERE a.time_stamp < b.time_stamp OR (a.time_stamp a.time_stamp < b.time_stamp  b.time_stamp AND a.time_stamp_ms < b.time_stamp_ms ))
WHERE a.cycle != 1

第一个填充每个循环 1 的 cycle_no,第二个填充所有其他行的 cycle_no 值

您可以使用以下触发器填充它(可能有更有效的方法)。

CREATE TRIGGER insert_mytable
BEFORE INSERT ON mytable
FOR EACH row
SET NEW.cycle_no = IF(NEW.cycle = 1, (SELECT MAX(cycle_no) + 1 FROM mytable WHERE cycle = 1 ), (SELECT MAX(cycle_no) FROM mytable WHERE cycle = 1 ));

然后您可以像这样获取最新值(这依赖于 cycle_no 仅递增 1):-

SELECT z.*
FROM
(
    SELECT b.cycle_no, MAX(b.cycle)
    FROM mytable a
    INNER JOIN mytable b
    ON b.cycle_no = (a.cycle_no - 1)
    WHERE a.cycle = 1
    GROUP BY b.cycle_no
) Sub1
INNER JOIN mytable z
ON z.cycle_no = Sub1.cycle_no

在我敲出的测试数据(约 750 万条记录)上,这需要约 53 秒。不确定这是否对您有用。

【讨论】:

  • 我的循环从 1.....n(现在 n=250 虽然会改变)到现在的 cycle_id 填充了 1,1,1,1,1,1 ;2,2,2,2,2;3,3,3;4,4,4;等所有这些总共有 5813752 条,这是所有的总记录。我刚刚打电话给你做的那个查询,它又花了很长时间才回来......
  • 考虑到数据量,我想不出更快的方法。对不起。
  • 我认为使用带有日期和时间的 where 子句进行过滤可以加快速度......我会在我组织并正确解释答案后立即发布。
  • 过滤会提高速度,但只能通过减少您正在处理的数字来实现。如果您想要一天的记录(与您之前所说的相比大约 2m),它仍然会很慢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-03
  • 2017-07-23
  • 1970-01-01
相关资源
最近更新 更多