如何解决SQL Server-在多天内按分钟汇总数据
上下文
我正在使用Microsoft SQL Server 2016。
有一个数据库表“ Raw_data”,其中包含计算机的状态以及启动时间。有几台机器,每台机器每分钟会多次将其状态写入数据库。
为了减少数据量,我正在尝试将数据聚合为1分钟的数据块,以保存数据以供进一步分析。由于容量限制,我想每隔几分钟执行一次此转换逻辑(例如,计划的SQL Server代理作业),删除原始数据,只保留聚合的数据。
为简化示例,我们假设“ Raw_data”看起来像这样:
╔════╦════════════╦════════╦═════════════════════╗
║ id ║ fk_machine ║ status ║ created_at ║
╠════╬════════════╬════════╬═════════════════════╣
║ 1 ║ 2222 ║ 0 ║ 2020-08-19 22:15:00 ║
║ 2 ║ 2222 ║ 3 ║ 2020-08-19 22:15:30 ║
║ 3 ║ 2222 ║ 5 ║ 2020-08-19 23:07:00 ║
║ 4 ║ 2222 ║ 1 ║ 2020-08-20 00:20:00 ║
║ 5 ║ 2222 ║ 0 ║ 2020-08-20 00:45:00 ║
║ 6 ║ 2222 ║ 5 ║ 2020-08-20 02:20:00 ║
╚════╩════════════╩════════╩═════════════════════╝
还有数据库表“ Dim_date”和“ Dim_time”,它们看起来像这样:
╔══════════╦══════════════╗
║ datekey ║ date_iso8601 ║
╠══════════╬══════════════╣
║ 20200101 ║ 2020-01-01 ║
║ 20200102 ║ 2020-01-02 ║
║ ... ║ ... ║
║ 20351231 ║ 2035-12-31 ║
╚══════════╩══════════════╝
╔═════════╦══════════╦═════════════════╗
║ timekey ║ time_iso ║ min_lower_bound ║
╠═════════╬══════════╬═════════════════╣
║ 1 ║ 00:00:01 ║ 00:00:00 ║
║ 2 ║ 00:00:02 ║ 00:00:00 ║
║ ... ║ ... ║ ... ║
║ 80345 ║ 08:03:45 ║ 08:03:00 ║
║ ... ║ ... ║ ... ║
║ 134504 ║ 13:45:04 ║ 13:45:00 ║
║ 134505 ║ 14:45:05 ║ 13:45:00 ║
║ ... ║ ... ║ ... ║
║ 235959 ║ 23:59:59 ║ 23:59:59 ║
╚═════════╩══════════╩═════════════════╝
结果应如下所示:
╔══════════════╦═════════════════╦════════════╦════════╦═══════════════╗
║ date_iso8601 ║ min_lower_bound ║ fk_machine ║ status ║ total_seconds ║
╠══════════════╬═════════════════╬════════════╬════════╬═══════════════╣
║ 2020-08-19 ║ 22:15:00 ║ 2222 ║ 0 ║ 30 ║
║ 2020-08-19 ║ 20:15:00 ║ 2222 ║ 3 ║ 30 ║
║ 2020-08-19 ║ 20:16:00 ║ 2222 ║ 3 ║ 60 ║
║ 2020-08-19 ║ 20:17:00 ║ 2222 ║ 3 ║ 60 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 2020-08-19 ║ 23:06:00 ║ 2222 ║ 3 ║ 60 ║
║ 2020-08-19 ║ 23:07:00 ║ 2222 ║ 5 ║ 60 ║
║ 2020-08-19 ║ 23:08:00 ║ 2222 ║ 5 ║ 60 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 2020-08-20 ║ 00:19:00 ║ 2222 ║ 5 ║ 60 ║
║ 2020-08-20 ║ 00:20:00 ║ 2222 ║ 1 ║ 60 ║
║ 2020-08-20 ║ 00:21:00 ║ 2222 ║ 1 ║ 60 ║
║ ... ║ ... ║ ... ║ ... ║ ... ║
║ 2020-08-20 ║ 00:44:00 ║ 2222 ║ 1 ║ 60 ║
║ 2020-08-20 ║ 00:45:00 ║ 2222 ║ 0 ║ 60 ║
╚══════════════╩═════════════════╩════════════╩════════╩═══════════════╝
尝试
要计算每分钟每个状态的持续时间,我使用了CTE和LEAD从数据库表中的下一个状态获取开始日期和时间,然后与维度表合并并汇总结果。
WITH CTE_MACHINE_STATES(START_DATEKEY,START_TIMEKEY,FK_MACHINE,END_DATEKEY,END_TIMEKEY)
AS (SELECT CAST(CONVERT(CHAR(8),CREATED_AT,112) AS INT),-- ISO: yyyymmdd
CONVERT(INT,REPLACE(CONVERT(CHAR(8),READING_TIME,108),':','')),STATUS,CAST(CONVERT(CHAR(8),LEAD(CREATED_AT,1) OVER(PARTITION BY FK_MACHINE
ORDER BY CREATED_AT),CONVERT(INT,''))
FROM RAW_DATA)
SELECT DATE_ISO8601,MIN_LOWER_BOUND,SUM(1) AS TOTAL_SECONDS -- Duration
FROM CTE_MACHINE_STATES
CROSS JOIN DIM_DATE
CROSS JOIN DIM_TIME
WHERE TIMEKEY >= START_TIMEKEY AND
TIMEKEY < END_TIMEKEY AND
END_TIMEKEY IS NOT NULL AND -- last entry per machine and status
DATEKEY BETWEEN START_DATEKEY AND END_DATEKEY
GROUP BY FK_MACHINE,DATE_ISO8610,MIN_LOWER_BOUND
ORDER BY DATE_ISO8610,MIN_LOWER_BOUND;
问题
如果状态持续到午夜之后,则不会正确汇总。例如,“ Raw_data”中id = 3的状态开始于23:07,结束于第二天的00:20。在这里,timekey大于end_timekey,因此过滤器TIMEKEY < END_TIMEKEY
将状态get从结果表中排除。对于如何更改联接条件以包括此类持久状态,我还没有提出解决方案,但可以得到预期的结果。
PS:我已经写过,通常每隔几秒钟就会进行一次状态更新。因此,该问题仅在边缘情况下才会发生,例如如果机器关闭了。
解决方案
很遗憾,我没有收到有关如何使用日期和时间维表获得预期结果的答案。但是dnoeth使用递归CTE的方法很好,所以我同意了:
WITH cte_outer AS (
SELECT fk_machine,status,created_at,DATEADD(minute,DATEDIFF(minute,'2000',created_at),'2000') AS min_lower_bound,--truncates seconds from start time
LEAD(created_at) OVER(PARTITION BY fk_machine ORDER BY created_at) AS end_time
FROM raw_data
),cte_recursive AS (
SELECT fk_machine,min_lower_bound,end_time,CASE
WHEN end_time > DATEADD(minute,1,min_lower_bound)
THEN DATEDIFF(s,min_lower_bound))
ELSE DATEDIFF(s,end_time)
END AS total_seconds
FROM cte_outer
UNION ALL
SELECT fk_machine,min_lower_bound),-- next time segment (minute)
end_time,CASE
WHEN end_time >= DATEADD(minute,2,min_lower_bound)
THEN 60
ELSE DATEDIFF(s,end_time)
END
FROM cte_recursive
WHERE end_time > DATEADD(minute,min_lower_bound)
)
SELECT min_lower_bound,fk_machine,total_seconds
FROM cte_recursive
ORDER BY fk_machine,min_lower_bound
解决方法
对于这样的事情,将键连接到单个日期时间并没有看起来那么昂贵。然后,您可以调用DATEDIFF()来检查比较的正,负,绝对值。我进行了类似的工作,将即时数据转换为数十年的分钟汇总,而datediff确实发挥了作用。但是,如果您仅提取原始数据并使用具有良好日期时间库的语言执行计算,则效果会更好。除非有,否则SQL始终是答案。
以下可能引起问题的原因如下:
WHERE TIMEKEY >= START_TIMEKEY AND
TIMEKEY < END_TIMEKEY AND
END_TIMEKEY IS NOT NULL AND
DATEKEY BETWEEN START_DATEKEY AND END_DATEKEY
如果日期和时间没有分开,您可以说:
WHERE DateTimeKey >= START_DateTimeKey AND
DateTimeKey < END_DateTimeKey AND
END_TIME-KEY IS NOT NULL
如果您尝试按时间值进行汇总,则消除任何时间键表将很有帮助,因为这可能是另一个问题来源。用递归和时间段持续时间替换时间键表可能是一个好主意。您还需要考虑以下情况:
事件的结束时间必须始终在汇总时段开始时间的开始时间之后:
DateDiff(second,Period_Start_Time,Event_End) > 0
事件的开始时间必须始终在汇总时间段结束时间之前>
DateDiff(second,Event_Start) <= @Period_Duration
有多种方法可以在各个时间段之间分配事件数据,但datediff也有助于线性分配。
,这是递归CTE的用例,每次递归将created_at
增加一分钟:
with cte as
(
select fk_machine,status,start_minute,end_time,case
when end_time > dateadd(minute,1,start_minute)
then datediff(s,created_at,dateadd(minute,start_minute))
else datediff(s,end_time )
end as seconds
from
(
select fk_machine,datediff(minute,created_at),0) as start_minute,lead(created_at)
over (PARTITION BY fk_machine
order by created_at) as end_time
from tab
) as dt
union all
select fk_machine,start_minute),case
when end_time >= dateadd(minute,2,start_minute)
then 60
else datediff(s,end_time)
end
from cte
where end_time > dateadd(minute,start_minute)
)
select * from cte
order by 1,3,4;
请参见fiddle
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。