解锁 SQL Server 2022的时间序列数据功能

SQL Server2022在处理时间序列数据时,SQL Server 提供了一些优化和功能,比如 DATE_BUCKET 函数、窗口函数(如 FIRST_VALUE 和 LAST_VALUE)以及其他时间日期函数,以便更高效地处理时间序列数据。


GENERATE_SERIES函数

SQL Server 2022 引入了一个新的函数 GENERATE_SERIES,它用于生成一个整数序列。
这个函数非常有用,可以在查询中生成一系列连续的数值,而无需创建临时表或循环。

GENERATE_SERIES ( start, stop [, step ] )
start:序列的起始值。
stop:序列的终止值。
step:每次递增或递减的步长(可选)。如果省略,默认为1。

使用场景包括快速生成一系列数据用于测试或填充表或者结合日期函数生成一系列日期值。

示例

生成的结果集将包含 20 行,每行显示从 '2019-02-28 13:45:23' 开始,按分钟递增的时间。

SELECT DATEADD(MINUTE, s.value, '2019-02-28 13:45:23') AS [Interval]
FROM GENERATE_SERIES(0, 20, 1) AS s;

对于每一个 s.value,DATEADD 函数将基准日期时间增加相应的分钟数。


DATE_BUCKET函数

SQL Server 2022 引入了一个新的函数 DATE_BUCKET,用于将日期时间值按指定的时间间隔分组(即分桶)。

这个函数在时间序列分析、数据聚合和分段分析等场景中非常有用。

DATE_BUCKET ( bucket_width, datepart, startdate, date )
bucket_width:时间间隔的大小,可以是整数。
datepart:时间间隔的类型,例如 year, month, day, hour, minute, second 等。
startdate:起始日期,用于定义时间间隔的起点。
date:需要分组的日期时间值。

使用 DATE_BUCKET 函数时,指定的时间间隔单位(如 YEAR、QUARTER、MONTH、WEEK 等)以及起始日期(origin)决定了日期时间值被分配到哪个存储桶。这种方式有助于理解时间间隔的计算是如何基于起始日期来进行的。

示例

DECLARE @date DATETIME = '2019-09-28 13:45:23';
DECLARE @origin DATETIME = '2019-01-28 13:45:23'; SELECT 'Now' AS [BucketName], @date AS [DateBucketValue]
UNION ALL
SELECT 'Year', DATE_BUCKET (YEAR, 1, @date, @origin)
UNION ALL
SELECT 'Quarter', DATE_BUCKET (QUARTER, 1, @date, @origin)
UNION ALL
SELECT 'Month', DATE_BUCKET (MONTH, 1, @date, @origin)
UNION ALL
SELECT 'Week', DATE_BUCKET (WEEK, 1, @date, @origin) --假如日期时间值如下:
Now: 2019-09-28 13:45:23 --按年分组:
DATE_BUCKET(YEAR, 1, @date, @origin)
从 2019-01-28 13:45:23 开始的年度存储桶,2019-09-28 落入 2019-01-28 至 2020-01-28 的存储桶中。
结果:2019-01-28 13:45:23 --按季度分组:
DATE_BUCKET(QUARTER, 1, @date, @origin)
从 2019-01-28 13:45:23 开始的季度存储桶,每个季度 3 个月。
2019-09-28 落入第三个季度存储桶(即从 2019-07-28 13:45:23 到 2019-10-28 13:45:23)。
结果:2019-07-28 13:45:23 --按月分组:
DATE_BUCKET(MONTH, 1, @date, @origin)
从 2019-01-28 13:45:23 开始的月度存储桶,每个月一个存储桶。
2019-09-28 落入第九个存储桶(即从 2019-09-28 13:45:23 到 2019-10-28 13:45:23)。
结果:2019-09-28 13:45:23 --按周分组:
DATE_BUCKET(WEEK, 1, @date, @origin)
从 2019-01-28 13:45:23 开始的每周存储桶。
2019-09-28 落入从 2019-09-23 13:45:23 到 2019-09-30 13:45:23 的存储桶。
结果:2019-09-23 13:45:23

