【问题标题】:Get Averages Between Timestamps with multiple Statuses获取具有多个状态的时间戳之间的平均值
【发布时间】:2017-01-17 23:00:48
【问题描述】:

我正在尝试收集有关数据的简单统计信息,例如服务在线时间、服务离线时间、平均值等。我已经找到了一些解决方案,但它们都依赖于某些东西,例如行背靠背 (ROW_NUMBER -1) 或只有两种状态。

我的数据以日志的形式出现,总是在事后(即没有实时数据)。我要弄清楚的最大问题是有两个以上的州。目前,可能有四种不同的状态(启用、禁用、活动、非活动),我希望能够收集每种状态的数据。

我一次只提供一行数据,其中包含服务名称、旧状态、新状态和时间戳。目前,数据存储在一个表中。我无法更改数据的提供方式,但我可以更改数据的存储方式,并且我开始认为该表是我的主要缺点。

下面是一个示例,说明当前数据如何最终出现在我的表中:

CREATE TABLE IF NOT EXISTS statusupdates (
  sid int UNIQUE, 
  fullname VARCHAR(64), 
  oldstatus VARCHAR(16), 
  newstatus VARCHAR(16), 
  time TIMESTAMP);

INSERT INTO statusupdates VALUES
(null, 'fictHTTP', 'Off', 'On', '2017-01-01 02:20:00'),
(null, 'faked', 'On', 'Inactive', '2017-01-01 02:25:00'),
(null, 'ipsum', 'Inactive', 'On', '2017-01-01 02:30:00'),
(null, 'resultd', 'On', 'Inactive', '2017-01-01 02:35:00'),
(null, 'ipsum', 'On', 'Active', '2017-01-01 02:40:00'),
(null, 'fictHTTP', 'On', 'Active', '2017-01-01 02:45:00'),
(null, 'faked', 'Inactive', 'Off', '2017-01-01 02:50:00'),
(null, 'ipsum', 'Active', 'Off', '2017-01-01 02:55:00'),
(null, 'resultd', 'Inactive', 'Off', '2017-01-01 03:00:00');

我相信我发现的一种方法是将其缩小到一项,例如resultd。像SELECT fullname, newstatus, time FROM statusupdates WHERE fullname='resultd' ORDER BY time DESC; 这样的东西。然后使用该数据,使用相同的方法进行另一个查询,但向前迈出一步(因为它是降序)并从该记录中获取newstatus。当我输入时,它似乎很草率。

或者抓取oldstatus 并在第二个查询中使用它来查找以下记录的newstatus。但同样,这可能是草率的。

我知道还有一种方法可以将这两个理论查询结合起来。所以,总而言之,我远远超出了我的头脑,请原谅我!最后,我想查看每个状态的总时间、平均时间等统计数据。我现在最大的障碍是获取查询以提供结果,例如,ipsum 的每个时间戳条目,以便我可以从前一个条目中获取持续时间,并重复此操作,直到它完成所有记录。

或者,也许,我完全想多了,把所有数据都放在一个表中,让它变得太复杂了——到目前为止,我已经在这个项目中为不相关的项目做了两次。

额外的想法:一个实例,我可以做SELECT old_status, new_status, time FROM statusupdates WHERE time = '2017-01-01 03:00:00' 然后我可以像这样使用old_status,SELECT old_status, new_status, time FROM statusupdates WHERE time < 'timeStamp' AND new_status = 'oldStatus' 然后减去两个时间戳,这会给我一个例子的数据。但是,接下来如何做,然后下一步,直到它击中所有这些。

更新,另一个想法:结合您的一些很棒的建议,向后阅读日志怎么样? 没关系,到那时,它们被读取的方向无关紧要。当它遇到状态时,创建一个不完整的记录。它将包含 old_status 和 time_stamp 作为 end_time。然后当它再次遇到该服务时,它会检查 new_status = old_status 并使用 time_stamp 作为 start_time 更新记录。

这似乎会导致大量开销。必须检查每条记录以查看它是否存在,如果不存在,则更新一个。或者这还不算太糟糕?

【问题讨论】:

  • 这听起来像是一个数据流。你看过 Streams 吗?
  • 我觉得如果我这样做会更容易。但不是,静态文本文件在编写之后很长时间。
  • 也许您可以汇总文件数据?
  • 这基本上就是我想要做的。我最大的问题是如何首先构建它。一个实例,我可以做SELECT old_status, new_status, time FROM statusupdates WHERE time = '2017-01-01 03:00:00' 然后我可以像这样使用 old_status,SELECT old_status, new_status, time FROM statusupdates WHERE time < 'timeStamp' AND new_status = 'oldStatus' 然后减去两个时间戳,这会给我一个例子的数据。但是,接下来如何做,下一步,直到它击中所有的人。
  • 逐行搜索java流8文件。有几个例子可以帮助你

标签: java sql hsqldb usage-statistics


【解决方案1】:

