【问题标题】:how to delete every record except one per hour如何删除除每小时一条以外的每条记录
【发布时间】:2014-10-23 20:17:57
【问题描述】:

我有一个 mysql 表,其中包含数百万条传感器记录,结构如下:

datanumber (auto increment), 
stationid (int), 
sensortype (int),
measuredate (datetime),
data (medtext)

每个站点每 2-10 分钟添加一个记录,每种传感器类型(2-5 个传感器)

我希望每小时、每个传感器、每个站点只保留一条记录 并且仅当测量日期超过 1 年时也是如此。

我了解如何选择超过一年的数据,但我不知道如何删除除每小时一个之外的行。它是每小时保存的第一个、最后一个还是随机值并不重要。我也不需要计算平均值什么的,只需剥离存储的记录量

【问题讨论】:

  • 听起来你需要一个 cron 任务。你研究过它是如何工作的吗?

标签: mysql sql


【解决方案1】:

你应该能够做类似的事情

Select * from observations where <old> group by sensortype, stationid, extract(year_month, measure_date), extract(day_hour, measure_date);

group_by 会将每组中的记录折叠成一个。如果需要,您可以将其选择到新表中。

如果你需要实际删除所有多余的旧记录,只需使用上述查询选择数据编号,然后删除所有记录NOT IN(&lt;those ids&gt;)

【讨论】:

  • 我认为你必须假设 roadfox 确实想要删除“多余的旧”记录,因为这正是他说他想做的事情。此外,他不想删除任何小于或等于 1 年的记录,而上述建议的查询没有考虑到这一点。在任何情况下,听起来都会保留数千甚至数百万行,所以我倾向于怀疑 NOT IN + 子查询是否能够充分执行。
【解决方案2】:

如果您要删除大量行,那么 MySQL 文档推荐的一种方法是选择要保留到临时表中的行,然后执行原子表重命名。可能是这样的:

INSERT INTO
    sensordata_squeezed

  SELECT
      datanumber,
      stationid,
      sensortype,
      measuredate,
      data
    FROM
        sensordata
    WHERE
        measuredate < DATE_SUB(CURDATE(), INTERVAL 1 YEAR)
    GROUP BY
        DATE_ADD(DATE(measuredate), INTERVAL HOUR(measuredate) HOUR),
        stationid,
        sensortype

  UNION ALL

  SELECT
      datanumber,
      stationid,
      sensortype,
      measuredate,
      data
    FROM
        sensordata
    WHERE
        measuredate >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)
;

RENAME TABLE 
    sensordata TO sensordata_old,
    sensordata_squeezed TO sensordata
;

DROP TABLE sensordata_old
;

注意:这依赖于 MySQL 的记录行为,即从聚合查询中选择既不是分组列也不是组的聚合函数的列:它从每个组中选择一个不确定的值。 (这是对标准 SQL 的扩展。)我假设在每个组中,所有非聚合列值都来自同一行;您应该检查一下,因为该部分没有记录在案,而这种方法依赖于它来维护数据完整性。

这种方法可以让您避免大型、昂贵的连接和大量子查询。

请注意,无论您如何执行此操作,您都必须解决如何避免丢失此操作运行时传入的数据的问题,因为这可能需要很长时间。

【讨论】:

  • 我尝试在我的数据库上运行它,但它现在已经阻塞了一个多小时。因此,虽然在重命名表期间不接受新数据不是问题。我必须看看我是否可以在查询运行时提高性能
  • 看起来真的很有希望,首先我认为它会阻止数据库,但那是因为我在 phpmyadmin 中测试了查询,该查询被锁定了几个小时,如果我在命令行上运行它,所有其他查询仍然响应.但是运行查询和重命名它的切换时间至少在第一次运行时会成为一个问题。非常感谢您指出方向
【解决方案3】:

如果我们可以使用row_number over( ... ),这将是一个简单的问题,但 MySQL 的解决方案并不难。对于这样的问题,看看我们是否可以查询我们想要删除的行的列表。这听起来很容易。首先,我们想要列出每天的每个小时以及该小时的第一个(最少)条目:

select  Date( MeasureDate ) TheDate, Hour( MeasureDate ) TheHour, Min( MeasureDate ) MinTime
from    T
group by TheDate, TheHour;

所以我们只需要将表连接回这个结果集:

select  T.*
from    T
join(
  select  Date( MeasureDate ) TheDate, Hour( MeasureDate ) TheHour, Min( MeasureDate ) MinTime
  from    T
  group by TheDate, TheHour
) as T1
  on  T1.MinTime = T.MeasureDate

这为我们提供了我们想要保留的所有行。所以使用left join 来反转结果:

select  T.*
from    T
left join(
  select  Date( MeasureDate ) TheDate, Hour( MeasureDate ) TheHour, Min( MeasureDate ) MinTime
  from    T
  group by TheDate, TheHour
) as T1
  on  T1.MinTime = T.MeasureDate
where T1.MinTime is null;

select 更改为delete et viola:

delete  TDel
from    T TDel
left join(
  select  Date( MeasureDate ) TheDate, Hour( MeasureDate ) TheHour, Min( MeasureDate ) MinTime
  from    T
  group by TheDate, TheHour
) as T1
on  T1.MinTime = TDel.MeasureDate
where T1.MinTime is null;

您可以酌情添加其他字段,例如SensorType,以保留每个传感器每小时的第一次输入,或者您想要调整它。 SqlFiddle

【讨论】:

  • 很抱歉我的延迟回复并感谢您的解释,这是一个开始,我必须再摆弄一些才能获得不错的性能,因为目前它会阻塞数据库几分钟。但我现在知道如何做到这一点,非常感谢!
猜你喜欢
  • 1970-01-01
  • 2019-06-09
  • 2016-11-19
  • 2011-08-18
  • 2018-02-17
  • 2019-05-22
  • 1970-01-01
  • 2021-07-11
  • 2022-08-16
相关资源
最近更新 更多