SELECT 'Now' AS [BucketName], GETDATE() AS [BucketDate]
UNION ALL
SELECT '5 Minute Buckets', DATE_BUCKET (MINUTE, 5, GETDATE())
UNION ALL
SELECT 'Quarter Hour', DATE_BUCKET (MINUTE, 15, GETDATE()); Now:
BucketName: Now
BucketDate: 2024-07-26 16:14:11.030
这是当前时间,即 GETDATE() 返回的系统当前时间。 5 Minute Buckets:
BucketName: 5 Minute Buckets
BucketDate: 2024-07-26 16:10:00.000
这是将当前时间按 5 分钟间隔进行分组的结果。DATE_BUCKET(MINUTE, 5, GETDATE()) 返回当前时间所在的 5 分钟区间的起始时间。在这个例子中,16:14:11 落在 16:10:00 到 16:15:00 之间,因此返回 16:10:00。 Quarter Hour:
BucketName: Quarter Hour
BucketDate: 2024-07-26 16:00:00.000
这是将当前时间按 15 分钟间隔进行分组的结果。DATE_BUCKET(MINUTE, 15, GETDATE()) 返回当前时间所在的 15 分钟区间的起始时间。在这个例子中,16:14:11 落在 16:00:00 到 16:15:00 之间,因此返回 16:00:00。

更多实际场景示例

按自定义起始日期分组
假设我们有一系列事件时间 EventTime,希望从'2023-01-01'日期开始,每周进行分组统计事件数量。

--创建表 Events:

USE [testdb]
GO CREATE TABLE Events (
EventID INT PRIMARY KEY,
EventTime DATETIME
); INSERT INTO Events (EventID, EventTime) VALUES
(1, '2023-01-02 14:30:00'),
(2, '2023-01-08 09:15:00'),
(3, '2023-01-09 17:45:00'),
(4, '2023-01-15 12:00:00'),
(5, '2023-01-16 08:00:00'),
(6, '2023-01-22 19:30:00'),
(7, '2023-01-29 11:00:00'); --从'2023-01-01'起始日期开始,每周进行分组统计事件数量。
DECLARE @origin DATETIME = '2023-01-01'; SELECT
DATE_BUCKET(WEEK, 1, EventTime, @origin) AS WeekStart,
COUNT(*) AS EventCount
FROM
Events
GROUP BY
DATE_BUCKET(WEEK, 1, EventTime, @origin)
ORDER BY
WeekStart;

按自定义时间间隔分组
假设我们有一个传感器数据表 SensorReadings

USE [testdb]
GO CREATE TABLE SensorReadings (
ReadingID INT PRIMARY KEY, --唯一标识
ReadingTime DATETIME, --读数的时间
Value FLOAT --读数的值
); INSERT INTO SensorReadings (ReadingID, ReadingTime, Value) VALUES
(1, '2023-07-26 10:03:00', 23.5),
(2, '2023-07-26 10:05:00', 24.1),
(3, '2023-07-26 10:09:00', 22.8),
(4, '2023-07-26 10:15:00', 25.0),
(5, '2023-07-26 10:20:00', 23.9),
(6, '2023-07-26 10:27:00', 24.3),
(7, '2023-07-26 10:29:00', 24.5); --我们希望按 10 分钟的间隔将数据分组,并计算每个间隔的平均读数值。
SELECT
DATE_BUCKET(MINUTE, 10, ReadingTime) AS BucketStartTime,
ROUND(AVG(Value),4) AS AverageValue
FROM
SensorReadings
GROUP BY
DATE_BUCKET(MINUTE, 10, ReadingTime)
ORDER BY
BucketStartTime;

如果是传统方法需要使用公用表表达式CTE才能完成这个需求

