SQL Server-在多天内按分钟汇总数据

如何解决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            ║
╚══════════════╩═════════════════╩════════════╩════════╩═══════════════╝

尝试

要计算每分钟每个状态的持续时间,我使用了CTELEAD从数据库表中的下一个状态获取开始日期和时间,然后与维度表合并并汇总结果。

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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-