获取具有多个状态的时间戳之间的平均值
Get Averages Between Timestamps with multiple Statuses
我正在尝试收集有关数据的简单统计信息,例如服务在线时间、服务离线时间、平均值等。我已经找到了一些解决方案,但它们都依赖于某些东西,例如行背靠背(ROW_NUMBER -1
)或只有两种状态。
我的数据以日志的形式出现,总是在事后(即没有实时数据)。我要弄清楚的最大问题是有两个以上的状态。目前,可能有四种不同的状态(启用、禁用、活动、非活动),我希望能够收集每种状态的数据。
我每次只提供一行数据,其中包含服务名称、旧状态、新状态和时间戳。当前数据存储在单个 table 中。我无法更改数据的提供方式,但我可以更改数据的存储方式,我开始认为 table 是我的主要缺点。
这是一个示例,说明数据如何最终出现在我的 table 中:
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
的每个时间戳条目,这样我就可以从前一个条目中获取持续时间,然后重复此操作直到完成所有操作的记录。
或者,也许,我完全想多了,把所有的数据都塞进一个 table 中,这让事情变得太复杂了——到目前为止,我已经在这个项目上为不相关的项目做了两次.
额外的想法:单个实例,我可以做 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.
这似乎会导致大量开销。必须检查每条记录以查看它是否存在,如果不存在则创建一个,如果存在则更新一个。或者这还不算太糟?
你可以重新考虑你的数据结构作为
statusUpdate {
fullName,
oldStatus,
newStatus,
startTime,
endTime
}
现在您可以轻松地发出 SQL 查询来获取您的统计信息:
示例
select sum(endTime - startTime) from statusUpdate where oldStatus='active' group by fullName
如果您对数据库没有任何控制权,那么您可以在内存中创建一个,但如果此数据量很大,那将非常昂贵。
编辑
到目前为止,Alex 的解决方案似乎是最好的,但是如果数据库完全不受您的控制,您可以尝试在解析日志文件时构建统计信息,因为日志文件保证列出按时间排序的记录。
这可能会使用更少的内存 space 并且可以进一步微调。
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);
}
您可以访问数据库中的 window 函数吗?如果是这样,您可以获得每条记录的下一行的值(按全名分区):
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
编辑
在没有window函数的情况下,可以用稍微复杂一点的方式得到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
;
我正在尝试收集有关数据的简单统计信息,例如服务在线时间、服务离线时间、平均值等。我已经找到了一些解决方案,但它们都依赖于某些东西,例如行背靠背(ROW_NUMBER -1
)或只有两种状态。
我的数据以日志的形式出现,总是在事后(即没有实时数据)。我要弄清楚的最大问题是有两个以上的状态。目前,可能有四种不同的状态(启用、禁用、活动、非活动),我希望能够收集每种状态的数据。
我每次只提供一行数据,其中包含服务名称、旧状态、新状态和时间戳。当前数据存储在单个 table 中。我无法更改数据的提供方式,但我可以更改数据的存储方式,我开始认为 table 是我的主要缺点。
这是一个示例,说明数据如何最终出现在我的 table 中:
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
的每个时间戳条目,这样我就可以从前一个条目中获取持续时间,然后重复此操作直到完成所有操作的记录。
或者,也许,我完全想多了,把所有的数据都塞进一个 table 中,这让事情变得太复杂了——到目前为止,我已经在这个项目上为不相关的项目做了两次.
额外的想法:单个实例,我可以做 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.
这似乎会导致大量开销。必须检查每条记录以查看它是否存在,如果不存在则创建一个,如果存在则更新一个。或者这还不算太糟?
你可以重新考虑你的数据结构作为
statusUpdate {
fullName,
oldStatus,
newStatus,
startTime,
endTime
}
现在您可以轻松地发出 SQL 查询来获取您的统计信息: 示例
select sum(endTime - startTime) from statusUpdate where oldStatus='active' group by fullName
如果您对数据库没有任何控制权,那么您可以在内存中创建一个,但如果此数据量很大,那将非常昂贵。
编辑
到目前为止,Alex 的解决方案似乎是最好的,但是如果数据库完全不受您的控制,您可以尝试在解析日志文件时构建统计信息,因为日志文件保证列出按时间排序的记录。 这可能会使用更少的内存 space 并且可以进一步微调。
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);
}
您可以访问数据库中的 window 函数吗?如果是这样,您可以获得每条记录的下一行的值(按全名分区):
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
编辑
在没有window函数的情况下,可以用稍微复杂一点的方式得到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
;