--查询:按 10 分钟间隔分组并计算平均值
WITH TimeIntervals AS (
SELECT
ReadingID,
ReadingTime,
Value,
--将分钟数归约到最近的 10 分钟的整数倍, 从2010年到现在有多少个10分钟区间
DATEADD(MINUTE, (DATEDIFF(MINUTE, '2000-01-01', ReadingTime) / 10) * 10, '2010-01-01') AS BucketStartTime
FROM
SensorReadings
)
SELECT
BucketStartTime,
ROUND(AVG(Value), 4) AS AverageValue
FROM
TimeIntervals
GROUP BY
BucketStartTime
ORDER BY
BucketStartTime;

WITH TimeIntervals AS (...)公共表表达式(CTE)用于计算每条记录的 BucketStartTime。
DATEDIFF(MINUTE, '2000-01-01', ReadingTime) / 10 计算 ReadingTime 到基准时间 '2000-01-01' 的分钟数,然后除以 10,得到当前时间点所在的 10 分钟区间的索引。
DATEADD(MINUTE, ..., '2000-01-01') 将该索引转换回具体的时间点,即区间的起始时间。

查询主部分:
选择 BucketStartTime 和相应区间内读数值的平均值。
使用 GROUP BY 按 BucketStartTime 分组,并计算每个分组的平均值。
ORDER BY 用于按照时间顺序排列结果。


FIRST_VALUE 和 LAST_VALUE 窗口函数

在 之前版本的SQL Server 中,FIRST_VALUE 和 LAST_VALUE 是窗口函数,用于在一个分区或窗口中返回第一个或最后一个值。

SQL Server 2022 引入了新的选项 IGNORE NULLS 和 RESPECT NULLS 来处理空值(NULL)的方式,从而增强了这些函数的功能。

基本语法

FIRST_VALUE
返回指定窗口或分区中按指定顺序的第一个值。
FIRST_VALUE ( [scalar_expression ] )
OVER ( [ partition_by_clause ] order_by_clause [ rows_range_clause ] ) LAST_VALUE
返回指定窗口或分区中按指定顺序的最后一个值。
LAST_VALUE ( [scalar_expression ] )
OVER ( [ partition_by_clause ] order_by_clause [ rows_range_clause ] ) 新功能:IGNORE NULLS 和 RESPECT NULLS
IGNORE NULLS: 忽略分区或窗口中的 NULL 值。
RESPECT NULLS: 默认行为,包含分区或窗口中的 NULL 值。

示例

假设我们有一个表 MachineTelemetry,包含以下数据:

CREATE TABLE MachineTelemetry (
[timestamp] DATETIME,
SensorReading FLOAT
); INSERT INTO MachineTelemetry ([timestamp], SensorReading) VALUES
('2023-07-26 10:00:00', 23.5),
('2023-07-26 10:00:15', 24.1),
('2023-07-26 10:00:30', NULL),
('2023-07-26 10:00:45', 25.0),
('2023-07-26 10:01:00', NULL),
('2023-07-26 10:01:15', 23.9),
('2023-07-26 10:01:30', NULL),
('2023-07-26 10:01:45', 24.3);

默认行为(包含 NULL 值)