您可以访问数据库中的窗口函数吗?如果是这样,您可以获得每条记录的下一行的值(按全名分区):

  select  fullname,
          newstatus,
          avg( time_diff ) as avg_time
  from    (
            select  fullname,
                    oldstatus,
                    newstatus,
                    /* get the time value of the next row for this fullname record */
                    lead( time ) over( 
                      partition by fullname 
                      order by time 
                      rows between 1 following and 1 following 
                    ) as next_time,
                    time,
                    next_time - time as time_diff
            from    statusupdates
          ) as a
   group by fullname,
          newstatus

编辑

在没有窗口函数的情况下,您可以通过稍微复杂的方式获得next_time

select a.*,
       b.next_time
from   statusupdates as a
       left join
       (
       select a.fullname,
              a.time,
              min( b.time ) as next_time
       from   statusupdates as a
              left join
              statusupdates as b
              on a.fullname = b.fullname
              and a.time < b.time
       group by a.fullname,
              a.time
       ) as b
       on a.fullname = b.fullname
       and a.time = b.time
;

【讨论】:

  • 不幸的是,我不相信 HSQLDB 支持 LEAD。 :(
  • 更新为尝试不使用铅的东西:-)
  • 非常感谢您!实际上,关于子查询的很棒的教程。我想最终得到一个状态更新的所有记录的完整数据集,我假设我必须为每一行运行该查询?目前我有大约 63,000 条记录。当然,一旦我弄清楚了,我会通过指定范围来降低这个数字。我不确定 SQL 是否有某种形式的循环或我可以执行的一些技巧来导致循环。
  • 我很高兴这有帮助!我认为您应该能够以灵活的方式使用生成的查询进行报告。例如,您可以将其用作子查询,按全名分组并从 next_time 中减去时间以获得经过的总时间。然后你可以总结或平均经过的时间。
  • 我倾向于发现,尽管您可以通过某些技巧在 SQL 中使用循环来做事,但它往往过于复杂,不适合 SQL 擅长的领域。将数据作为一个整体进行处理(在需要时使用适当的过滤器)可能会非常快,即使对于大型表也是如此。分组和聚合非常强大。您还可以查看索引字段字段以提高性能。
【解决方案2】:

你可以重新考虑你的数据结构为

statusUpdate {
  fullName,
  oldStatus,
  newStatus,
  startTime,
  endTime
}

现在您可以轻松地执行 SQL 查询来获取统计信息: 例子

select sum(endTime - startTime) from statusUpdate where oldStatus='active' group by fullName

如果您对数据库没有任何控制权,那么您可以在内存中创建一个,但如果这些数据量很大,这将非常昂贵。

编辑

到目前为止,Alex 的解决方案似乎是最好的,但如果数据库完全不受您的控制,您可以尝试在解析日志文件时构建统计信息,因为日志文件保证列出按时间排序的记录。 这可能会使用更少的内存空间,并且可以进一步微调。

public class Aggregation {

    String fullName;
    String prevStatus;
    String currStatus;
    Date prevTime;
    Date currTime;

    Map<String, List<Long>> timePeriodListMap = new HashMap<>();
    Map<String, Long> totalTimeMap = new HashMap<>();

    public void add(Status status) {
        if(!fullName.equals(status.fullName)) {
            throw new RuntimeException("Wrong "+fullName);
        }
        if(!currStatus.equals(status.oldStatus)) {
            throw new RuntimeException("Previous record's newStatus is not this record's oldStatus");
        }
        if(prevTime.compareTo(status.time) > 0){
            throw new RuntimeException("Unsorted by time");
        }

        if(currTime == null) {
            fullName = status.fullName;
            prevTime = status.time;             
        } else {
            if(!timePeriodListMap.containsKey(prevStatus)) {
                timePeriodListMap.put(prevStatus, new ArrayList<Long>());
            }
            timePeriodListMap.get(prevStatus).add(status.time.getTime() - currTime.getTime());
            prevTime = currTime;
            currTime = status.time;
        }           
        prevStatus = status.oldStatus;
        currStatus = status.newStatus;          
    }

}

Map<String, Aggregation> statusDB = new HashMap<String, TestClass.Aggregation>();
//read from the file as status one by one
public void process(Status status) {        
    if(!statusDB.containsKey(status.oldStatus)) {
        Aggregation aggregation = new Aggregation();
        statusDB.put(status.fullName, aggregation);
    }
    statusDB.get(status.fullName).add(status);
}

【讨论】:

  • 这太棒了。感谢您提供有关嵌套查询和连接的快速教程。有了这些信息,我就可以填充一个包含开始日期和结束日期的表格。这将大大简化这一点。现在我正在考虑是否应该在涉及数据库之前使用相同的方法弄清楚如何执行此操作。但在我的脑海中,我想我必须先将整个文件加载到内存中,因为我会跳过。
  • 由于性能问题,我可能会使用您的解决方案。再次感谢您。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-29
  • 1970-01-01
  • 2020-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多