--使用 FIRST_VALUE 和 LAST_VALUE 进行差距分析
--默认行为(包含 NULL 值)
SELECT
[timestamp],
DATE_BUCKET(MINUTE, 1, [timestamp]) AS [timestamp_bucket],
SensorReading,
FIRST_VALUE(SensorReading) OVER (
PARTITION BY DATE_BUCKET(MINUTE, 1, [timestamp])
ORDER BY [timestamp]
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS [Default_FIRST_VALUE (RESPECT NULLS)],
LAST_VALUE(SensorReading) OVER (
PARTITION BY DATE_BUCKET(MINUTE, 1, [timestamp])
ORDER BY [timestamp]
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS [Default_LAST_VALUE (RESPECT NULLS)]
FROM MachineTelemetry
ORDER BY [timestamp];

忽略 NULL 值

--忽略 NULL 值
SELECT
[timestamp],
DATE_BUCKET(MINUTE, 1, [timestamp]) AS [timestamp_bucket],
SensorReading,
FIRST_VALUE(SensorReading) IGNORE NULLS OVER (
PARTITION BY DATE_BUCKET(MINUTE, 1, [timestamp])
ORDER BY [timestamp]
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS [First_Reading (IGNORE NULLS)],
LAST_VALUE(SensorReading) IGNORE NULLS OVER (
PARTITION BY DATE_BUCKET(MINUTE, 1, [timestamp])
ORDER BY [timestamp]
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS [Last_Reading (IGNORE NULLS)]
FROM MachineTelemetry
ORDER BY [timestamp];


总结

实际上,对于时间序列我们一般使用专业的时间序列数据库,例如InfluxDB 。

它使用 TSM(Time-Structured Merge Tree)作为存储引擎称,这是 LSM 树的一种变体,专门优化用于时间序列数据的写入和查询性能。

另外,SQL Server 的时间序列功能是使用行存储引擎(Row Store)作为其存储引擎,这意味着数据是按行进行存储和处理的。

在大部分场景下面,如果性能不是要求非常高,其实SQL Server 存储时间序列数据性能是完全足够的,而且额外使用InfluxDB数据库需要维护多一个技术栈,对运维要求更加高。

特别是现在追求数据库一体化的趋势背景下,无论是时间序列数据,向量数据,地理数据,json数据都最好在一个数据库里全部满足,减轻运维负担,复用技术栈,减少重复建设成本是比较好的解决方案。

参考文章

https://sqlbits.com/sessions/event2024/Time_Series_with_SQL_Server_2022

https://www.microsoft.com/en-us/sql-server/blog/2023/01/12/working-with-time-series-data-in-sql-server-2022-and-azure-sql/

https://www.mssqltips.com/sqlservertip/6232/load-time-series-data-with-sql-server/

本文版权归作者所有,未经作者同意不得转载。

解锁 SQL Server 2022的时间序列数据功能的更多相关文章

  1. SQL Server 2022 AlwaysOn新特性之包含可用性组介绍

    由于技术能力有限,文章仅能进行简要分析和说明,如有不对的地方,请指正,谢谢. SQL Server的容灾功能一直弱于Oracle和MySQL,无法自动同步元数据(用户.登录名.权限.SQL 代理作业. ...

  2. 恢复SQL Server被误删除的数据

    恢复SQL Server被误删除的数据 <恢复SQL Server被误删除的数据(再扩展)> 地址:http://www.cnblogs.com/lyhabc/p/4620764.html ...

  3. sql server实现自定义分割月功能

    本文目录列表: 1.为何出现自定义分割月需求 2.sql server实现自定义分割月功能 3.测试验证效果 4.总结语 5.参考清单列表   1.为何出现自定义分割月的需求   今天梳理一个平台的所 ...

  4. [转]实战 SQL Server 2008 数据库误删除数据的恢复

    实战 SQL Server 2008 数据库误删除数据的恢复 关键字:SQL Server 2008, recover deleted records 今天有个朋友很着急地打电话给我,他用delete ...

  5. SQL Server 2012 数据库各个版本功能对比

    作为这篇SQL SERVER 2008数据库各版本功能对比 的姊妹篇,就写点SQL Server 2012 各个版本的区别以及物理以及逻辑上的限制. 个部分来分http://technet.micro ...

  6. SQL Server 2008 数据库误删除数据的恢复

    原文:SQL Server 2008 数据库误删除数据的恢复 原文:http://www.cnblogs.com/dudu/archive/2011/10/15/sql_server_recover_ ...

  7. ASP.net(C#)利用SQL Server实现注册和登陆功能

    说说我现在吧,楼主现在从事的事IT行业,主攻DotNet技术:当然这次上博客园我也是有备而来,所有再次奉献鄙人拙作,以飨诸位,望诸位不吝赐教. 世界上大多数的工作都是熟练性的工种,编程也不例外,做久了 ...

  8. SQL Server 2005中的CHECKSUM功能

    原文:SQL Server 2005中的CHECKSUM功能 转自此处 页面 checksum 是SQL2005的新功能,提供了一种比残缺页检测强大的机制检测IO方面的损坏.以下是详细描述: 页面 C ...

  9. MS SQL Server数据库修复/MDF数据文件数据恢复/MDF质疑/mdf无法附加

    微软的SQL Server 数据库最常用的有两种类型的文件: 1.主要数据文件,文件后缀一般是.MDF: 2.事务日志文件,文件后缀一般是.LDF. 用户数据表.视图.存储过程等等数据,都是存放在MD ...

  10. 转:Sql Server中清空所有数据表中的记录

    如果要删除数据表中所有数据只要遍历一下数据库再删除就可以了,清除所有数据我们可以使用搜索出所有表名,构造为一条SQL语句进行清除了,这里我一一给各位同学介绍.   使用sql删除数据库中所有表是不难的 ...

随机推荐

  1. NumPy 二项分布生成与 Seaborn 可视化技巧

    二项分布 简介 二项分布是一种离散概率分布,用于描述在固定次数的独立试验中,事件"成功"的次数的概率分布.它通常用于分析诸如抛硬币.做选择题等具有两个结果(成功或失败)的事件. 参 ...

  2. 恭喜PaddleOCRSharp开源项目通过PaddleOCR社区常规赛优秀项目首次评选

    PaddleOCR优秀社区项目推荐: PaddleOCR社区常规赛首次评选结果已于日前出炉,本次优秀项目推广为大家带来的是[部署篇]:️ PaddleOCR的.NET调用库:包含文本识别.文本检测.基 ...

  3. 7.16考试总结(NOIP模拟17)[世界线·时间机器·weight]

    车如流水马如龙,花月正春风 前言 其实,一开始 T1 是看错了题的,我以为是无向图来着,就想直接搞到每一个联通块的完全图,然后减去总边数就好了. 发现错误之后,码了个暴力,想得 40pts 来着,没想 ...

  4. 机器学习算法(一):1. numpy从零实现线性回归

    系列文章目录 机器学习算法(一):1. numpy从零实现线性回归 机器学习算法(一):2. 线性回归之多项式回归(特征选取) @ 目录 系列文章目录 前言 一.理论介绍 二.代码实现 1.导入库 2 ...

  5. tomcat部署Jenkins

    安装环境 jdk 1.8 tomcat 9.0 jenkins 2.290 准备工作 安装好Tomcat,8080端口启动 安装好jdk,配置好环境变量 ECS服务器安全组放开8080端口 关闭防火墙 ...

  6. INFINI Labs 产品更新 | Easysearch 支持 SQL 查询、Console 告警功能支持邮件等多渠道

    INFINI Labs 产品又更新啦~.本次更新概要如下:Easysearch 新增 SQL 插件和JDBC 驱动,支持 SQL 查询,支持 SQL 常用函数等:Console 针对告警功能做了升级优 ...

  7. sql的删除语句

    好久没用过sql的删除语句了,今天写删除语句的时候报错了,应该是:   DELETE FROM 表名称 WHERE 列名称 = 值 我写成了: DELETE FROM 表名称 别名 WHERE 别名. ...

  8. Do not access Object.prototype method 'hasOwnProperty' from target object

    hasOwnProperty 判断对象是否为空 在使用 hasOwnProperty 判断对象是否为空时遇到了一下问题,总结一下 // Do not access Object.prototype m ...

  9. Base64编码和解码字符串

    Base64编码和解码字符串 package com.example.core.mydemo.cpic; import org.apache.commons.codec.binary.Base64; ...

  10. python实现推送消息到微信公众号

    使用到库: Requests 实现方式: 微信已开放了对应的接口,直接通过python的requests库,发起请求,实现推送消息到公众号 微信公众号准备: 1.没有注册微信公众号,可以使用微信提供的 ...